macropage / plentyone-php-sdk
PlentyOne REST API SDK based on Saloon PHP
Requires
- php: ^8.2
- saloonphp/saloon: ^4.0
Requires (Dev)
- erickskrauch/php-cs-fixer-custom-fixers: ^1.3
- friendsofphp/php-cs-fixer: ^3.94
- phpstan/phpstan: ^2.1
- rector/rector: ^2.4
This package is auto-updated.
Last update: 2026-04-07 16:46:05 UTC
README
A lightweight PHP SDK for the PlentyONE REST API, built on Saloon PHP v4.
Requirements
- PHP 8.2+
- Composer
Installation
composer require macropage/plentyone-php-sdk
Quick Start
use PlentyOne\PlentyOneConnector; $connector = new PlentyOneConnector('https://your-instance.plentymarkets.com/rest'); $connector->login('username', 'password'); // Find a variation by SKU $variation = $connector->variations()->find('YOUR-SKU')->json('entries.0'); // Find a variation by EAN/barcode $variation = $connector->variations()->list(['barcode' => '4002516915737'])->json('entries.0'); // Get all images for an item $images = $connector->images()->forItem($variation['itemId'])->json();
Authentication
The SDK handles Bearer token authentication automatically. After calling login(), the token is stored in memory and sent with every request. If the token expires (24h), the SDK re-authenticates automatically before the next request.
$connector = new PlentyOneConnector('https://your-instance.plentymarkets.com/rest'); $connector->login('username', 'password'); // All subsequent calls are authenticated
Supported API Calls
The SDK exposes resources via the connector. Each resource returns a Saloon Response object that you can call ->json() on (or ->json('path.to.value') for dot-notation access).
Items
| Method | Description | API Endpoint |
|---|---|---|
items()->get(int $itemId, ?string $with, ?string $lang) |
Get a single item (incl. texts) | GET /rest/items/{id} |
items()->list(...) |
List items with filters | GET /rest/items |
Available filters for list(): id, name, manufacturerId, flagOne, flagTwo, page, itemsPerPage, with, lang, updatedBetween, variationUpdatedBetween, variationRelatedUpdatedBetween, or
// Item with German texts $item = $connector->items()->get(699331)->json(); $name = $item['texts'][0]['name1'] ?? null; // Items by manufacturer $items = $connector->items()->list(manufacturerId: '1', itemsPerPage: 50)->json();
Variations
| Method | Description | API Endpoint |
|---|---|---|
variations()->find(string $numberExact) |
Find variation by SKU | GET /rest/items/variations?numberExact=... |
variations()->get(int $itemId, int $variationId, ?string $with) |
Get a specific variation | GET /rest/items/{id}/variations/{varId} |
variations()->list(array $filters) |
List variations with filters | GET /rest/items/variations |
variations()->documents(int $itemId, int $variationId) |
Get file-type properties (documents) | GET /rest/items/{id}/variations/{varId} |
variations()->addDocument(int $itemId, int $variationId, int $propertyId, string $fileUrl) |
Add or update a document | POST/PUT /rest/properties/relations |
variations()->removeDocument(int $itemId, int $variationId, int $propertyId) |
Remove a document | DELETE /rest/properties/relations/{id} |
Available filters for list(): numberExact, id, itemId, isActive, isMain, isBundle, page, itemsPerPage, categoryId, plentyId, referrerId, manufacturerId, supplierId, variationTagId, storeSpecial, with, lang, numberFuzzy, barcode, itemName, itemDescription, flagOne, flagTwo, sku, supplierNumber, supplierNumberFuzzy, updatedBetween, createdBetween, relatedUpdatedBetween, stockWarehouseId
Useful with relations: item, variationBarcodes, variationCategories, variationDefaultCategory, variationSalesPrices, variationSuppliers, variationShippingProfiles, variationClients, variationMarkets, tags, properties, variationProperties
The default variation response already includes purchasePrice (current EK netto), movingAveragePrice (gleitender Durchschnitts-EK), weightG, weightNetG, widthMM, lengthMM, heightMM and many more fields without any with parameter.
Images
| Method | Description | API Endpoint |
|---|---|---|
images()->forItem(int $itemId) |
Get all images of an item | GET /rest/items/{id}/images |
images()->forVariation(int $itemId, int $variationId) |
Get variation image links | GET /rest/.../variation_images |
images()->upload(...) |
Upload an image (via URL or base64) | POST /rest/items/{id}/images/upload |
images()->updatePosition(int $itemId, int $imageId, int $position) |
Change image sort order | PUT /rest/items/{id}/images/{imgId} |
images()->delete(int $itemId, int $imageId) |
Delete an image | DELETE /rest/items/{id}/images/{imgId} |
images()->linkToVariation(int $itemId, int $variationId, int $imageId) |
Link image to a variation | POST /rest/.../variation_images |
images()->unlinkFromVariation(int $itemId, int $variationId, int $imageId) |
Unlink image from a variation | DELETE /rest/.../variation_images/{imgId} |
Upload via URL
$response = $connector->images()->upload( itemId: 668423, uploadUrl: 'https://example.com/image.png', position: 0, name: 'Product front', alternate: 'Alt text for SEO', ); $imageId = $response->json('id');
Upload via base64
$base64 = base64_encode(file_get_contents('/path/to/image.png')); $response = $connector->images()->upload( itemId: 668423, uploadImageData: $base64, uploadFileName: 'product.png', fileType: 'png', position: 0, ); $imageId = $response->json('id');
Note: PlentyONE processes images asynchronously. Right after upload,
width,height, andsizemay still be0. The metadata is populated after a few seconds.
Categories
| Method | Description | API Endpoint |
|---|---|---|
categories()->list(array $filters) |
List categories with filters | GET /rest/categories |
categories()->get(int $id, ?string $with, ?string $lang, ...) |
Get a single category by ID | GET /rest/categories/{id} |
Available filters for list(): type, with, page, itemsPerPage, parentId, lang, name, level, plentyId, linklist, updatedAt, tagId, metaKeywords
// Single category with German details $response = $connector->categories()->get(4737, with: 'details', lang: 'de'); $category = $response->json('entries.0'); echo $category['details'][0]['name']; // "Miele" echo $category['parentCategoryId']; // 1793
Note:
categories()->get()returns a paginated response with one entry — even for a single-ID lookup. Always grabentries.0.
Manufacturers
| Method | Description | API Endpoint |
|---|---|---|
manufacturers()->get(int $id) |
Get a single manufacturer by ID | GET /rest/items/manufacturers/{id} |
$itemId = 699331; $item = $connector->items()->get($itemId)->json(); $manu = $connector->manufacturers()->get($item['manufacturerId'])->json(); echo $manu['name']; // "Miele"
Accounts (Contacts / Suppliers)
Suppliers in PlentyONE are stored as contacts with typeId = 4.
| Method | Description | API Endpoint |
|---|---|---|
accounts()->getContact(int $contactId, ?string $with) |
Get a single contact (supplier, customer, …) | GET /rest/accounts/contacts/{id} |
// Resolve a supplier name from variationSuppliers $variation = $connector->variations()->get($itemId, $varId, 'variationSuppliers')->json(); foreach ($variation['variationSuppliers'] as $sup) { $contact = $connector->accounts()->getContact($sup['supplierId'], 'options')->json(); echo trim($contact['firstName'] . ' ' . $contact['lastName']) . "\n"; }
Stock
| Method | Description | API Endpoint |
|---|---|---|
stock()->list(?int $variationId, ?string $updatedAtFrom, ...) |
List stock with filters | GET /rest/stockmanagement/stock |
stock()->forVariation(int $variationId) |
Convenience: stock entries for one variation | GET /rest/stockmanagement/stock?variationId=... |
stock()->warehouses() |
List all warehouses (id, name, type, …) | GET /rest/stockmanagement/warehouses |
// Net stock for one variation across all warehouses $entries = $connector->stock()->forVariation(35747)->json('entries'); $netTotal = array_sum(array_column($entries, 'netStock')); // Find the "Leverkusen" warehouse and get its net stock $warehouses = $connector->stock()->warehouses()->json(); $leverkusenId = null; foreach ($warehouses as $wh) { if (stripos($wh['name'], 'Leverkusen') !== false) { $leverkusenId = $wh['id']; break; } } foreach ($entries as $e) { if ($e['warehouseId'] === $leverkusenId) { echo "Leverkusen netto: {$e['netStock']}\n"; } }
Shipping
| Method | Description | API Endpoint |
|---|---|---|
shipping()->presets(...) |
List shipping presets (profiles) | GET /rest/orders/shipping/presets |
shipping()->forItem(int $itemId) |
Item shipping profiles for one item | GET /rest/items/{id}/item_shipping_profiles |
shipping()->allItemProfiles() |
All item shipping profile links | GET /rest/items/item_shipping_profiles |
Webstores
| Method | Description | API Endpoint |
|---|---|---|
webstores()->list() |
List all webstores/clients with PlentyID and name | GET /rest/webstores |
$webstores = $connector->webstores()->list()->json(); foreach ($webstores as $ws) { echo $ws['storeIdentifier'] . ': ' . $ws['name'] . "\n"; }
Order Referrers (Sales Channels)
| Method | Description | API Endpoint |
|---|---|---|
referrers()->list() |
List all order referrers/sales channels with ID and name | GET /rest/orders/referrers |
$referrers = $connector->referrers()->list()->json(); foreach ($referrers as $r) { echo $r['id'] . ': ' . ($r['backendName'] ?? $r['name']) . "\n"; }
Properties
| Method | Description | API Endpoint |
|---|---|---|
properties()->list(?int $page, ?int $itemsPerPage, ?string $with) |
List all properties (paginated) | GET /rest/properties |
properties()->groups(?int $page, ?int $itemsPerPage, ?string $with) |
List all property groups (paginated) | GET /rest/properties/groups |
$response = $connector->properties()->list(page: 1, itemsPerPage: 100, with: 'names'); $properties = $response->json('entries'); $response = $connector->properties()->groups(page: 1, itemsPerPage: 100, with: 'names'); $groups = $response->json('entries');
Tags
| Method | Description | API Endpoint |
|---|---|---|
tags()->list(?int $page, ?int $itemsPerPage, ?string $with) |
List all tags | GET /rest/tags |
tags()->link(int $tagId, string $tagType, int $relationshipValue) |
Link a tag to a variation/item | POST /rest/tags/relationships |
tags()->unlink(int $tagId, string $tagType, int $relationshipValue) |
Unlink a tag from a variation/item | DELETE /rest/tags/relationships |
Catalogs
| Method | Description | API Endpoint |
|---|---|---|
catalogs()->list() |
List all catalogs | GET /rest/catalogs |
catalogs()->get(string $id) |
Get a single catalog | GET /rest/catalogs/{id} |
catalogs()->preview(string $id) |
Preview catalog data | GET /rest/catalogs/{id}/preview |
catalogs()->publicUrl(string $id) |
Get the public download URL | GET /rest/catalogs/{id}/public-url |
catalogs()->privateUrl(string $id) |
Get the private download URL | GET /rest/catalogs/{id}/private-url |
catalogs()->export(string $id) |
Trigger an export run | POST /rest/catalogs/{id}/export |
catalogs()->versions(string $id) |
List all versions of a catalog | GET /rest/catalogs/{id}/versions |
catalogs()->version(string $id, string $versionId) |
Get a specific catalog version | GET /rest/catalogs/{id}/versions/{versionId} |
catalogs()->templates() |
List all catalog templates | GET /rest/catalogs/templates |
Documents (File-Properties)
Documents (manuals, data sheets, etc.) are stored as properties with cast: "file" on variations.
// Get all documents for a variation $docs = $connector->variations()->documents($itemId, $variationId); // Add a document (creates property link if it doesn't exist) $connector->variations()->addDocument( itemId: 668423, variationId: 19408, propertyId: 498, // e.g. 498 = manual fileUrl: 'https://example.com/manual.pdf', ); // Remove a document $connector->variations()->removeDocument( itemId: 668423, variationId: 19408, propertyId: 498, );
Need More?
If you need additional endpoints, feel free to fork the repository or submit a pull request. The SDK follows a consistent Saloon pattern: one Request class per endpoint, grouped under a thin Resource class on the connector.
License
MIT