Errors and rate limits.

Every code the API can return, which of them are worth retrying, and the ceilings you will hit first. Read from the handler source on 2026-08-20, not written from memory.

The envelope

Every error uses OpenAI's shape, plus a quorum block carrying the request id where one exists — so a failure is as traceable as a success.

{
  "error": {
    "message": "Rate limit exceeded for this API key.",
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "param": null
  },
  "quorum": { "request_id": null }
}

Branch on code, never on message. Codes are stable; messages are prose and may be reworded.

Every code

The complete set, read from the handlers on 2026-08-20. If you get something not on this list, that is a bug on our side — tell us.

Authentication and permission

CodeHTTPRetry?What it means
invalid_api_key401NoMissing, malformed, or not a real key. Check the Bearer prefix.
revoked_api_key401NoThe key expired or was revoked. Issue a new one.
org_suspended403NoThe organisation's access is suspended. Usually billing.
browser_origin_not_allowed403NoThe request carried a browser Origin header. See below — this one catches people out.
insufficient_scope403NoThe key exists but lacks the scope for this endpoint.

Your request

CodeHTTPRetry?What it means
invalid_model400Nomodel missing or not a string.
invalid_messages400Nomessages missing or empty.
model_not_found404NoNo mode with that id is exposed to your organisation. Call GET /v1/models.
stream_not_supported400NoYou sent stream: true. See below.
context_length_exceeded413NoPrompt exceeds the key's max_input_tokens — 8,000 unless raised.
request_in_progress409WaitA call with this Idempotency-Key is still running. Wait for it; do not fire another.
missing_request_id400NoReceipts need a request id.
receipt_not_found404NoNo receipt under your organisation for that id. Another org's id returns this, never a partial leak.
method_not_allowed405NoWrong verb for the path.

Test runs

CodeHTTPWhat it means
invalid_buckets400buckets missing or empty.
invalid_bucket400Unknown bucket name. Valid: light, medium, hard.
invalid_bucket_count400Each bucket takes an integer from 1 to 100.
too_many_questions400300 questions is the cap for a single run.
invalid_judges400Judges must be a subset of nova_pro, mistral_large, llama4_maverick.

Limits and our side

CodeHTTPRetry?What it means
rate_limit_exceeded429YesRequests-per-minute ceiling for this key. Retry-After is sent.
concurrency_limit_exceeded429YesToo many calls in flight for the organisation. Retry-After is sent.
service_unavailable503YesA dependency was unreachable and the gate failed closed rather than letting the call through unchecked.
internal_error500YesOur fault. Safe to retry with the same Idempotency-Key.

Rate limits

CeilingDefaultScope
Requests per minute60Per API key
Concurrent requests5Per organisation
Input tokens8,000Per key, per request

These are the defaults; a key can be raised. Both 429s carry Retry-After in seconds — honour it and back off exponentially rather than hammering.

Concurrency is the one that bites first in practice, because a deliberation holds its slot for tens of seconds. Five concurrent calls at a p50 of 28.5 s is roughly ten a minute sustained — well under the 60/minute rate ceiling. If you are fanning out, queue rather than parallelise.

Free endpoints are still metered

/v1/estimate bills nothing but takes the same key and the same ceilings as a billed call. Real cost per estimate is about $0.00006 — tiny, but tiny multiplied by unlimited automated volume is not tiny.

Idempotency

Send an Idempotency-Key header on /v1/chat/completions for anything a retry could duplicate. Behaviour:

If you omit the header, one is derived from your organisation, key, model and messages — so an identical repeat is deduplicated whether you asked for it or not. Vary the messages, or send your own key, when you genuinely want a second opinion on the same question.

Three that catch people out

A browser Origin header is a 403

Not a CORS misconfiguration on our side — a deliberate rule. This API is server-to-server, and a real key presented from a browser is a leaked key. If you are seeing this from a proxy or a test harness, strip the Origin header.

stream: true is a 400, not a stream

Provider token streaming is not implemented. It used to be silently ignored, which meant an SDK waiting for SSE got a plain JSON body and broke with no useful error. Failing loudly is the better wrong answer.

finish_reason: "cost_cap" is not an error

The run hit the mode's cost ceiling. You get a real answer, from a shorter deliberation than the mode would otherwise have run. Treat it as a signal worth logging, not a failure worth retrying — a retry will hit the same ceiling.