Errors
Every status code the gateway returns, what actually causes it, and the fix.
Errors return JSON with a type and a human-readable message. Status codes follow the Anthropic and OpenAI conventions.
{"type": "error", "error": {"type": "authentication_error", "message": "invalid api key"}}
Status codes
| Code | Meaning | Most common real cause | Fix |
|---|---|---|---|
400 | Bad request | Missing max_tokens on Messages, or a malformed messages array | Check the body against Messages |
401 | Unauthenticated | Key sent in the wrong header for the format | x-api-key for Messages, Bearer for Chat Completions |
402 | Payment required | Balance exhausted or plan expired | Top up or buy a plan |
403 | Forbidden | Key revoked, or model not enabled for the account | Create a new key; check /v1/models |
404 | Not found | Base-URL mistake — usually a doubled or missing /v1 | See Base URLs |
429 | Rate limited | Too many concurrent requests | Back off exponentially; see Rate limits |
500 | Gateway error | Unexpected internal failure | Retry once; if it persists, contact support with the request id |
529 | Upstream overloaded | Provider capacity, not your account | Retry with backoff — usually clears in seconds |
Error types
| type | Meaning |
|---|---|
invalid_request_error | The body is malformed or a required field is missing |
authentication_error | Key missing, malformed, or revoked |
permission_error | Key is valid but not allowed to use that model |
not_found_error | Unknown path or unknown model id |
rate_limit_error | Throttled |
api_error | Internal failure |
overloaded_error | Upstream capacity exhausted |
Retrying correctly
Retry 429, 500, 502, 503 and 529. Never blind-retry 400, 401, 402 or 403 — the same request fails identically, and for partially generated responses you may pay twice.
import time, httpx
def call(body, key, tries=4):
for i in range(tries):
r = httpx.post(class="s">"https://aiprimetech.io/v1/messages", json=body, timeout=120,
headers={class="s">"x-api-key": key,
class="s">"anthropic-version": class="s">"2023-06-01"})
if r.status_code in (429, 500, 502, 503, 529):
time.sleep(2 ** i) class=class="s">"c"># 1s, 2s, 4s, 8s
continue
r.raise_for_status()
return r.json()
raise RuntimeError(class="s">"upstream unavailable after retries")
Add jitter to the sleep in production. Synchronised retries from many workers turn one blip into a thundering herd against the same upstream.