PovitoDevelopers

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.

json
{
  "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 }
  ]
}
json
{
  "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_sku and at least one of on_hand or price_iqd; null counts as not sent. A row without them is rejected with validation_error on its own, and the rest of the request still runs. A row with no external_sku comes back with "external_sku": null; results are in request order.
  • on_hand and price_iqd are absolute values, not deltas. Sending the same body twice leaves the same end state. Neither field writes an adjustment audit entry.
  • on_hand must 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 with validation_error, and nothing in that row is written, not even its price_iqd. It sets stock at your primary stock location only.
  • price_iqd sets your list price in whole dinars. It works only on a single-variant product. On a multi-variant product the row is rejected with product_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_iqd of the wrong JSON type, such as "35000", is dropped, and a row whose only field it was reports accepted without changing anything.
  • Within a row, stock is written before price. If the price write fails, the row is rejected even though the stock change was already applied. Re-sending the row under a new Idempotency-Key is safe because both values are absolute; the same key would only replay this response.
  • The response is 200 even when some rows are rejected, so always read results[]. A rejected row carries one of inventory_item_not_found or product_not_found (the SKU is not yours), product_has_multiple_variants, validation_error or internal_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.

json
{
  "reason": "Weekly stocktake, Karrada branch",
  "category": "damaged",
  "items": [
    { "external_sku": "ERP-1042", "delta": -2 },
    { "external_sku": "ERP-1043", "delta": 5, "location_id": "sloc_01K2F4H6K8M0P2R4T6W8Y0A2C4" }
  ]
}
json
{
  "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 with GET /products/{sku} and send the movement again under a new key only if it did not apply.

  • A 400, 404 or 422 stores nothing, so you can fix the body and resend it with the same key.

  • A 500 is stored and replayed, because the failure may have come after stock moved. Check the stock with GET /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.

  • reason is required and is stored on every audit entry.

  • category is one of external_sale, damaged, restock, other.

  • delta is a signed, non-zero integer. Include each SKU at most once per request.

  • before and after are whole units, and after is always before + 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-number after.

  • location_id is optional. It must be one of your stock locations and defaults to your primary location.

  • unit_value is accepted only with external_sale. It is what one unit actually sold for, in IQD, and must be greater than 0. An external_sale with a negative delta and a unit_value posts the outside-sale revenue to your books. It also posts cost of goods where a unit cost is known. A damaged movement 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.

  • Unlike /inventory/bulk, this writes an audit-trail entry. Use it when the reason matters. Use /inventory/bulk when you are simply syncing your system's current numbers.