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
- phpunit/phpunit: ^11.0
- rector/rector: ^2.4
Suggests
None
Provides
None
Conflicts
None
Replaces
None
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()->getDescription(int $itemId, int $variationId, string $lang) |
Get the item description in one language | GET /rest/items/{id}/variations/{varId}/descriptions/{lang} |
items()->updateDescription(int $itemId, int $variationId, string $description, string $lang) |
Update only the long description in one language | PUT /rest/items/{id}/variations/{varId}/descriptions/{lang} |
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();
PlentyONE stores this value in texts[].description on the item. However, the dedicated
description endpoint requires the item's main variation ID for addressing. Only description is
sent in the update body; the other item and text fields are not included.
$itemId = 12345; $mainVariationId = 67890; $lang = 'de'; $description = $connector->items() ->getDescription($itemId, $mainVariationId, $lang) ->json('description'); $connector->items()->updateDescription( $itemId, $mainVariationId, '<p>The new long description</p>', $lang, );
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(?array $with) |
List all warehouses (id, name, typeId, …). Pass with: ['repairWarehouse'] to load the repair-warehouse relation. |
GET /rest/stockmanagement/warehouses |
stock()->redistribute(array $redistributions) |
Rebook ("umbuchen") stock between warehouses / storage locations | PUT /rest/stockmanagement/stock/redistribute |
stock()->moveBetweenWarehouses(int $variationId, int $currentWarehouseId, int $newWarehouseId, int $quantity, ...) |
Convenience: move a single variation quantity between two warehouses | PUT /rest/stockmanagement/stock/redistribute |
// Net stock for one variation across all warehouses $entries = $connector->stock()->forVariation(35747)->json('entries'); $netTotal = array_sum(array_column($entries, 'netStock')); // Find a warehouse by name and get its net stock $warehouses = $connector->stock()->warehouses()->json(); $warehouseId = null; foreach ($warehouses as $wh) { if (stripos($wh['name'], 'Main Warehouse') !== false) { $warehouseId = $wh['id']; break; } } foreach ($entries as $e) { if ($e['warehouseId'] === $warehouseId) { echo "Net stock: {$e['netStock']}\n"; } }
Redistribute (rebook stock between warehouses)
redistribute() moves stock from one warehouse/storage location to another – the total
stock stays the same, only the location changes. For batch-managed or BBD/MHD variations,
batch / bestBeforeDate must be included in the redistribution object.
// Move 2 units of a variation from warehouse 106 to warehouse 122 $connector->stock()->moveBetweenWarehouses( variationId: 3686, currentWarehouseId: 106, newWarehouseId: 122, quantity: 2, reasonId: 101, // movement reason (Bewegungsgrund), if required by your config ); // Or several redistributions in one call $connector->stock()->redistribute([ ['variationId' => 3686, 'reasonId' => 101, 'quantity' => 2, 'currentWarehouseId' => 106, 'newWarehouseId' => 122], ['variationId' => 3686, 'reasonId' => 101, 'quantity' => 1, 'currentWarehouseId' => 125, 'newWarehouseId' => 122], ]);
Note: This is a writing call. It actually moves stock in PlentyONE – test against a sandbox / with a safe
reasonIdbefore using it in production.
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"; }
Orders
| Method | Description | API Endpoint |
|---|---|---|
orders()->list(array $filters) |
List orders, supports date-range filters | GET /rest/orders |
orders()->search(array $filters) |
Search orders with filters (paginated) | GET /rest/orders/search |
orders()->get(int $orderId, ?array $with) |
Get a single order by ID | GET /rest/orders/{orderId} |
orders()->statuses(?string $lang, ?int $page, ?int $itemsPerPage) |
List the configured order statuses (id + names, paginated) | GET /rest/orders/statuses |
orders()->itemTransactions(int $orderId, ?int $page, ?int $itemsPerPage) |
List item stock transactions for one order (paginated) | GET /rest/orders/items/transactions |
orders()->update(int $orderId, array $body) |
Update an order (send only changed fields) | PUT /rest/orders/{orderId} |
orders()->setStatus(int $orderId, int|float|string $statusId) |
Convenience: change an order's status | PUT /rest/orders/{orderId} |
list()vs.search()— pick the right one:search()filterscreatedAt/updatedAtfor equality only,list()is the only order endpoint that understands date ranges. Uselist()for backfills and incremental syncs,search()for everything the list endpoint cannot filter (see the table below).
Filters for list() (all verified against a live system): createdAtFrom, createdAtTo,
updatedAtFrom, updatedAtTo, page, itemsPerPage, with.
list() — GET /rest/orders |
search() — GET /rest/orders/search |
|
|---|---|---|
Date ranges (createdAtFrom/To, updatedAtFrom/To) |
✅ | ❌ (createdAt matches exactly; …From/…To fail or are ignored) |
orderTypeId, withDeleted |
❌ silently ignored (HTTP 200, unchanged count) | ✅ |
Dynamic filters (orderProperty_{typeId}, …) |
❌ | ✅ |
max itemsPerPage |
250 | 250 |
Pagination cap page * itemsPerPage |
60,000 | 60,000 |
Details for list():
- Dates must be W3C including timezone (
2026-08-17T00:00:00+02:00). Without a timezone the API answers HTTP 422 (Error parsing date string. String must be in W3C format.). - Both range bounds are inclusive — consecutive slices may return a boundary order twice, but never drop one.
itemsPerPageabove 250 → HTTP 422 (The number of items per page exceeds the maximum of 250.).page * itemsPerPageabove 60,000 → HTTP 422. To read more than 60,000 orders, split the query into smaller time slices. The SDK stays a thin wrapper and does not paginate, retry or throttle for you.
Common filters for search(): orderId, plentyId, orderTypeId, statusId, referrerId, ownerId, locationId, createdAt, updatedAt, contactData, itemVariationId, variationNumber, documentNumber, tag, shippingStatus, sortBy, sortOrder, page, itemsPerPage, with, lazyLoaded, withDeleted. Dynamic filters: orderProperty_{typeId}, orderDate_{typeId}, documentNumber_{documentType}, addressRelation_{typeId}, relationReference_{referenceType}_{relationType}.
Order type IDs: 1 = sales order, 2 = delivery, 3 = returns, 4 = credit note, 5 = warranty, 6 = repair, 7 = offer, 8 = advance order, 9 = multi-order, 10 = multi credit note, 11 = multi delivery, 12 = reorder, 13 = partial delivery.
Important: The search loads no relations by default (
lazyLoaded = true). To getproperties,orderItems,addressesetc. you must request them viawith: [...](or setlazyLoaded => false).
// All orders of a time span, including their items and amounts. // list() is the only order endpoint that can do ranges - search() cannot. $resp = $connector->orders()->list([ 'createdAtFrom' => '2026-08-17T00:00:00+02:00', 'createdAtTo' => '2026-08-19T00:00:00+02:00', 'itemsPerPage' => 250, 'with' => ['orderItems.amounts'], ]); $orders = $resp->json('entries'); // Incremental sync: everything that CHANGED in the time span - this also catches // cancellations and returns booked onto older orders. $page = 1; do { $resp = $connector->orders()->list([ 'updatedAtFrom' => '2026-08-18T00:00:00+02:00', 'updatedAtTo' => '2026-08-19T00:00:00+02:00', 'itemsPerPage' => 250, 'page' => $page, 'with' => ['orderItems.amounts'], ]); foreach ($resp->json('entries') as $order) { // ... process $order } $page++; } while (! $resp->json('isLastPage')); // Mind the cap: page * itemsPerPage must stay below 60,000. For bigger result sets, // cut the range into smaller slices (e.g. per day or per month). // Find the Plenty order for an Amazon order number. // The Amazon order id is stored as order property type 7 ("External order ID"). $resp = $connector->orders()->search([ 'orderProperty_7' => '123-1234567-1234567', 'with' => ['orderItems', 'addresses', 'properties'], ]); $order = $resp->json('entries.0'); // Latest orders of a sales channel (referrerId 4.01 = Amazon Germany) $latest = $connector->orders()->search([ 'referrerId' => '4.01', 'sortBy' => 'orderId', 'sortOrder' => 'desc', 'itemsPerPage' => 20, 'with' => ['properties'], ])->json('entries'); // Single order with relations $order = $connector->orders()->get(12345, ['orderItems', 'addresses', 'relations'])->json(); // Configured order statuses (paginated). Statuses are NOT a fixed enum — // they are configured per system, so resolve names from the instance. $statuses = []; $page = 1; do { $resp = $connector->orders()->statuses('de', $page, 100); foreach ($resp->json('entries') as $s) { $statuses[(string) $s['statusId']] = $s['names']['de'] ?? null; } $isLast = $resp->json('isLastPage'); $page++; } while (! $isLast); // $statuses now maps each configured statusId to its name, e.g. $statuses['7'] // Stock transactions belonging to the items of one order. $transactions = $connector->orders()->itemTransactions(12345, page: 1, itemsPerPage: 250); foreach ($transactions->json('entries') as $transaction) { // Each entry links back through orderItemId. } // Change an order's status (writing call — mutates the order in PlentyONE). // Pass the exact configured statusId (sub-statuses are decimal, e.g. 8.01 !== 8.1). $connector->orders()->setStatus(12345, 7); // Or update several order fields at once. $connector->orders()->update(12345, ['statusId' => 7]);
Note:
update()/setStatus()are writing calls that change real data in PlentyONE. Test against a non-critical order first. Order statuses are configured per system — resolve the targetstatusIdviaorders()->statuses().
itemTransactions() deliberately accepts exactly one scalar order ID. PlentyONE
returns HTTP 200 while silently ignoring an array of order IDs and the tested
createdAt... / updatedAt... range filters. Use the pagination envelope and do
not infer that an unsupported filter worked merely because the response was successful.
Reorders
| Method | Description | API Endpoint |
|---|---|---|
reorders()->create(array $payload) |
Create header and items; requires explicit statusId and enforces typeId 12 | POST /rest/reorders |
reorders()->update(int $orderId, array $payload) |
Update header or items | PUT /rest/reorders/{orderId} |
tags()->link(int $tagId, 'order', int $orderId) |
Attach a tag to the created reorder | POST /rest/tags/relationships |
reorders()->get(int $orderId, ?array $with = null) |
Get a type-12 order with its items and related data | GET /rest/orders/{orderId} |
reorders()->search(array $filters = []) |
Search type-12 orders with related data (paginated) | GET /rest/orders/search |
reorders()->deliveryDate(int $orderId) |
Get the calculated delivery date of a reorder | GET /rest/reorders/{orderId}/delivery_date |
$reorder = $connector->reorders()->get(1497905)->json(); $items = $reorder['orderItems']; // Each item includes its name, variation, quantity, amounts (prices/discounts), // properties, dates, references and stock transactions. $response = $connector->reorders()->search([ 'statusId' => 19.1, 'page' => 1, 'itemsPerPage' => 50, ]); $reorders = $response->json('entries'); // Optional: replace the default relations for a smaller response. $reorder = $connector->reorders()->get(1497905, ['orderItems.amounts'])->json(); $deliveryDate = $connector->reorders()->deliveryDate(12345)->json('deliveryDate');
get() and search() reuse the order endpoints, as documented in the
purchase-order guide.
Their default with includes order amounts/VATs, dates, properties, references,
addresses, supplier contact, receiving warehouse, accounting location, payment
terms/payments, tags, comments, document metadata, shipping packages/pallets, and
all items with amounts, properties/order properties, dates, references, variation
data, barcodes, transactions, comments and serial numbers. The API response is
returned unchanged, including fields not explicitly known to the SDK.
This loads the related order data, not document file contents, the Flow Tracker,
mail history or every nested product-master relation. Pass an explicit with
array to select other supported relations; [] requests only API defaults.
get() throws UnexpectedValueException if the ID belongs to a different order
type. search() always enforces orderTypeId = 12, even if a different value is
passed, and returns one page; use entries, isLastPage and lastPageNumber to
read further pages. The order-search filter limitations above still apply.
deliveryDate() returns PlentyONE's calculated delivery date. It is not necessarily
the same value as order date type 11 stored on the reorder.
Create a reorder with header, supplier, receiving warehouse and items in one call, then attach a tag (replace the example IDs with your own):
$created = $connector->reorders()->create([ 'statusId' => 19, // Santos: open. 19.1 starts the supplier-order Flow. 'plentyId' => $plentyId, 'ownerId' => $ownerId, 'referrerId' => 0, 'relations' => [ ['referenceType' => 'contact', 'referenceId' => $supplierId, 'relation' => 'sender'], ['referenceType' => 'warehouse', 'referenceId' => $warehouseId, 'relation' => 'receiver'], ], 'dates' => [ ['typeId' => 7, 'date' => '2026-11-02T00:00:00+01:00'], // Payment due date ['typeId' => 11, 'date' => '2026-10-15T00:00:00+02:00'], // Delivery date ], 'properties' => [['typeId' => 6, 'value' => 'de']], // Document language 'orderItems' => [[ 'typeId' => 1, 'itemVariationId' => $variationId, // Internal variation ID, not variation number 'quantity' => 2, 'orderItemName' => 'Example item', 'properties' => [['typeId' => 21, 'value' => '1']], 'dates' => [['typeId' => 11, 'date' => '2026-10-15T00:00:00+02:00']], 'amounts' => [[ 'currency' => 'EUR', 'exchangeRate' => 1, 'priceOriginalGross' => 12.64, 'surcharge' => 0, 'discount' => 0, 'isPercentage' => true, ]], ]], ])->json(); $orderId = (int) $created['id']; // Persist this ID before the separate tag call. $connector->tags()->link(227, 'order', $orderId); $reorder = $connector->reorders()->get($orderId)->json(); // Optional: change a delivery date later. $connector->reorders()->update($orderId, [ 'dates' => [['typeId' => 11, 'date' => '2026-10-20T00:00:00+02:00']], ]);
create() requires a non-null statusId and always sends typeId = 12.
Status IDs depend on your PlentyONE configuration; 19 is Santos' open status.
Both methods pass the nested payload through, including all supplied items,
prices, discounts, currency/exchange rate, dates and properties. Further field
validation is performed by PlentyONE. For item updates, include the existing
order-item id. Do not copy response-only IDs, documents or stock transactions
from an existing reorder into a creation payload.
The example keeps status 19 and omits purchase date type 16. Setting that date or changing the status can initiate ordering; these methods do not add either automatically. Tag linking is a separate write: if it fails, the reorder still exists. Resume using the saved order ID instead of creating it again.
Properties
| Method | Description | API Endpoint |
|---|---|---|
properties()->list(?int $page, ?int $itemsPerPage, ?string $with) |
List all properties (paginated) | GET /rest/properties |
properties()->get(int $propertyId) |
Get one property | GET /rest/properties/{propertyId} |
properties()->create(array $payload) |
Create a property (writing call) | POST /rest/properties |
properties()->delete(int $propertyId) |
Delete a property (writing call) | DELETE /rest/properties/{propertyId} |
properties()->groups(?int $page, ?int $itemsPerPage, ?string $with) |
List all property groups (paginated) | GET /rest/properties/groups |
properties()->group(int $groupId, ?string $with) |
Get one property group | GET /rest/properties/groups/{groupId} |
properties()->attachToGroup(int $groupId, int $propertyId) |
Attach a property to a group (writing call) | POST /rest/properties/groups/{groupId}/properties/{propertyId} |
properties()->attachManyToGroups(array $relations) |
Bulk-attach properties to groups (writing call) | PUT /rest/v2/properties/groups/relations |
$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'); // Create a property. `position` is accepted by the API although it is absent // from some endpoint descriptions. $property = $connector->properties()->create([ 'names' => [['lang' => 'en', 'name' => 'Range']], 'typeIdentifier' => 'item', 'cast' => 'int', 'position' => 160, ]); // Assign the new property to a group. $connector->properties()->attachToGroup(42, $property->json('id')); // Assign multiple existing properties to groups in one request. $connector->properties()->attachManyToGroups([ ['propertyId' => 123, 'groupId' => 42], ['propertyId' => 456, 'groupId' => 42], ]);
Note:
create(),attachToGroup()andattachManyToGroups()are writing calls that permanently change PlentyONE master data. Verify the payload in a dry run before applying it.
Tags
| Method | Description | API Endpoint |
|---|---|---|
tags()->list(?int $page, ?int $itemsPerPage, ?string $with) |
List all tags | GET /rest/tags |
tags()->relationships(int $tagId, string $type, ?int $page, ?int $itemsPerPage) |
List relationships of a tag (paginated) | GET /rest/v2/tags/relationships |
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 |
Tag relationships are paginated. For type: 'variation', each
entries[].value contains a variation ID. Iterate pages until isLastPage is
true and compare the collected relationship count with totalsCount.
Catalogs
| Method | Description | API Endpoint |
|---|---|---|
catalogs()->list() |
List all catalogs | GET /rest/catalogs/catalogs |
catalogs()->create(array $body) |
Create a catalog | POST /rest/catalogs/catalogs |
catalogs()->copy(array $body) |
Copy one or more catalogs | PUT /rest/catalogs/catalogs/copy |
catalogs()->copyFormat(string $catalogId, array $body) |
Copy a catalog format | PUT /rest/catalogs/catalogs/{catalogId}/copy |
catalogs()->import(array $body) |
Import a catalog | POST /rest/catalogs/catalogs/import |
catalogs()->migrate() |
Migrate catalogs from Dynamo DB to S3 | POST /rest/catalogs/catalogs/migrate |
catalogs()->activate(string $id, bool $active = true) |
Activate or deactivate a catalog | POST /rest/catalogs/catalogs/activate/{id} |
catalogs()->archive() |
List archived (deleted) catalogs | GET /rest/catalogs/catalogs/archive |
catalogs()->restore(string $id) |
Restore an archived catalog | POST /rest/catalogs/catalogs/archive/{id}/restore |
catalogs()->get(string $id) |
Get a single catalog | GET /rest/catalogs/catalogs/{id} |
catalogs()->delete(string $id) |
Delete a catalog | DELETE /rest/catalogs/catalogs/{id} |
catalogs()->update(string $id, array $body) |
Update a catalog | PUT /rest/catalogs/catalogs/{id} |
catalogs()->content(string $id) |
Get catalog content | GET /rest/catalogs/catalogs/{id}/content |
catalogs()->updateContent(string $id, array $body) |
Update catalog content | PUT /rest/catalogs/catalogs/{id}/content |
catalogs()->preview(string $id) |
Preview catalog data | GET /rest/catalogs/catalogs/{id}/preview |
catalogs()->previewVdi(string $id, int $variationId) |
Preview VDI catalog export | POST /rest/catalogs/catalogs/{id}/preview/vdi |
catalogs()->publicUrl(string $id) |
Get the public download URL | GET /rest/catalogs/catalogs/{id}/url/public |
catalogs()->privateUrl(string $id) |
Get the private download URL | GET /rest/catalogs/catalogs/{id}/url/private |
catalogs()->export(string $id) |
Get catalog export configuration | GET /rest/catalogs/catalogs/{id}/export |
catalogs()->initiateExport(string $id) |
Start catalog data generation (V1) | POST /rest/catalogs/export/{id}/initiate |
catalogs()->versions(string $id) |
List all versions of a catalog | GET /rest/catalogs/catalogs/{id}/versions |
catalogs()->version(string $id, string $versionId) |
Get a specific catalog version | GET /rest/catalogs/catalogs/{id}/versions/{versionId} |
catalogs()->templates() |
List all catalog templates | GET /rest/catalogs/templates |
catalogs()->scheduleDays() |
List available schedule days | GET /rest/catalogs/catalogs/schedule/days |
catalogs()->token() |
Generate an alphanumeric token | GET /rest/catalogs/catalogs/token |
catalogs()->checkConnection(string $protocol, array $body = []) |
Check FTP/FTPS/SFTP connection | POST /rest/catalogs/connection/check/{protocol} |
Since v0.7.0, initiateExport() starts asynchronous data generation using the
connector's existing REST authentication and an empty request body. A successful
start returns HTTP 200 with an empty body: do not call json() on it or expect
a job ID. This only acknowledges the start; it does not mean the export is ready.
The existing export() downloads the catalog definition/configuration and
does not generate export data.
$catalogId = '11111111-2222-4333-8444-555555555555'; $started = $connector->catalogs()->initiateExport($catalogId); echo $started->status(); // 200; $started->body() is empty // Separately inspect statuses and identify the new run for this catalog. $statuses = $connector->catalogStatuses()->list(catalogId: $catalogId)->json(); // Once you have selected its status ID from entries[n].status.id: $status = $connector->catalogStatuses()->get($statusId)->json(); if ($status['status']['state'] === 'complete' && $status['status']['has_data'] && 0 === $status['errors']['count']) { $downloadUrl = $status['urls']['download']['private']; // Fetch the catalog's current file separately using the existing REST bearer token. }
Match the run to the start time and catalog; an older completed status or preview is not evidence that this start finished. Check the run's errors before consuming its file, then validate the downloaded content. The download URL is catalog-wide, not bound to a status ID: before completion it may serve an older file, and another run may replace it. Verify the file's freshness and content against the intended run. Status polling, run selection and file downloads remain the caller's responsibility; the SDK does not perform them automatically.
If the start request times out, check the status list before starting it again: Plenty may already have accepted the first request.
Catalog Statuses
| Method | Description | API Endpoint |
|---|---|---|
catalogStatuses()->list(?string $catalogId, ?int $page, ?int $itemsPerPage) |
List all catalog statuses (optional filter by catalogId) | GET /rest/catalogs/statuses |
catalogStatuses()->get(int $id) |
Get a specific catalog status | GET /rest/catalogs/statuses/{id} |
catalogStatuses()->data(int $id) |
Get status data | GET /rest/catalogs/statuses/{id}/data |
catalogStatuses()->logs(int $id) |
List status logs | GET /rest/catalogs/statuses/{id}/logs |
catalogStatuses()->histories(int $id) |
List status histories | GET /rest/catalogs/statuses/{id}/histories |
catalogStatuses()->historyFile(int $id, string $filename) |
Get a single status history file | GET /rest/catalogs/statuses/{id}/histories/{filename} |
catalogStatuses()->cancel(int $statusId) |
Cancel an export run | POST /rest/catalogs/statuses/{statusId}/cancel |
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