Search by

malpka32 / inpost-buy-sdk

malpka32

PHP SDK for InPost Von Halsky (formerly InPost Buy, inpsa) marketplace API – offers, orders, returns, claims, refunds, price program, OAuth2 PKCE

Package info

github.com/malpka32/inpost-buy-sdk

Documentation

pkg:composer/malpka32/inpost-buy-sdk

Fund package maintenance!

Other

Statistics

Installs: 78

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.8.0 2026-09-30 13:01 UTC

This package is auto-updated.

Last update: 2026-09-30 13:23:17 UTC


README

PHP SDK for the InPost Von Halsky API (formerly InPost Buy, inpsa) — sell your products in the InPost Mobile app marketplace. Categories, offers, orders, returns, claims, refunds and the price program — all wrapped in a clean, type-safe interface.

Covers 100% of non-deprecated operations of InPost Von Halsky API 1.7.1 — see API coverage.

CI PHP PHPStan Code style: PSR-12 Packagist Version Maintained License

What is InPost Von Halsky?

InPost Von Halsky (launched as InPost Buy, API codename inpsa) is InPost's shopping platform inside the InPost Mobile app. Merchants integrate their product catalog and orders via REST API: publish offers, receive orders, handle returns and claims. This SDK handles authentication, serialization and mapping so you focus on business logic — whether you build a PrestaShop/WooCommerce/Magento module, an ERP connector or a custom shop integration.

Features

  • Categories — fetch product categories as a tree (read-only), with details and attributes
  • Offers — create single or batch offers; batch price/stock updates; attribute patch (upsert/remove); close/reopen; events; hints; deposit types
  • Offer attachments — list, upload (with display priority), reorder, download, delete
  • Price Program — opt-in/opt-out offers in InPost Attractive Prices, list declarations, track commands
  • Orders — list, fetch, accept/refuse, events, refund payment (full or partial), delivery methods
  • Returns — list (per organization or order), fetch, accept, reject
  • Claims (complaints) — list with filters, fetch details with documents, reject / partial refund / full refund, claim types dictionary
  • Organizations — list organizations available for the integration
  • OAuth2 — client credentials grant (default) with in-memory caching; OAuth2 PKCE (Authorization Code flow) for merchant integrations (e.g. PrestaShop/WooCommerce modules)
  • Custom token provider — use InPostBuyClient::createWithTokenProvider() with any AccessTokenProviderInterface
  • Accept-Language — set response language (Polish pl or English en) via Language enum
  • Typed DTOs, enums and collections — OfferDto, OrderDto, ReturnDto, ClaimDto, … no raw arrays in your code
  • Exceptions — NotFoundException, BadRequestException, ServerException etc. with HTTP status and error details

Why this SDK?

InPost also publishes an official, OpenAPI-generated PHP client. Choose what fits your project — this SDK focuses on:

  • Hand-written, domain-oriented API — $client->acceptReturn($id) instead of generic generated request objects
  • OAuth2 PKCE out of the box — the merchant authorization flow needed by multi-tenant modules (PrestaShop, WooCommerce, …)
  • No Guzzle lock-in — built on symfony/http-client-contracts, works with any compatible client
  • Strict quality — PHP 8.1+, PHPStan level 10, typed enums and collections, CI on PHP 8.1–8.3

Requirements

Installation

composer require malpka32/inpost-buy-sdk

Quick Start

<?php

use malpka32\InPostBuySdk\Client\InPostBuyClient;
use malpka32\InPostBuySdk\Dto\Common\ListSort;
use malpka32\InPostBuySdk\Dto\Offer\OfferStatus;
use malpka32\InPostBuySdk\Dto\Order\OrderStatus;
use Symfony\Component\HttpClient\HttpClient;

$client = new InPostBuyClient(
    httpClient: HttpClient::create(),
    clientId: 'your-client-id',
    clientSecret: 'your-client-secret',
    organizationId: 'your-org-uuid',
    sandbox: true,  // use false for production
);

// Fetch categories
$categories = $client->getCategories();
foreach ($categories as $category) {
    echo $category->name . " (" . $category->id . ")\n";
}

// Fetch offers
$offers = $client->getOffers(offerStatus: [OfferStatus::PUBLISHED], limit: 20);

// Fetch orders
$orders = $client->getOrders(status: OrderStatus::CREATED, sort: [ListSort::CREATED_AT_DESC]);

Usage

Language (Accept-Language)

API returns localized content (category names, error messages etc.). Set language via Language enum:

use malpka32\InPostBuySdk\Config\Language;

// Polish (default)
$client = new InPostBuyClient(..., language: Language::Polish);

// English
$client = InPostBuyClient::createWithTokenProvider(..., language: Language::English);

Supported values: Language::Polish (pl), Language::English (en).

Categories

Categories are returned as a tree (CategoryTreeCollection — each node has id, name, parentId, children).

$categories = $client->getCategories();

foreach ($categories as $node) {
    printf(
        "ID: %s | Name: %s | Parent: %s | Children: %d\n",
        $node->id,
        $node->name,
        $node->parentId ?? '-',
        count($node->children)
    );
}

You can iterate the tree recursively:

$tree = $client->getCategories();  // one API call, returns CategoryTreeCollection

foreach ($tree as $root) {
    echo $root->name . "\n";
    foreach ($root->children as $child) {
        echo "  " . $child->name . "\n";
    }
}

Creating an Offer

An offer is built from nested DTOs: ProductDto, StockDto, PriceDto. Optionally add attributes (e.g. color, size) and dimensions.

use malpka32\InPostBuySdk\Client\InPostBuyClient;
use malpka32\InPostBuySdk\Dto\Offer\OfferDto;
use malpka32\InPostBuySdk\Dto\Offer\PriceDto;
use malpka32\InPostBuySdk\Dto\Offer\Product\DimensionDto;
use malpka32\InPostBuySdk\Dto\Offer\Product\ProductDto;
use malpka32\InPostBuySdk\Dto\Offer\StockDto;
use malpka32\InPostBuySdk\Collection\AttributeValueCollection;
use malpka32\InPostBuySdk\Dto\Attribute\AttributeValueDto;

$product = new ProductDto(
    name: 'Cool T-Shirt',
    description: 'Comfortable cotton t-shirt in various sizes.',
    brand: 'MyBrand',
    categoryId: '67909821-cc25-45ec-80ce-5ac4f2f01032',  // from getCategories()
    sku: 'TSHIRT-001',
    ean: '5901234567890',
    attributes: AttributeValueCollection::fromAttributes(
        new AttributeValueDto('attr-color-uuid', ['Red'], 'en'),
        new AttributeValueDto('attr-size-uuid', ['M', 'L'])
    ),
    dimension: new DimensionDto(width: 200, height: 50, length: 300, weight: 200)  // mm, g
);

$offer = new OfferDto(
    externalId: 'SKU-TSHIRT-001',
    product: $product,
    stock: new StockDto(quantity: 10, unit: 'UNIT'),
    price: new PriceDto(amount: 99.99, currency: 'PLN', taxRateInfo: '23%')
);

$result = $client->putOffer($offer);
echo "Created offer ID: {$result->offerId}\n";

Batch Offers

Create multiple offers in one request:

use malpka32\InPostBuySdk\Collection\OfferCollection;

$offers = OfferCollection::fromOffers($offer1, $offer2, $offer3);
$ids = $client->putOffers($offers);

foreach ($ids as $id) {
    echo "Created: $id\n";
}

Listing and Filtering Offers

use malpka32\InPostBuySdk\Dto\Common\ListSort;
use malpka32\InPostBuySdk\Dto\Offer\OfferStatus;

$offers = $client->getOffers(
    offerStatus: [OfferStatus::PENDING, OfferStatus::PUBLISHED],
    limit: 50,
    offset: 0,
    sort: [ListSort::UPDATED_AT_DESC]
);

foreach ($offers as $offer) {
    echo $offer->externalId . " – " . $offer->product->name . "\n";
}

OAuth2 PKCE (merchant flow)

For integrations where merchants authorize via OAuth2 (e.g. PrestaShop modules), use PKCE flow:

use malpka32\InPostBuySdk\Auth\PkceOAuth2Client;
use malpka32\InPostBuySdk\Auth\PkceTokenProvider;
use malpka32\InPostBuySdk\Client\InPostBuyClient;
use malpka32\InPostBuySdk\Config\InPostBuyEndpoints;
use malpka32\InPostBuySdk\Config\Language;

// 1. Initiate authorization – redirect merchant to $result['authorize_url']
$pkceClient = new PkceOAuth2Client($httpClient);
$result = $pkceClient->initiateAuthorization(
    redirectUri: 'https://your-shop.com/module/callback',
    clientId: $clientId,
    sandbox: true,
    stateStorage: $yourPkceStateStorage,  // implement PkceStateStorageInterface
);

// 2. On callback – exchange code for tokens
$tokens = $pkceClient->exchangeCodeForTokens(
    code: $_GET['code'],
    redirectUri: $redirectUri,
    clientId: $clientId,
    clientSecret: $clientSecret,
    state: $_GET['state'],
    tokenUrl: InPostBuyEndpoints::tokenUrl($sandbox),
    stateStorage: $yourPkceStateStorage,
    tokenStorage: $yourTokenStorage,  // implement TokenStorageInterface
);

// 3. Create client with token provider
$tokenProvider = new PkceTokenProvider(
    $yourTokenStorage,
    $pkceClient,
    $clientId,
    $clientSecret,
    InPostBuyEndpoints::tokenUrl($sandbox),
);

$client = InPostBuyClient::createWithTokenProvider(
    $httpClient,
    $tokenProvider,
    $organizationId,
    sandbox: true,
    language: Language::English,  // optional: pl (default) or en
);

Orders

use malpka32\InPostBuySdk\Dto\Common\ListSort;
use malpka32\InPostBuySdk\Dto\Order\OrderPaymentStatus;
use malpka32\InPostBuySdk\Dto\Order\OrderStatus;
use malpka32\InPostBuySdk\Dto\Order\OrderStatusDto;
use malpka32\InPostBuySdk\Dto\Order\OrderUpdateStatus;

// List orders (optionally filter by status/payment status/sort)
$orders = $client->getOrders(
    status: OrderStatus::CREATED,
    paymentStatus: OrderPaymentStatus::PAID,
    sort: [ListSort::CREATED_AT_DESC],
);

foreach ($orders as $order) {
    echo $order->inpostOrderId . " – " . ($order->reference ?? 'no ref') . "\n";
}

// Fetch single order
$order = $client->getOrder('order-uuid-from-inpost');
if ($order !== null) {
    var_dump($order->status, $order->orderLines);
}

// Accept order
$client->updateOrderStatus('order-uuid', new OrderStatusDto(status: OrderUpdateStatus::ACCEPTED));

// Refuse with reason
$client->updateOrderStatus('order-uuid', new OrderStatusDto(
    status: OrderUpdateStatus::REFUSED,
    comment: 'Out of stock'
));

Refunds and delivery methods

use malpka32\InPostBuySdk\Dto\Offer\Core\MoneyDto;

// Full or partial refund of order payment
$refund = $client->refundOrder('order-uuid', new MoneyDto(amount: 49.99, currency: 'PLN'));
echo $refund->status?->value; // PENDING | SUCCESS | FAILED

// Delivery methods with localized names (v2)
foreach ($client->getDeliveryMethods() as $method) {
    echo $method->code->value . ': ' . $method->name . "\n"; // APM: Paczkomat InPost
}

Returns

use malpka32\InPostBuySdk\Dto\Order\Returns\ReturnStatus;

$result = $client->getReturns(status: [ReturnStatus::NEW], limit: 30);
echo "Total: {$result->page->total}\n";

foreach ($result->returns as $return) {
    echo $return->id . ' – order ' . $return->orderId . ' – ' . ($return->returnReason?->text ?? '-') . "\n";
    foreach ($return->orderLines as $line) {
        echo '  ' . $line->offer->product?->name . "\n";
    }
}

$client->getOrderReturns('order-uuid');     // returns of one order
$client->acceptReturn('return-uuid');        // or ->rejectReturn('return-uuid')

Claims (complaints)

use malpka32\InPostBuySdk\Dto\Order\Claim\ClaimListFilter;
use malpka32\InPostBuySdk\Dto\Order\Claim\ClaimSort;
use malpka32\InPostBuySdk\Dto\Order\Claim\ClaimState;

$claims = $client->getClaims(new ClaimListFilter(
    states: [ClaimState::RESOLUTION_IN_PROGRESS],
    submissionDateFrom: new DateTimeImmutable('-30 days'),
    sort: [ClaimSort::EXPIRES_AT_ASC],
));

foreach ($claims->claims as $summary) {
    if ($summary->orderId === null) {
        continue;
    }
    $claim = $client->getClaim($summary->orderId, $summary->claimId);

    echo $claim->specification?->issueDescription . "\n";
    foreach ($claim->specification?->supportingDocuments ?? [] as $document) {
        echo '  ' . $document->filename . ' (' . $document->type?->value . ")\n";
    }
}

// Resolve – each can be performed once per claim
$client->refundClaim('order-uuid', 'claim-uuid', 'Full refund granted');
$client->refundClaimPartially('order-uuid', 'claim-uuid', 'Partial refund – 25%');
$client->rejectClaim('order-uuid', 'claim-uuid', 'Damage caused by user');

$claimTypes = $client->getClaimTypes(); // localized dictionary

Price Program (InPost Attractive Prices)

use malpka32\InPostBuySdk\Collection\CampaignDeclarationProposalCollection;
use malpka32\InPostBuySdk\Dto\Offer\Campaign\CampaignDeclarationProposalDto;

$commands = $client->declareCampaignParticipation(CampaignDeclarationProposalCollection::fromProposals(
    CampaignDeclarationProposalDto::optIn('offer-uuid-1'),
    CampaignDeclarationProposalDto::optOut('offer-uuid-2'),
));

foreach ($commands as $command) {
    $status = $client->getCampaignCommandStatus($command->commandId);
    foreach ($status->errors as $error) {
        echo $error->message . "\n";
    }
}

$current = $client->getOfferCampaignDeclarations('offer-uuid-1');
echo $current->declaration?->participationMode?->value ?? 'no declaration';

Organizations

foreach ($client->getOrganizations() as $organization) {
    echo $organization->id . ' – ' . $organization->name . "\n";
}

Error Handling

The SDK throws specific exceptions for HTTP errors:

Exception HTTP
BadRequestException 400
UnauthorizedException 401
ForbiddenException 403
NotFoundException 404
UnprocessableEntityException 422
TooManyRequestsException 429
ServerException 5xx

All extend malpka32\InPostBuySdk\Exception\ApiException and provide:

  • getStatusCode() — HTTP status code
  • getResponseBody() — raw response body
  • getErrorResponse() — parsed error (errorCode, errorMessage, details)
  • isRetryable() — true for 5xx and 429
  • getRetryAfterSeconds() — from Retry-After header when available
use malpka32\InPostBuySdk\Exception\NotFoundException;
use malpka32\InPostBuySdk\Exception\ApiException;

try {
    $order = $client->getOrder('non-existent');
} catch (NotFoundException $e) {
    echo "Order not found: " . $e->getMessage();
} catch (ApiException $e) {
    echo "API error: " . $e->getStatusCode();
    if ($e->isRetryable()) {
        echo " – retry after " . ($e->getRetryAfterSeconds() ?? '?') . " seconds";
    }
}

Testing & quality

composer ci                # full check: PHPStan, cs-check, tests
composer test              # run tests
composer test:coverage     # tests + coverage (clover + HTML in build/coverage/)
composer phpstan           # static analysis (level 10)
composer cs-check          # code style check (PSR-12, dry run)
composer cs-fix            # fix code style (PHP-CS-Fixer)

With Docker:

docker compose run --rm test ci        # full check (recommended before commit)
docker compose run --rm test           # tests
docker compose run --rm test phpstan   # PHPStan
docker compose run --rm test cs-check  # code style

CI runs on push/PR (PHP 8.1–8.3): code style (PSR-12), PHPStan, tests with coverage. See Actions.

Documentation

Official InPost Von Halsky API docs: inpsa-api-portal.inpost-group.com

API coverage

All non-deprecated operations of API 1.7.1 are supported. The deprecated GET /v1/orders/delivery-methods is intentionally skipped in favour of v2.

Area Client methods
Organizations getOrganizations()
Categories getCategories(), getCategory(), getCategoryAttributes()
Offers getOffers(), putOffer(), putOffers(), updateOfferPrices(), updateOfferStocks(), patchOfferAttributes(), getOffer(), getOfferDetails(), closeOffer(), reopenOffer(), getOfferCommandStatus(), getOfferEvents(), getOfferHint(), getDepositTypes()
Offer attachments getOfferAttachments(), createOfferAttachment(), updateOfferAttachmentPriorities(), downloadOfferAttachment(), deleteOfferAttachment()
Price Program getCampaignDeclarations(), declareCampaignParticipation(), getOfferCampaignDeclarations(), getCampaignCommandStatus()
Orders getOrders(), getOrder(), updateOrderStatus(), getOrderCommandStatus(), getOrderEvents(), refundOrder(), getDeliveryMethods()
Returns getReturns(), getOrderReturns(), getReturn(), acceptReturn(), rejectReturn()
Claims getClaims(), getClaim(), rejectClaim(), refundClaimPartially(), refundClaim(), getClaimTypes()

Support the project

This project is actively maintained: we keep it in sync with InPost API changes and welcome issues and pull requests on GitHub.

If this library helps you, consider buying me a coffee — it allows me to maintain and update the library alongside InPost API changes.

→ buycoffee.to/malpka32

License

MIT