PovitoDevelopers

Limits and errors#

Pagination#

GET /products and GET /orders are cursor-paginated and return newest first. Pass the previous response's next_cursor as cursor. Keep requesting until has_more is false. The cursor is opaque and paging is forward only.

Endpoint limit default limit max limit counts
GET /products 20 100 products, each with all its variants
GET /orders 50 200 orders

A limit that is not a positive integer is ignored and the default applies. Larger values are capped at the maximum.

Filters are applied before a page is cut, so a page is short only when it is the last one — or, rarely, when a record disappears between the page query and the read that fills it in. Stop paging on has_more, never on page size. See GET /products.

Idempotency and rate limits#

All three write endpoints — POST /products/bulk, PATCH /inventory/bulk and POST /inventory/adjustments — require an Idempotency-Key header of 1 to 255 characters; a UUID per logical request is the usual choice. A request without one is refused with 400 idempotency_key_required.

Povito stores the first outcome for 24 hours, against the key and the API key that sent it, and answers a repeat with the same body from it, with the header Idempotent-Replayed: true. Nothing runs again: no row is re-applied and no stock moves twice. Bodies are compared as JSON, so member order and spacing do not matter.

  • The same key with a different body, or on another endpoint, is refused with 409 idempotency_key_conflict. Use a new key for a new request.
  • A repeat while the first request is still running gets 409 idempotency_key_in_progress. Retry shortly.
  • A request refused before any work starts stores nothing, so you can fix it and resend it with the same key: any 400 or 429, and on POST /inventory/adjustments a 404 or 422.
  • A 500 is stored and replayed, because the failure may have come after a write. Check the current state, then resend under a new key.

(The Checkout API replays too. See Errors.)

Each bulk endpoint allows 60 requests per 600 seconds per API key, in a fixed window. Each endpoint has its own counter, so product syncs do not use up your stock-sync allowance. Going over returns 429 rate_limited with a Retry-After header, in seconds until the window resets.

  • The limit counts requests, not rows: send 500 stock rows in one call, not 500 calls.
  • The check runs after the key, scope and Idempotency-Key checks and before the body is validated. A replayed response is answered before it and does not count; a request refused for a bad body does.
  • The other endpoints have no rate limit.

Body size#

JSON request bodies are limited to 100 KB. A larger body is refused with 413 before Povito's handlers run. Split the batch.

Errors#

Errors from Povito's handlers use this envelope:

json
{
  "error": {
    "code": "validation_error",
    "message": "items[3].external_sku is required.",
    "details": [{ "field": "items[3].external_sku", "issue": "missing" }],
    "request_id": "req_m1n2b3v4c5"
  }
}
  • Branch on code. message is English, for humans, and may change.
  • details[] appears on some validation_errors only. Each entry has an issue, and usually a field. The issues this API uses are missing, invalid, too_many, empty, not_applicable, duplicate_variant and negative_result.
  • request_id is on every envelope error. It is the X-Request-Id header you sent, or a generated req_… id. Log it: Povito support needs it to trace a specific failed call.

Malformed JSON and bodies over 100 KB never reach Povito's handlers. They come back in the HTTP framework's shape, with no code and no request_id:

json
{ "message": "request entity too large", "type": "invalid_data" }
Status code When
400 validation_error A required field is missing or invalid — including since on GET /orders and cursor on GET /products — or a batch is empty or over the row cap. A bad row in a bulk request is not a 400; see below
400 idempotency_key_required A write request without Idempotency-Key
400 none (framework shape) Malformed JSON
401 invalid_api_key No Authorization: Bearer key; a malformed, unknown or revoked key; a wrong secret. Seller dashboard login tokens are refused the same way
401 api_key_wrong_environment A well-formed key from the other environment: a test key sent to Live, or a live key sent to the Sandbox. See Keys and environments
403 insufficient_scope A valid key without the scope this endpoint checks; the message names the scope
404 product_not_found GET /products/{sku}: the SKU matches none of your products
404 inventory_item_not_found POST /inventory/adjustments: a SKU or location_id is not yours
404 order_not_found No such order, or it belongs to another seller
409 idempotency_key_conflict A write endpoint: this API key already used the Idempotency-Key with a different body or on another endpoint. Send a new key
409 idempotency_key_in_progress A write endpoint: the first request with this Idempotency-Key is still running. Retry shortly
413 none (framework shape) The body is over 100 KB
422 validation_error POST /inventory/adjustments only: a delta would take stock below zero, or your account has no stock location
429 rate_limited A bulk endpoint's limit is exceeded; read Retry-After
500 internal_error An unexpected failure. Retry reads with backoff. On a write endpoint the 500 is stored against your Idempotency-Key and replayed: check the current state, then resend under a new key

Rows rejected inside a 200#

The bulk endpoints return 200 even when individual rows fail. A top-level 4xx means the whole request was refused. A row with "status": "rejected" inside results[] means only that row failed, including a row that is not an object or has no external_sku; such a row's result has "external_sku": null, and results are in request order. Its error has only code and message, never details or request_id. Row codes are validation_error, product_not_found, inventory_item_not_found, product_has_multiple_variants, catalog_only_category, catalog_product_locked and internal_error.

Treating "HTTP 200" as "everything synced" is the most common integration bug here.

How this compares with the Checkout API's errors: Errors.