Errors, retries and rate limits
The error envelopes of both APIs side by side, which errors to retry, idempotency, rate limits and their headers.
The Channel API and the Store API refuse requests in different shapes, and the gateway in front of both has a third. This page puts them side by side and says which refusals a client retries.
Three error shapes
| Source | Shape | Codes |
|---|---|---|
| Gateway, before any API | { "error": "<CODE>", "message": "..." } | Upper case: INVALID_REQUEST, INVALID_CREDENTIALS, TOKEN_INVALID, TOKEN_EXPIRED, TENANT_INACTIVE |
| Channel API | { "error": { "code", "message", "details", "requestId" } } | Lower case: validation_failed, forbidden, state_conflict, ... |
| Store API | { "code", "message", "fieldErrors" } | Upper case: VALIDATION, CONFLICT, APPROVAL_REQUIRED, ... |
A Channel API refusal:
{
"error": {
"code": "validation_failed",
"message": "Request validation failed",
"details": [
{ "path": "items[0].quantity", "code": "minimum", "message": "must be >= 1" }
],
"requestId": "2f408915-2608-4f0e-90a7-2a7abd34f487"
}
}
A Store API refusal:
{
"code": "CONFLICT",
"message": "Your cart changed. Review the current cart.",
"fieldErrors": {}
}
- Channel API
detailsnames each field at fault as{ path, code, message };requestIdequals theX-Request-Idresponse header. - Store API
messageis written for the buyer and can be shown as is;fieldErrorsis keyed by field on sign-up and password change. - Branch on the code, never on the message text.
Codes side by side
| Situation | Channel API | Store API |
|---|---|---|
| A field is missing, malformed or unknown | 400 validation_failed | 400 VALIDATION |
| No usable identity | 401 unauthenticated | 401 UNAUTHORIZED |
| The key lacks the scope, or the tenant does not match | 403 forbidden | 403 FORBIDDEN |
| Nothing at that id | 404 not_found | 404 NOT_FOUND, 404 PRODUCT_NOT_FOUND |
A stale version or revision | 409 version_conflict | 409 CONFLICT |
| The document already exists | 409 duplicate (with onDuplicate: error) | 409 CONFLICT (a duplicate account on sign-up) |
| The state forbids the action | 409 state_conflict, details[0].code names the case | 409 CHECKOUT_PENDING, PAYMENT_PENDING, QUOTE_INCOMPLETE, QUOTE_PENDING, CART_CHOICE_REQUIRED |
| A business rule refuses it | 422 totals_mismatch, inventory_unavailable, unresolved_reference, inactive_customer | 403 APPROVAL_REQUIRED, CONFIRMATION_REQUIRED, CHECKOUT_DISABLED; 409 PRODUCT_VARIANT_REQUIRED |
| An idempotency key reused with another body | 422 idempotency_key_reused | 409 CONFLICT (a checkout key reused for another quote) |
| Too many requests | 429 rate_limited | 429 RATE_LIMITED |
| A dependency or the endpoint is briefly unavailable | 503 dependency_unavailable, 503 route_disabled | 503 UNAVAILABLE |
| Unexpected failure | 500 internal |
Full lists: Channel API errors and Store API errors.
What to retry
| Answer | Retry | How |
|---|---|---|
429 | Yes | Wait the seconds in Retry-After. |
503 | Yes | Exponential backoff with jitter. On the Channel API keep the same Idempotency-Key. A Store API write that answered 503 was not applied unless a later read shows it. |
409 in_progress (Channel API) | Yes | After Retry-After, with the same key. |
500 internal (Channel API) | Once or twice | With the same Idempotency-Key: a failed request is not stored, so the retry runs again. |
| A timeout or network error on a write | Yes | With the same Idempotency-Key (Channel API) or idempotencyKey (Store API). If the first attempt landed, its answer replays. |
401 TOKEN_EXPIRED, or a Store API 401 on an expired buyer token | After a refresh | Refresh the token, then retry once. |
409 version_conflict, 409 CONFLICT | After a re-read | Read the document or the cart again, re-apply the change. |
409 QUOTE_PENDING | Yes | Repeat cart.get { "quote": true }. |
409 CHECKOUT_PENDING, 409 PAYMENT_PENDING | Poll | checkout.status or payments.status; never start a second checkout. |
Other 400, 403, 404, 422 | No | Fix the request: the same request gets the same answer. |
Idempotency
Channel API. Every POST, PUT, PATCH and DELETE takes an Idempotency-Key header: 7 to 50 characters that name the request on your side (an order number, a webhook delivery id).
| Retry | Answer |
|---|---|
| Same key, same body | The stored first answer, with Idempotent-Replayed: true. Answers are kept 24 hours. |
| Same key, different body | 422 idempotency_key_reused. |
| Same key while the first request runs | 409 in_progress with Retry-After. |
Keys are scoped per tenant and per endpoint, and a failed request is not stored. Beyond the 24 hours, onDuplicate: update (the default on creates) makes a replayed document an update by externalId, so a full feed can be sent again safely.
Store API. Checkout, cart.addMany, cart claims, invoice payments, saved payment methods, subscriptions and applications take idempotencyKey (or requestId) in the body: 7 to 50 characters from A-Z a-z 0-9 _ -. A retry with the same key answers the first attempt. A lost checkout.start answer is found again with checkout.status { idempotencyKey, quoteFingerprint }.
Kentro Pixel. Events are deduplicated on event_id per property for at least 14 days; server requests may add X-Kentro-Idempotency-Key (up to 128 characters). See Pixel server API.
Rate limits
Channel API
- Per tenant and per minute: 1,200 writes (
POST,PUT,PATCH,DELETE) and 6,000 reads (GET) by default. Kentro sets higher limits per tenant. - Every response carries
X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Reset(seconds until the window resets). A refusal is429 rate_limitedwithRetry-After. POST /v1/batchcounts every operation it carries.- Bulk sizes:
POST /v1/orders/importandPOST /v1/products/importtake up to 1,000 documents (answered202, thenGET /v1/operations/:id);POST /v1/batchtakes up to 100 operations;POST /v1/fulfillment-queue/shipup to 100 entries;POST /v1/inventory/historyup to 500 entries.
Store API
catalog.suggest(the search typeahead): 30 requests per 3 seconds per buyer, per guest, or perX-Storefront-Client-Ipfor signed-out visitors.application.start: 60 per minute; otherapplication.*operations: 120 per minute.session.signupandaccount.confirm, buyer sign-in and password reset are limited per account and per source address. SendX-Storefront-Client-Ipso the limits key on the buyer, not on the storefront server.- The gateway applies per-key limits as well. Bodies are limited to 64 KB, application uploads excepted.
Kentro Pixel
- 600 events per minute per public key (bursts up to 1,200) and 6,000 per server key (bursts up to 12,000); 100 events per browser request, 500 per server request. See Limits, errors and troubleshooting.
Staying under the limits
- Read
X-RateLimit-Remainingand slow down before it reaches zero. - Load history through the import endpoints, not one write at a time.
- Poll with
updatedSinceand a sensible interval instead of re-reading whole lists. - Queue writes behind a single worker per tenant, so a burst from the source system becomes a steady stream.
Updated about 12 hours ago

