API Reference
Errors & rate limits
Error shape, status codes, rate limits, and which failures are worth retrying.
Errors return a non-2xx status and a single detail field with a human-readable
message. There is no machine-readable error code — branch on the HTTP status.
{
"detail": "chave de acesso inválida"
}Messages may come back in Portuguese regardless of request language — treat
detail as diagnostic text for logs, not something to show end users
as-is.
Status codes
| Status | Meaning | Retry? |
|---|---|---|
400 | Malformed request — invalid body, or (on the extraction endpoints) a file too large for the plan's context window | No — fix the request |
401 | Missing, invalid, or expired key | No |
404 | Unknown endpoint | No |
413 | Request body, file, page count, or schema over the plan's limit | No — shorten or split it |
422 | max_tokens outside the accepted range | No — fix the value |
429 | Per-key rate limit or the plan's daily token quota was hit — see Rate limits | Yes, with the Retry-After header |
502 | The model didn't return output matching the request (e.g. schema-constrained JSON) | Sometimes — see the endpoint's own error table |
503 | Infrastructure is starting, restarting, or briefly out of capacity — see Rate limits | Yes, with the Retry-After header |
/v1/documents/extract, /v1/images/extract, and /v1/documents/generate
add a few statuses beyond this table — see
Structured extraction and
PDF generation.
Retrying
Retry only 429 and 503, sleeping for the number of seconds in
Retry-After. Retrying a 4xx never succeeds — it burns a request for no
benefit.
async function withRetry(fn: () => Promise<Response>, attempts = 5) {
for (let attempt = 0; ; attempt++) {
const res = await fn();
if (res.status !== 429 && res.status !== 503) return res;
if (attempt >= attempts - 1) return res;
const wait = Number(res.headers.get("Retry-After") ?? 5);
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
}
}The first Retry-After after a wake-up can be around a minute. Give your
HTTP client a generous timeout (120s is a safe default) so it doesn't give
up before the retry loop gets a chance to work.
Rate limits
Two independent limits, neither a queue — a request either goes through
immediately or gets rejected with 429/503 for you to retry.
Requests per minute
Each API key has a token-bucket limit, refilled continuously up to a
per-minute ceiling that scales with plan: 60/min (Go), 120/min (Pro),
300/min (Max), or a custom ceiling (Enterprise). Go over it and you get
429 with Retry-After telling you how many seconds until there's room.
Daily token quota
Separate from the per-minute limit, and off by default: a cap on tokens
generated per account per day. Where it's enabled, exceeding it returns 429
with Retry-After: 3600. This catches a different failure mode than the
per-minute limit — few requests, each generating a lot of tokens (long
documents, verbose reasoning, large max_tokens) — which a request count
alone doesn't.
Capacity, not a fixed concurrency tier
Capacity is elastic per machine, not a fixed slot count per plan — a stack
running alone on its infrastructure can use nearly all of it; other stacks on
the same infrastructure share it as they show up. Two 503 cases follow from
that:
Retry-After | Meaning |
|---|---|
| ~5s | No free capacity at this instant — usually resolves on the next attempt |
| ~60s | Your plan's infrastructure was paused for inactivity and is starting back up — expected on the first request after idle time, not a failure |
Processing a large batch (thousands of items) without limiting your own
client-side concurrency is the most common way to hit this in practice —
each 503 from an overloaded batch is a lost item unless your code retries
it, not something the server queues on your behalf.
Extraction and generation endpoints have their own file size and page/resolution ceilings on top of all this, which scale with plan — see the Limits section on Structured extraction and PDF generation.

