PHP client for the InPost Buy API (inpsa) — sell your products through InPost's marketplace. Categories, offers, orders — all wrapped in a clean, type-safe interface.
InPost Buy (inpsa) lets merchants integrate their product catalog and orders with InPost's platform. You publish offers, receive orders, and update their status — all via REST API. This SDK handles authentication, serialization, and mapping so you focus on business logic.
- Categories — fetch product categories as a tree (read-only), with details and attributes
- Accept-Language — set response language (Polish
plor Englishen) viaLanguageenum - Offers — create single or batch offers with products, stock, and pricing; close/reopen; events; deposit types
- Offer attachments — list, upload, download, delete attachments (images etc.)
- Orders — list orders, fetch details, accept or refuse with status updates
- OAuth2 — client credentials grant (default) with in-memory caching; OAuth2 PKCE (Authorization Code flow) for merchant integrations (e.g. PrestaShop modules)
- Custom token provider — use
InPostBuyClient::createWithTokenProvider()with anyAccessTokenProviderInterface - Typed DTOs —
OfferDto,ProductDto,OrderDtoetc., no raw arrays in your code - Exceptions —
NotFoundException,BadRequestException,ServerExceptionetc. with HTTP status and error details
- PHP 8.1+
- Symfony HttpClient (or any PSR-18-compatible client via Symfony contracts)
- ramsey/collection
composer require malpka32/inpost-buy-sdk<?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]);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 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";
}
}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";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";
}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";
}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
);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'
));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 codegetResponseBody()— raw response bodygetErrorResponse()— parsed error (errorCode, errorMessage, details)isRetryable()— true for 5xx and 429getRetryAfterSeconds()— fromRetry-Afterheader 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";
}
}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 styleCI runs on push/PR (PHP 8.1–8.3): code style (PSR-12), PHPStan, tests with coverage. See Actions.
Official InPost Buy (inpsa) API docs: inpsa-api-portal.inpost-group.com
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.
MIT