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
400or429, and onPOST /inventory/adjustmentsa404or422. - A
500is 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-Keychecks 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:
{
"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.messageis English, for humans, and may change. details[]appears on somevalidation_errors only. Each entry has anissue, and usually afield. The issues this API uses aremissing,invalid,too_many,empty,not_applicable,duplicate_variantandnegative_result.request_idis on every envelope error. It is theX-Request-Idheader you sent, or a generatedreq_…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:
{ "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.