API reference · Errors

Predictable failures.

The API returns standard HTTP status codes with a JSON error body shaped for the protocol you called — Anthropic-shaped on /v1/messages, OpenAI-shaped on the OpenAI endpoints. Match on the status; read error.message for the human explanation.

Status codes

StatusTypeMeaning
400invalid_request_errorMalformed JSON or bad parameters — fix the request; retrying the same body will fail the same way.
401authentication_errorMissing or invalid key. Check the header for the protocol you're calling.
403permission_errorThe key is valid but not allowed to do this.
404not_foundUnknown route or unknown model id — check the path and the catalog (GET /v1/models).
413invalid_request_errorRequest body too large. Trim the payload or split the work.
422invalid_request_errorThe request declares a requirement the chosen model cannot guarantee (e.g. a capability the model doesn't have). Switch models or drop the requirement.
429rate_limit_errorTransient rate limit (requests/min, tokens/min or concurrency). Carries retry-after (seconds) — back off and retry.
429allowance_exhaustedMonthly included allowance used up — retrying won't help until the month rolls over. The message names your plan and the next tier; retry-after is the seconds to reset. Upgrade to lift it immediately.
5xxapi_errorThe model did not return a response. Please retry — transient by design; a retry with backoff almost always succeeds.

Error body shape

Every error returns a JSON body with an error object carrying a type and a message. On the Anthropic endpoint it is Anthropic-shaped:

{
  "type": "error",
  "error": {
    "type": "authentication_error",
    "message": "Invalid API key."
  }
}
Anthropic-shaped (/v1/messages)

On the OpenAI endpoints it is OpenAI-shaped:

{
  "error": {
    "type": "invalid_request_error",
    "message": "Invalid API key."
  }
}
OpenAI-shaped (/v1/chat/completions, /v1/responses, ...)

Retries and backoff

The retry recipe every production agent should ship:

  • Retry only what can succeed 429 with type rate_limit_error and 5xx. A 429 with type allowance_exhausted won't clear until the month resets (or you upgrade), and a 4xx will fail identically on every attempt; fix the request instead.
  • Honor retry-after exactly — on 429 it is the number of seconds until capacity opens. Waiting less just burns attempts.
  • Exponential backoff with jitter everywhere else — e.g. 1s, 2s, 4s, 8s with ±20% jitter, capped around five attempts.
  • Fail loud after the cap — surface the final error.message; it is written to be actionable.
import random, time

def with_retries(send, max_attempts=5):
    for attempt in range(max_attempts):
        r = send()
        if r.status_code < 400:
            return r
        if r.status_code == 429:
            time.sleep(int(r.headers.get("retry-after", "1")))
        elif r.status_code >= 500:
            time.sleep((2 ** attempt) * (1 + random.uniform(-0.2, 0.2)))
        else:
            break  # other 4xx: not retryable
    raise RuntimeError(r.json()["error"]["message"])
Minimal backoff loop
Note

The official OpenAI and Anthropic SDKs already do all of this — including honoring retry-after — with no configuration. Hand-rolled HTTP clients are where retries usually go missing.

Streaming failures

If a stream cannot complete, it ends with a protocol-shaped error event rather than silently returning an empty response — your client always learns the turn failed. Treat a mid-stream error like a 5xx: retry the whole request with backoff. See Streaming.