Skip to content

Errors

The error body shapes of the native, OpenAI and Anthropic routes, every status and code, and what to do about each one.

Errors are ordinary HTTP statuses with a JSON body. Branch on the status, then on the code when there is one, and show the human message to people but never parse it: messages are written for readers and may be reworded. Which body shape you get depends on the route family, so this page starts there.

Error body shapes

RoutesShapeExample
Native /v1/*, /developer/*, /light/*, /engine/*{ "error": string, "code"?: string, ...details }{ "error": "This API key does not have the \"knowledge\" scope.", "code": "scope_denied" }
OpenAI dialect: /v1/chat/completions, /v1/proofs/*{ "error": { "message", "type", "code", "param" } }{ "error": { "message": "Insufficient balance", "type": "insufficient_quota", "code": "insufficient_quota", "param": null } }
Anthropic dialect: /v1/messages{ "type": "error", "error": { "type", "message" } }{ "type": "error", "error": { "type": "rate_limit_error", "message": "..." } }
A parser that handles all three shapes
type ApiError = { status: number; code?: string; message: string; retryAfter?: number }
async function parseError(res: Response): Promise<ApiError> {
const body: any = await res.json().catch(() => ({}))
const err = body?.error
const message = typeof err === 'string' ? err : err?.message ?? res.statusText
const code = body?.code ?? (typeof err === 'object' ? err?.code ?? err?.type : undefined)
const retry = Number(res.headers.get('retry-after'))
return { status: res.status, code, message, retryAfter: retry > 0 ? retry : undefined }
}

Catalogue

400 and 413: the request is wrong

StatusCodeMeaningWhat to do
400invalid_request_error (OpenAI and Anthropic)messages missing or empty, or no user message.Fix the payload. Retrying unchanged will fail again.
400noneA native route rejected the body: a required field is missing, a value has the wrong type, scopes is not a list of known scopes, a limit is not a positive whole number, Collection not found, file is required (multipart/form-data).Read error; it names the field.
400text_requiredPOST /v1/verify was sent an empty text.Send the text to check.
400invalid_passagespassages on /v1/verify is malformed (each needs source and content).See Le Vérificateur.
400ambiguous_scope/v1/verify received both passages and collectionIds.Send one or the other.
413text_too_largeThe text sent to /v1/verify exceeds the maximum.Split the text and verify the parts.
413noneAn uploaded file is over 25 MB.Split or compress the file.

401 and 403: who you are, what you may do

StatusCodeMeaningWhat to do
401noneAPI key required..., Invalid or revoked API key, API key expired, or User not found.Do not retry. Check the key, create a new one if needed. See Authentication.
403scope_deniedThe key does not carry the scope the route needs, or the route has no scope.Add the scope. The message names it.
403read_only_keyA read-only key tried to change something.Use a key without readonly for writes.
403client_suspendedThe workspace is suspended by the integrator that manages it.Contact that integrator. Keys keep working once access is restored.
403provider_unpaidThe integrator behind this client workspace has an overdue invoice.Contact your provider.

402: a limit on what you have bought

A 402 never means "retry". It means a quota, a plan or a ceiling must change first. Nothing is consumed when a call is refused with a 402. See Rate limits and quotas for what each limit measures.

StatusCodeExtra fieldsMeaningWhat to do
402insufficient_quotanoneOpenAI dialect only: the workspace has no questions left for this call.Add a question pack or move to a larger plan.
402QUESTION_LIMITrequired, availableNative routes: fewer questions left than the request needs. required and available are counted in questions.Add a pack or upgrade.
402SUBSCRIPTION_INACTIVEnoneThe trial is over or the subscription is no longer active.Choose a plan in the console.
402PLAN_UPGRADE_REQUIREDfeature, requiredPlanThe route belongs to a plan feature your plan does not include (for example the Registre needs the proof journal).Upgrade to requiredPlan.
402DOCUMENT_LIMITlimit, usedThe workspace reached its document volume.Delete documents, add users, or upgrade.
402SEAT_LIMITseatsThe subscription covers fewer users than you tried to add.Add a seat to the subscription.
402API_KEY_BUDGET_EXCEEDEDnoneThis key reached its own monthly question ceiling, set below the workspace allowance.Raise the key ceiling with PATCH /developer/api-keys/:id, or wait for the next month.

404, 429, 5xx

StatusCodeMeaningWhat to do
404noneThe object does not exist in this workspace. Another workspace's object looks identical.Check the id and the key's workspace.
404model_not_found (OpenAI), not_found_error (Anthropic)The agent:<slug> address matches no agent.List addresses with GET /v1/models.
404conversation_not_foundx-pulse-conversation names an unknown conversation.Omit the header to start a new one.
404proof_not_foundUnknown certificate id, or one from another workspace.Use the id exactly as returned in x-pulse-proof.
429rate_limitedThe key exceeded its requests per minute. Carries retry-after.Wait that many seconds, then retry.
429rate_limit_error (Anthropic)On /v1/messages a spent question allowance is a 429, not a 402: the Anthropic dialect has no payment status.Treat as a quota error, not a transient one: retrying will not help until questions are added.
429noneA route-level limit (for example 60 per minute on the Gateway and Cortex query routes, 30 on file upload). Produced by the rate limiter, so its body is not one of the shapes above.Branch on the status and retry-after, not on the body.
500server_error (OpenAI), api_error (Anthropic)The call failed inside Subsidia or at the model provider.Retry with backoff, at most three times. Persisting? Report the x-pulse-proof id if one was returned.
501address_not_supported (OpenAI), invalid_request_error (Anthropic)A reserved debate: address was used on the Gateway.Use the conversations API for debates.

Retrying safely

StatusRetry?How
400, 401, 403, 404, 413, 501NoThe request or the key must change first.
402NoNot until a quota, plan or ceiling changes. Surface it to a human.
429 with retry-afterYesSleep for retry-after seconds (at most 60: the window is one minute), then resend.
429 on /v1/messages, no retry-afterNoProbably a spent allowance. Check Rate limits and quotas.
500, network errorYesExponential backoff with jitter (1 s, 2 s, 4 s), three attempts, then give up.
Retry loop
async function call(url: string, init: RequestInit, attempts = 3): Promise<Response> {
for (let i = 0; i < attempts; i++) {
const res = await fetch(url, init)
if (res.ok) return res
if (res.status === 429 && res.headers.get('retry-after')) {
await new Promise((r) => setTimeout(r, Number(res.headers.get('retry-after')) * 1000))
continue
}
if (res.status >= 500 && i < attempts - 1) {
await new Promise((r) => setTimeout(r, 2 ** i * 1000 + Math.random() * 250))
continue
}
return res // 4xx: the caller decides
}
throw new Error('unreachable')
}

OpenAI and Anthropic dialects side by side

Situation`/v1/chat/completions``/v1/messages`
Empty or missing messages400 invalid_request_error400 invalid_request_error
Question allowance spent402 insufficient_quota429 rate_limit_error
Unknown agent: address404 model_not_found404 not_found_error
Reserved debate: address501 address_not_supported501 invalid_request_error
Internal failure500 server_error500 api_error
Bad or missing key401, native shape401, native shape
Does a failed call cost me a question?

A refusal before the model runs (400, 401, 402, 403, 404) consumes nothing. For a 500, report it with the proof id if one came back.

How do I tell a transient 429 from a spent allowance?

A transient 429 carries a retry-after header and, from a key limit, code: "rate_limited". A spent allowance on /v1/messages has error.type: "rate_limit_error" and no retry-after.

Related