Rate limits & errors
Rate limits
Section titled “Rate limits”Every /api/v1/vendor route is rate-limited using a sliding window (4 sub-segments — a burst right
at a window boundary is smoothed, not doubled), partitioned per authenticated caller (your
credential’s client id), falling back to caller IP only on the anonymous token endpoint itself.
| Route | Limit | Window |
|---|---|---|
POST /api/v1/vendor/auth/token |
10 requests | 60 seconds |
Every other /api/v1/vendor/** route |
120 requests | 60 seconds |
HTTP/1.1 429 Too Many RequestsRetry-After: 12Content-Type: application/json
{ "state": "RATE_LIMITED", "payload": null, "details": { "message": "Too many requests. Please try again later." } }Respect the Retry-After header (seconds) rather than retrying immediately or on a fixed-interval
guess. If you’re hitting the token endpoint’s 10/min limit, you almost certainly have a bug where
you’re fetching a fresh token on every request instead of caching it (see
Authentication & capabilities). The vendor data-route budget (120/min)
is intentionally more conservative than the partner API’s own 300/min — vendor integration traffic
is typically lower-volume (offer/stock/cost pushes, order polling) than partner order/cart traffic.
The envelope
Section titled “The envelope”Every response — success or failure — is wrapped the same way:
// success{ "state": "SUCCESS", "payload": { "...": "..." } }
// created (201 responses){ "state": "CREATED", "payload": { "...": "..." } }
// error{ "state": "SOME_ERROR_CODE", "payload": null, "details": { "...": "optional context" } }state is always present. On an error, it carries a stable, machine-readable code — parse and
branch on state, not on the HTTP status code alone.
HTTP status ↔ error shape
Section titled “HTTP status ↔ error shape”| HTTP status | Meaning |
|---|---|
| 200 / 201 | Success. |
| 400 | Request validation failed (a malformed/missing/out-of-range field). |
| 401 | Missing, invalid, or expired credentials/token. |
| 403 | Authenticated, but your token doesn’t carry the capability this endpoint requires. |
| 404 | The resource doesn’t exist — or belongs to another vendor. Cross-vendor access always reports 404, never 403, so a not-yours resource is indistinguishable from a genuinely nonexistent one. |
| 422 | The request was well-formed but violates a business rule (e.g. the webhook subscription cap). |
| 429 | Rate-limited. |
| 5xx | Something went wrong on our side — safe to retry with backoff. |
Common error codes
Section titled “Common error codes”| Code | HTTP | When |
|---|---|---|
UNAUTHORIZED_INVALID_CREDENTIALS |
401 | Wrong clientId/clientSecret at the token endpoint. |
UNAUTHORIZED_VENDOR_INACTIVE |
401 | Your vendor account is inactive. |
FORBIDDEN_CAPABILITY_NOT_GRANTED |
403 | Your token doesn’t carry the capability this endpoint requires — see details.requiredCapability. |
NOT_FOUND_VENDOR_OFFER |
404 | Unknown offer id, or one belonging to another vendor. |
NOT_FOUND_FULFILLMENT_GROUP |
404 | Unknown fulfilment group id, or one belonging to another vendor. |
NOT_FOUND_VENDOR_WEBHOOK_SUBSCRIPTION |
404 | Unknown subscription id. |
VALIDATION_WEBHOOK_EVENT_TYPE_NOT_SUBSCRIBED |
400 | Test-firing an event type your subscription isn’t subscribed to. |
BUSINESS_VENDOR_WEBHOOK_SUBSCRIPTION_LIMIT |
422 | You already have 5 active subscriptions. |
RATE_LIMITED |
429 | See above. |
What error responses never do
Section titled “What error responses never do”- Never distinguish “wrong client ID” from “wrong secret” at the token endpoint — both are the
same generic
UNAUTHORIZED_INVALID_CREDENTIALS, to avoid letting a caller enumerate valid client IDs. - Never leak a stack trace, an internal exception message, or an implementation detail in
details. - Never return a 200 with an error state embedded in the payload — if
stateisn’tSUCCESS/CREATED, the HTTP status code is always a matching 4xx/5xx too.