Errors & rate limits
The standard response envelope, error codes to branch on, and how rate limiting works.
Every API response uses one envelope, and every error carries a stable code. Branch on the code, never the message.
The envelope
{
"data": { "...": "your result, or null on error" },
"error": { "code": "ERROR_CODE", "message": "Human-readable explanation" },
"meta": { "requestId": "req_..." }
}
On success error is null; on failure data is null. Always log meta.requestId — it’s
what support needs to trace a request. Validation failures add an error.fields map keyed by
field name.
Error codes
| Status | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED |
Missing or invalid credentials |
| 403 | FORBIDDEN |
Authenticated, but not allowed (wrong role / org) |
| 403 | INSUFFICIENT_SCOPE |
The API key lacks a required scope |
| 403 | FEATURE_DISABLED |
The feature isn’t enabled for your plan |
| 402 | INSUFFICIENT_CREDITS |
Not enough AI credits for this action |
| 404 | NOT_FOUND |
The resource doesn’t exist or isn’t yours |
| 409 | ALREADY_EXISTS |
A conflicting resource already exists |
| 422 | VALIDATION_ERROR |
Input failed validation (see error.fields) |
| 429 | RATE_LIMITED |
Too many requests — back off |
Rate limits
Rate-limited endpoints return these headers on every response so you can self-throttle:
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
Requests allowed in the current window |
X-RateLimit-Remaining |
Requests left in the window |
X-RateLimit-Reset |
Seconds until the window resets |
Retry-After |
Seconds to wait before retrying (sent on 429) |
Exceeding a limit yields a 429 with code RATE_LIMITED; honor Retry-After and retry with
exponential backoff. Per-plan limits are listed under API keys & scopes.
Pagination
List endpoints return a bounded, most-recent-first set — there’s no cursor pagination today.
Narrow with per-endpoint filters (e.g. days on analytics), or use CSV/PDF export for full
history. Full reference: errors & limits.