Claude API Error Codes: Full Reference List
When a Claude API call fails, you get an HTTP status code plus a JSON body with an error.type field that tells you exactly what went wrong. This reference lists every error code you'll encounter, what triggers it, and how to handle it in production code.
Every error response from the Claude API follows the same shape:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "max_tokens: Field required"
}
}
The HTTP status code tells you the category of failure; error.type tells you the specific reason. Below is the full list, grouped by status code.
400 — invalid_request_error
This is the most common error during development. It means the request body is malformed, missing a required field, or contains a value that fails validation.
Typical causes:
- Missing
model,max_tokens, ormessages messagesarray starts with arole: "assistant"instead ofrole: "user"- Unsupported combination of parameters (e.g.
temperatureandtop_pboth set to unusual values withtool_choiceconflicts) - Malformed tool schema in a tool-use request
- Image payload that isn't valid base64 or exceeds size limits
Fix: read error.message carefully — it almost always names the exact field. Validate request bodies client-side before sending, especially if you're building the payload dynamically.
401 — authentication_error
The API key is missing, malformed, or revoked. Check that:
- The
Authorizationheader (orx-api-key, depending on provider) is actually being sent - The key hasn't been rotated or deleted in the dashboard
- You're not accidentally sending a test/sandbox key against a production endpoint
If you're using SubToAPI, this error fires when your sub_live_... key is invalid or disabled. Regenerate the key from the dashboard and confirm the Authorization: Bearer $SUBTOAPI_KEY header is set exactly as shown in the quickstart.
403 — permission_error
The key is valid but doesn't have permission for the requested resource. This shows up when:
- A team member's role restricts access to a given model or endpoint
- The account has been suspended for billing reasons
- You're calling a beta feature that isn't enabled on the account
404 — not_found_error
The requested resource doesn't exist — usually a wrong model name, a typo in the endpoint path, or referencing a message/batch ID that was never created (or has expired). Double-check the model string against current documentation; model names change with new releases and old ones get deprecated.
413 — request_too_large
The request body exceeds the maximum allowed size. This is distinct from context-window errors — it's about raw payload size, often triggered by large base64-encoded images or PDFs attached inline. Compress images, reduce resolution, or split large documents before sending.
422 — invalid_request_error (semantic validation)
Some APIs return 422 instead of 400 for requests that are syntactically valid JSON but semantically wrong — for example, a tool_choice that references a tool name not present in the tools array, or a stop_sequences array with invalid entries. Treat it the same way as a 400: read the message and fix the payload.
429 — rate_limit_error
You've exceeded requests-per-minute, tokens-per-minute, or concurrent-request limits. The response usually includes a Retry-After header. The correct handling pattern is exponential backoff with jitter, not a tight retry loop — hammering the endpoint immediately after a 429 just extends the throttling window.
async function callWithBackoff(fn, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
if (err.status !== 429 || attempt === maxRetries - 1) throw err;
const delay = Math.min(1000 * 2 ** attempt, 30000) + Math.random() * 500;
await new Promise(r => setTimeout(r, delay));
}
}
}
500 — api_error
An unexpected failure on the provider's infrastructure. This isn't your fault — retry with backoff, and if it persists across several attempts, check the provider's status page before assuming it's your code.
529 — overloaded_error
The API is temporarily over capacity. Functionally similar to a 429 for handling purposes: back off and retry. This tends to spike during peak usage windows or right after major model releases when demand surges.
Streaming-specific errors
When using server-sent events for streaming responses, errors can arrive mid-stream as an event: error message rather than an HTTP status code, since the connection was already established with a 200. Your stream parser needs to handle this case separately from a clean message_stop event — check for the error event type before assuming the stream completed successfully. See the streaming docs for the exact event sequence.
Building a single error handler
Rather than scattering try/catch blocks across your codebase, centralize error handling around error.type:
function handleClaudeError(err) {
switch (err.error?.type) {
case "invalid_request_error":
logAndAlert("Bad request payload", err.error.message);
break;
case "authentication_error":
rotateApiKey();
break;
case "rate_limit_error":
case "overloaded_error":
return retryWithBackoff();
case "api_error":
return retryWithBackoff({ maxRetries: 3 });
default:
throw err;
}
}
If you're routing requests through SubToAPI, error types and status codes are normalized so you don't need separate handling logic for direct API calls versus proxied ones — the same error.type values apply whether you're hitting /v1/messages directly or through your application keys. Check the messages endpoint docs for the full response schema, or the tool use docs if your errors are coming from malformed tool schemas.
FAQ
What does "overloaded_error" mean and is it my fault? No — a 529 overloaded_error means the API is at capacity. It's not caused by anything in your request. Retry with exponential backoff; it typically resolves within seconds to a couple of minutes.
Why am I getting 400 errors even though my JSON is valid? Valid JSON syntax doesn't guarantee a valid request. Check error.message for the specific field — common culprits are a missing max_tokens, a messages array not starting with role: "user", or an unsupported parameter combination.
How do I tell a rate limit error from a quota/billing error? Rate limit errors return 429 with error.type: "rate_limit_error" and are temporary — retrying after a delay works. Billing or quota issues typically return 403 permission_error and require action in your account dashboard, not a retry.