Developers
Errors and rate limits
Every error uses the same JSON envelope, so one handler covers the whole API. Quote the requestId when you contact support.
The error envelope
{
"error": {
"code": "insufficient_scope",
"message": "This API key needs the \"products:write\" scope for this request.",
"requestId": "req_…",
"details": { "requiredScope": "products:write" }
}
}codeis stable and machine-readable. Branch on it, not onmessage.messageis written for people and may change.detailsis optional structured context, such as validation issues or quota numbers.
Status codes
| Status | Meaning | What to do |
|---|---|---|
| 400 | Malformed request | Fix the request. Don’t retry unchanged. |
| 401 | Missing, invalid, expired or revoked API key | Check the key and environment. |
| 403 | The key lacks the scope, or the endpoint isn’t available to API keys | Add the scope to a new key, or use the portal. |
| 404 | Not found, or not in your company | Check the id. Other companies’ records always return 404. |
| 422 | Validation failed (details list the fields) | Fix the fields named in details. |
| 429 | Rate limited | Wait for Retry-After seconds, then continue. |
| 503 | Temporarily overloaded (code overloaded) | Retry once after Retry-After plus jitter, only for safe requests. |
Rate limits
Each API key has its own per-minute limit, set by your company’s plan. Every authenticated response includes X-RateLimit-Limit and X-RateLimit-Remaining.
| Plan | Requests per minute, per key |
|---|---|
| Starter | 60 |
| Growth | 600 |
| Enterprise | 6,000 |
When you go over the limit the API returns 429 rate_limited with a Retry-After header in seconds. Wait that long before sending more requests.
503 overloaded
If the API is briefly overloaded it sheds load with 503, error code overloaded and a short Retry-After. Nothing was processed.
- Retry once, after
Retry-Afterseconds plus a small random jitter (for example up to 1 second), so many clients don’t retry at the same moment. - If the retry also fails, stop and surface a calm “busy, try again shortly” message.
Never automatically retry writes
Only automatically retry reads (GET) and POST /v1/verify, which is a status check and safe to repeat. Don’t automatically retry other POST, PATCH or DELETE requests: a timeout doesn’t tell you whether the change happened. For code generation, send an idempotencyKey so a deliberate retry is safe.
async function withOverloadRetry(send: () => Promise<Response>) {
const first = await send();
if (first.status !== 503) return first;
const retryAfter = Number(first.headers.get('retry-after') ?? '1');
const delayMs = Math.max(1, retryAfter) * 1000 + Math.random() * 1000;
await new Promise((resolve) => setTimeout(resolve, delayMs));
return send(); // one retry only
}The TypeScript SDK does this for you: it retries safe requests once on 503 and exposes retryAfterSeconds on CredCheckError.