Every failure uses the same envelope shape. Branch on error.code, not on error.message — the message may be reworded.
{
"error": {
"code": "rate_limited",
"message": "Too many requests.",
"retryable": true,
"retry_after_seconds": 5,
"request_id": "req_01h..."
}
}| Status | Code | Retryable | Meaning |
|---|---|---|---|
| 400 | invalid_request | No | Malformed input, including failed request validation. |
| 400 | unknown_source | No | Unrecognized `source` query param. |
| 401 | missing_credentials | No | No Authorization / X-API-Key header sent. |
| 401 | invalid_token | No | The key is invalid, expired, or revoked. |
| 402 | quota_exceeded | No | Monthly quota exhausted. See `error.details.resets_at`. |
| 402 | payment_required | No | Live key without an active subscription. |
| 403 | insufficient_scope | No | The key lacks the scope this endpoint requires. |
| 403 | client_suspended | No | The account is suspended. |
| 404 | not_found | No | No matching route. |
| 404 | product_not_found | No | That product id has never been seen — run a search first. |
| 429 | rate_limited | Yes | Per-minute rate limit exceeded. Honor `Retry-After`. |
| 500 | internal_error | Yes | Unhandled error on the gateway. |
| 502 | upstream_error | Yes | The source returned something unexpected. |
| 503 | upstream_unavailable | Yes | The source is unreachable right now. |
| 503 | upstream_busy | Yes | Source or gateway is at capacity. Honor `Retry-After`. |
| 504 | upstream_timeout | Yes | A live scrape didn't finish in time. Quota is refunded. |
Every request counts against a per-minute limit tied to your key (defaults and overrides are visible in GET /v1/me). Exceeding it returns 429 rate_limited with a Retry-After header. Live search and live product fetches additionally share a per-minute, per-day and concurrency limit, since each one drives a real browser session upstream.
Quota is monthly and cost-weighted — cheap stored reads and expensive live calls draw from the same pool at different rates. Failures on the gateway's side (5xx, timeouts, aborted requests) are refunded automatically; you're never billed for a request that didn't succeed.
| Call | Units |
|---|---|
| Stored read (products, categories, sources, me) | 1 |
| Live product fetch | 5 |
| Live search | 10 |
| Live search with `refresh=true` | 15 |
GET /v1/search, and a cold GET /v1/products/:id that has to scrape live, can take 20–90 seconds. Set your client timeout to at least 130 seconds for those two calls.