Skip to main content
All errors - including unexpected server errors - return:
Some errors add context fields (details, limit, tokenBalance, resetAt, …). The code values are stable; branch on them, not on messages.
Branch on code, never on error. The error string is prose and it changes: on 402 INSUFFICIENT_TOKENS and 403 STARTER_LIMIT_REACHED for accounts on the free plan it now runs to a sentence or two and includes a URL. code and the HTTP status did not move and will not.

The upsell object

402 INSUFFICIENT_TOKENS and 403 STARTER_LIMIT_REACHED may carry an extra upsell object. Free accounts receive the founder_report offer; a paid, non-regional account with no deep credits may receive a scan_pack top-up on 402. It is additive and optional, absent on batch items, and may be absent at any time, so treat its presence as a bonus, never as a contract:
The same object also appears on GET /api/agent/analyses/:id when a starter scan owned by a free account has finished. note is written to be relayed to a human as-is. guarantee appears only on offer: "founder_report". It is omitted from scan_pack top-ups. url is tagged with the door the request came through: utm_source=agent_api for direct x-preuve-key calls like the one above, utm_source=mcp through the hosted MCP server. An error carrying upsell is a quota or billing answer, never a bad key. If you are an agent, do not send the user to the API-keys panel for one.

Common codes

Retry-After

A 429 RATE_LIMITED carries a Retry-After response header: whole seconds until the limiter’s window rolls over. Those windows are fixed one-minute buckets, so the number is exact, not an estimate - waiting that long is enough, and retrying sooner is not.
The JSON body is unchanged; keep branching on code. The quota-shaped 429s (CONCURRENT_LIMIT_REACHED, DAILY_LIMIT_REACHED, FAIR_USE_LIMIT, REGEN_LIMIT_REACHED) send no header - they clear on a different clock, and resetAt in the body is the field to use where one applies.

Retired codes

These were part of the HMAC request-signing scheme and can no longer be returned as of 2026-07-29. They are listed so a client that still branches on them knows the branch is dead, not that the docs forgot a code. An identical request sent twice now succeeds twice. If you relied on 409 NONCE_REPLAY for idempotency, use clientRunId instead - that is the real idempotency key and always was.

Partial success (207)

Enrich returns HTTP 207 with a success-shaped body when some sections or modules failed while others succeeded. Inspect failures (core sections) and the per-module modules map, then retry the failed parts - the route is idempotent and never regenerates what already completed.