Inventory#
Two ways to change stock: set the numbers your system holds, or post a movement with a reason. Every request goes to your environment's base URL with a key that has inventory:write, as in the quickstart.
PATCH /inventory/bulk#
Set absolute stock levels and prices. Most integrations call this endpoint on a schedule. Scope inventory:write. Requires Idempotency-Key: a retry with the same key gets the first response back without writing anything again. Up to 500 rows. Rate limit: 60 requests per 600 seconds per API key, counted separately from POST /products/bulk. See idempotency and rate limits.
{
"items": [
{ "external_sku": "ERP-1042", "on_hand": 12, "price_iqd": 419000 },
{ "external_sku": "ERP-1043", "on_hand": 0 },
{ "external_sku": "ERP-2001", "price_iqd": 35000 },
{ "external_sku": "ERP-2002", "on_hand": -3 }
]
}{
"results": [
{ "external_sku": "ERP-1042", "status": "accepted" },
{ "external_sku": "ERP-1043", "status": "accepted" },
{
"external_sku": "ERP-2001",
"status": "rejected",
"error": {
"code": "product_has_multiple_variants",
"message": "This product has more than one variant — use PUT /products/{id}/variants instead."
}
},
{
"external_sku": "ERP-2002",
"status": "rejected",
"error": { "code": "validation_error", "message": "on_hand must be a whole number of units, 0 or more." }
}
]
}- Each row needs
external_skuand at least one ofon_handorprice_iqd;nullcounts as not sent. A row without them isrejectedwithvalidation_erroron its own, and the rest of the request still runs. A row with noexternal_skucomes back with"external_sku": null; results are in request order. on_handandprice_iqdare absolute values, not deltas. Sending the same body twice leaves the same end state. Neither field writes an adjustment audit entry.on_handmust be a whole number, 0 or more, sent as a JSON number. A negative, fractional or non-numeric value (such as"12") rejects the row withvalidation_error, and nothing in that row is written, not even itsprice_iqd. It sets stock at your primary stock location only.price_iqdsets your list price in whole dinars. It works only on a single-variant product. On a multi-variant product the row is rejected withproduct_has_multiple_variants. The message names a Seller dashboard route; the Partner API cannot set a price per variant. While a Povito markdown campaign runs, the discounted price is recomputed from the new list price.- Send numbers. A
price_iqdof the wrong JSON type, such as"35000", is dropped, and a row whose only field it was reportsacceptedwithout changing anything. - Within a row, stock is written before price. If the price write fails, the row is
rejectedeven though the stock change was already applied. Re-sending the row under a newIdempotency-Keyis safe because both values are absolute; the same key would only replay this response. - The response is
200even when some rows are rejected, so always readresults[]. A rejected row carries one ofinventory_item_not_foundorproduct_not_found(the SKU is not yours),product_has_multiple_variants,validation_errororinternal_error.
POST /inventory/adjustments#
Post a stock movement with a reason, for anything that changed outside Povito: a shop-floor sale, breakage, a restock. Scope inventory:write. Up to 100 items. Requires Idempotency-Key: a retry with the same key is answered from the first attempt instead of moving stock again. No rate limit.
{
"reason": "Weekly stocktake, Karrada branch",
"category": "damaged",
"items": [
{ "external_sku": "ERP-1042", "delta": -2 },
{ "external_sku": "ERP-1043", "delta": 5, "location_id": "sloc_01K2F4H6K8M0P2R4T6W8Y0A2C4" }
]
}{
"reason": "Weekly stocktake, Karrada branch",
"category": "damaged",
"data": [
{ "variant_id": "variant_01K4R8Z2S8T4W6Y8A0C2E4G6J8", "before": 12, "after": 10, "delta": -2 },
{ "variant_id": "variant_01K4P1C8F0H2K4M6P8R0T2V4X6", "before": 0, "after": 5, "delta": 5 }
]
}Retry with the same key
Send a new Idempotency-Key for each movement, and reuse it only to retry that movement. Povito keeps the first outcome for 24 hours, per API key. A retry with the same key and body gets that response back, with the header Idempotent-Replayed: true, and moves no stock. The body is compared as JSON, so member order and spacing do not matter.
The same key with a different body is refused with
409 idempotency_key_conflict. Use a new key for a new movement.A retry while the first attempt is still running gets
409 idempotency_key_in_progress. Retry shortly. If it persists, check the stock withGET /products/{sku}and send the movement again under a new key only if it did not apply.A
400,404or422stores nothing, so you can fix the body and resend it with the same key.A
500is stored and replayed, because the failure may have come after stock moved. Check the stock withGET /products/{sku}, and send the movement again under a new key only if it did not apply.A key longer than 255 characters is refused with
400 validation_error.reasonis required and is stored on every audit entry.categoryis one ofexternal_sale,damaged,restock,other.deltais a signed, non-zero integer. Include each SKU at most once per request.beforeandafterare whole units, andafteris alwaysbefore + delta. If a stock level holds a fraction, which is possible only through writes outside Povito's seller and partner APIs, it is rounded to the nearest unit, and the adjustment writes the whole-numberafter.location_idis optional. It must be one of your stock locations and defaults to your primary location.unit_valueis accepted only withexternal_sale. It is what one unit actually sold for, in IQD, and must be greater than 0. Anexternal_salewith a negative delta and aunit_valueposts the outside-sale revenue to your books. It also posts cost of goods where a unit cost is known. Adamagedmovement with a negative delta posts a cost write-off. This bookkeeping is best-effort and never fails the adjustment.All or nothing, not per row. If any item fails one of these checks, the whole request is refused and nothing is written:
- an unknown SKU or
location_id(404 inventory_item_not_found); - a SKU repeated in the request (
400,details[].issue: duplicate_variant); - a delta that would take stock below zero (
422,details[].issue: negative_result); - a seller account with no stock location (
422).
This is the only Partner API endpoint that returns
422.- an unknown SKU or
Unlike
/inventory/bulk, this writes an audit-trail entry. Use it when the reason matters. Use/inventory/bulkwhen you are simply syncing your system's current numbers.