What you will be able to do
- Sort any Claude API error into fix-the-request or retry from its status code and error type
- Predict what the SDK retries on its own and configure max_retries deliberately
- Resolve 413 and timeout failures by changing payload size or transport, not by retrying
- Tell a rate-limit 429, an acceleration 429, a spend-cap 429 and a spend-limit 400 apart and pick the recovery for each
- Choose between changing the request, waiting for the SDK, changing transport and escalating, based on the evidence in a failed call
- Read a failed call's status, error type, request ID and headers to name the failure mode and decide whether the integration layer or the model's output is at fault
Key concept
Error origin decides recovery — A 4xx means the request, key, account or payload must change before it can succeed. A 5xx or a temporary 429 means the same request may succeed later, so you retry it with backoff. Picking the wrong group either loops forever or wastes time fixing code that was fine.
1.Sorting an error: fix the request or retry it
A failed Claude API call tells you whose problem it is before you open your own logs. The status code and the error.type string split failures into two groups. A 4xx means something is wrong with the request itself: its body, its key, its account or its size. Sending it again unchanged will fail again. A 500, 504 or 529 means something went wrong on the server side, and the same request may succeed a moment later. If you retry a 400, you loop forever. If you debug a 500 by hand, you spend an afternoon on code that was never broken.
The error body always has the same shape. It is JSON with a top-level error object that holds a type and a message, plus a request_id. The same ID comes back on every response, successful or not, in the request-id header. Log it with every failure. Support needs it when a 500 persists, and it links an application log line to the exact API call. The Python and TypeScript SDKs expose it as _request_id on response objects.
{
"type": "error",
"error": {
"type": "not_found_error",
"message": "The requested resource could not be found."
},
"request_id": "req_011CSHoEeqs5C35K2UUqR7Fy"
}| Status and type | What went wrong | Recovery |
|---|---|---|
| 400 invalid_request_error | Format or content of the request | Change the request; do not retry as-is |
| 401 authentication_error | API key malformed, revoked or expired | Fix the key |
| 402 billing_error | Billing or payment information | Check payment details in the Claude Console |
| 403 permission_error | Key lacks permission for the resource | Check organization access and workspace settings |
| 404 not_found_error | Resource not found | Check the endpoint path and resource IDs |
| 409 conflict_error | Request conflicts with the resource's current state | Resolve the conflict, then retry |
| 413 request_too_large | Request exceeds the maximum bytes | Shrink the payload below the endpoint limit |
| 429 rate_limit_error | Rate limit or spend cap reached | Depends on which limit; see the last section |
| 500 api_error | Unexpected error inside Anthropic's systems | Retry with exponential backoff; contact support with the request ID if it persists |
| 504 timeout_error | Request timed out while processing | Use the streaming Messages API for long requests |
| 529 overloaded_error | API temporarily overloaded | Retry with backoff |
Two rows need a closer look. A 409 conflict_error is a 4xx that you do retry, but only after you resolve its cause. The cause is either a resource that was changed concurrently or a value that must be unique and is already in use. The classic case is two workers updating the same resource at the same moment. Retrying without making them run one after the other just reproduces the conflict. A 500 api_error is the opposite case. Nothing in your request caused it, so you back off and retry, and if it persists you open a support ticket with the request ID. New type values may appear over time, so branch on the types you know and handle unknown ones by their status group.
A single failed call is a small trace, and reading it field by field names the failure mode. The status code and error.type give the group. The message often carries the specific cause: a 400 about thinking blocks starts with the position of the offending block, such as messages.1.content.0, which points at the exact entry in your history that your code altered. Headers add more. A retry-after header means waiting will work, and its absence on a 429 together with an error.details.error_code of enforced_spend_limit_reached means it will not. The request_id ties all of this to one call, so the same ID appears in your log line, the error body and any support ticket.
Patterns across several traces separate failure modes that one trace cannot. The same call failing identically on every attempt points at the request, key or account. A 5xx that clears on the next attempt points at the server side. A 429 still failing hours later points at a spend cap, not a per-minute limit. The first split to make is where the failure sits. An error body means the integration layer failed and no output was produced, so you read the status and fix or retry. Streaming adds a catch: an error can arrive after the API has already returned a 200, so a 200 status alone does not prove the call succeeded. When the trace shows a clean 200 and the result is still wrong, the question moves to the model's output, covered in the stop-reason lesson.
Checkpoint 1 of 8· Fill the gap
Which attribute gives you the ID to log alongside this call?
message = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
)
print(f"Request ID: {message. ? }")The Python and TypeScript SDKs expose the request ID as the _request_id property on top-level response objects. id is the message ID, not the request ID.
Checkpoint 2 of 8· Check yourself
Two instances of a background worker hit the same resource within milliseconds, and one gets 409 conflict_error. What is the right response?
A 409 means the request conflicts with the resource's current state. The fix is to resolve the conflict first and then retry.
“Resolve the conflict, then retry the request.”Source: platform.claude.com
Checkpoint 3 of 8· Exam question
A production job calling the Messages API begins failing intermittently with HTTP 429 responses. Each failed response includes a `retry-after` header. The team's current retry code catches the exception and immediately resubmits the same request in a tight loop. What is the most effective change to make to the retry logic?
Correct answer: A — Parse the retry-after header and wait at least that many seconds before resubmitting, backing off further on repeated 429s
- A. Correct. rate_limit_error responses carry a retry-after header specifying the wait time; honoring it (and increasing backoff on repeated failures) is the documented handling for 429s and avoids hammering an already-throttled endpoint.
- B. Batch processing is a separate asynchronous workflow with its own rate limits and queue semantics; switching APIs does not fix a retry loop that ignores retry-after on the existing synchronous call.
- C. max_tokens affects output token consumption and OTPM accounting, not the request-per-minute or input-token limits that commonly trigger 429s, so this does not address the retry loop's core defect.
- D. Rate limits are enforced at the organization level, not per API key, so switching keys does not grant a separate quota and does not fix the missing backoff logic.
Sources1
2.What the SDK handles before your code sees an error
The official SDKs turn error JSON into typed exceptions. In Python, for example, a 404 surfaces as anthropic.NotFoundError. Your recovery code should branch on the exception class, never on the message text. Order matters: catch the most specific classes first, because a broad handler placed above them swallows the cases you meant to handle differently. Go doesn't follow the one-class-per-status pattern. Its SDK has a single *anthropic.Error type, and you branch on StatusCode.
The SDK is already retrying before your handler runs. It retries temporary failures (connection errors, rate limits and 5xx server errors) twice by default with exponential backoff. When the response has a retry-after header, the SDK waits as it says. The client's max_retries setting changes the retry count or turns retries off. This has two consequences for debugging. First, a retryable error that reaches your code has already been retried, so the problem lasted longer than the backoff. Second, wrapping the call in your own retry loop multiplies the SDK's attempts. Set max_retries to the number of attempts you actually want, and put your own handling on the 4xx errors, which the SDK does not retry.
Choosing a recovery strategy means picking one of four moves for each error that reaches your handler. Change the request for 400, 401, 402, 403, 404 and 413, since no repeat can succeed. Wait and let the SDK retry for a 429 that carries retry-after, a 500 or a 529. Change the transport for a 504 or a long non-streaming call, by moving to streaming or the Message Batches API. Stop and escalate when retrying is pointless: a spend-cap 429 until access resumes, or a 500 that persists, which goes to support with its request ID.
The choice also tunes max_retries. A user-facing request that must answer in seconds can lower it or set it to zero and show a fallback quickly. A nightly batch job can tolerate longer backoff and keep the default or raise it. Whatever the setting, an error that reaches your handler after the retries are spent is evidence: if a 5xx survived the backoff, log the request ID and escalate rather than looping again. A 409 is the one 4xx where your own retry makes sense, and only after you have resolved the conflict.
Checkpoint 4 of 8· Check yourself
With default client settings, which of these failures will the SDK retry on its own before raising?
The SDK retries temporary failures (connection errors, rate limits and 5xx server errors) twice by default. 529 is a 5xx. The others are 4xx errors that need a changed request.
“The official SDK automatically retries transient failures (such as connection errors, rate limits, and 5xx server errors) with exponential backoff, twice by default”Source: platform.claude.com
Checkpoint 5 of 8· Check yourself
A worker's call raises 429 rate_limit_error after the SDK's retries, the response has no retry-after header, and error.details.error_code is enforced_spend_limit_reached. Which strategy fits?
A tier spend-cap 429 keeps failing until access resumes, so neither more retries nor a changed body helps. The recovery is a higher tier or waiting for the reset.
“A tier spend-cap 429 has no retry-after header and keeps failing until access resumes”Source: platform.claude.com
Sources1
3.Oversized requests and long-running calls
A 413 request_too_large is about bytes, and the model never sees the request. On the direct Claude API, Cloudflare rejects it before it reaches the API servers. Prompt wording, model choice and max_tokens make no difference. The only fix is a payload under the size limit of the endpoint you are calling. Retrying the same body cannot succeed.
| Endpoint | Maximum request size |
|---|---|
| Messages API | 32 MB |
| Token Counting API | 32 MB |
| Batch API | 256 MB |
| Files API | 500 MB |
A timeout is the same kind of problem, measured in time instead of bytes. A 504 timeout_error means the request timed out while processing, and the fix for long-running requests is the streaming Messages API. Idle connections cause a quieter version of the same failure. Some networks drop idle connections after a variable period, so a non-streaming call with a large max_tokens can fail or hang with no response from Anthropic at all. The SDKs guard against this in two ways. They set TCP keep-alive, and they check that a non-streaming request is not expected to run longer than 10 minutes. When that check fails, switch to streaming. The SDK can read the whole stream for you and return the same complete Message a non-streaming call would. The other option is the Message Batches API, where you poll for results instead of holding a connection open.
Checkpoint 6 of 8· Check yourself
A single Messages API call that includes several large documents in the request returns 413 request_too_large. What resolves it?
A 413 rejects the request for its byte size before it reaches the API servers. Retrying, changing the model or changing max_tokens does nothing. Only a smaller payload works.
“On the direct Claude API, Cloudflare returns this error before the request reaches the API servers.”Source: platform.claude.com
Sources1
4.Telling apart the 429s, and the 400 that looks like one
Several different limits share the same status codes, so the code alone doesn't tell you how to recover. Rate limits cap requests and tokens per minute, and spend limits cap monthly cost. The tiers and numbers are covered in [Rate limits, spend limits and service tiers](lesson:2.1). For debugging, what matters is the signals each response carries.
| Cause | Status and type | Distinguishing signal | Recovery |
|---|---|---|---|
| Rate limit (RPM, ITPM, OTPM) | 429 rate_limit_error | Message names the exceeded limit; retry-after header present | Wait as retry-after says; the SDK honors it |
| Acceleration limit | 429 rate_limit_error | Follows a sharp increase in API usage | Temporary; back off and retry |
| Usage tier monthly spend cap | 429 rate_limit_error | No retry-after; error.details.error_code is enforced_spend_limit_reached | Stop retrying; move to a higher tier or wait for 00:00 UTC on the first of next month |
| Spend limit you set (organization or workspace) | 400 invalid_request_error | Message begins You have reached your specified API usage limits | Raise or remove the limit |
| Claude Code workspace limit | 429 | retry-after header present | Wait as retry-after says |
The retry-after header is the quickest way to tell them apart. A rate-limit 429 names the limit you exceeded and includes retry-after, which the SDK already honors. A rate limit can be enforced over a shorter window than a minute. For example, 60 RPM may be enforced as one request per second, so a burst can hit the limit even when the per-minute average looks fine. Acceleration limits also return 429 rate_limit_error, but they are triggered by a sharp increase in usage rather than a steady rate. The tier spend-cap 429 is the one where you must stop retrying. It has no retry-after, and its error.details.error_code is enforced_spend_limit_reached.
{
"type": "error",
"error": {
"type": "rate_limit_error",
"message": "You have reached your API usage limits: your organization has crossed its monthly API usage threshold, set based on your organization's API tier. You will regain access on 2026-09-01 at 00:00 UTC.",
"details": { "error_code": "enforced_spend_limit_reached" }
},
"request_id": "req_018EeWyXxfu5pfWkrYcMdjWG"
}There is also a 400 that looks like a request error but is really a quota. When usage reaches a spend limit you set for the organization or a workspace, the API returns 400 invalid_request_error. The message begins You have reached your specified API usage limits (or workspace API usage limits). The request body is fine, so editing it does nothing. Raising or removing the limit restores access. Limits on the Claude Code workspace are checked separately and can return a 429 with retry-after instead.
Checkpoint 7 of 8· Match them up
Match each response to the recovery it calls for
Tap a term, then the definition that fits it.
The spend-cap 429 has the same rate_limit_error type as an ordinary rate limit but no retry-after header. The spend limit you set comes back as a 400 even though the request is valid.
“The error type is rate_limit_error, the same as for a rate limit, but the response has no retry-after header.”Source: platform.claude.com
Checkpoint 8 of 8· Exam question
An agent sends a tool_use request for a `query_database` tool. The client-side tool executor throws a connection exception, and the developer's code returns a tool_result block with `is_error: true` and the exception message as content, then sends this back to Claude in the next turn. Claude's subsequent response apologizes and suggests checking the database connection instead of returning query results. Where does the root cause of this failed turn lie?
Correct answer: A — In the integration layer, because the tool executor failed to reach the database; Claude's reply correctly reflects the is_error tool_result it was given
- A. Correct. The failure occurred in the client-side tool executor (the integration layer) before any model reasoning happened; Claude only saw the is_error tool_result the application constructed and responded appropriately to that signal, so its output is not the source of the defect.
- B. Claude does not autonomously retry tool calls on its own initiative when given an error result; the application is responsible for deciding whether and how to retry the failed tool invocation.
- C. is_error is a general-purpose flag for signaling any tool execution failure back to Claude, including connection failures, and is not restricted to cases where the tool name was misspelled.
- D. Acknowledging a reported tool failure is the expected and desired model behavior given an is_error tool_result; no prompt tuning is meant to suppress this signal, since suppressing it would hide real integration failures from the user.
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Every 400 invalid_request_error means the request body is malformed and must be edited.Why is that wrong?
A spend limit you set also returns 400 invalid_request_error. The body is valid, and the fix is to raise or remove the limit.
Covered in Telling apart the 429s, and the 400 that looks like one
2.Any 429 rate_limit_error clears if you let the SDK keep retrying.Why is that wrong?
A tier spend-cap 429 has no retry-after header and keeps failing until access resumes. Check whether error.details.error_code is enforced_spend_limit_reached.
Covered in Telling apart the 429s, and the 400 that looks like one
3.A 413 request_too_large is a capacity problem that a retry or a bigger-context model can fix.Why is that wrong?
A 413 rejects the request for its byte size before it reaches the API servers. Only a smaller payload fixes it.
Covered in Oversized requests and long-running calls
4.A 200 response means the call succeeded, so any failure must be in the model's output.Why is that wrong?
On a streaming response an error can occur after the API has already returned a 200, and it does not follow the usual error handling.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.https://platform.claude.com/docs/en/api/errorsOfficial docs
“The API always returns errors as JSON, with a top-level error object that always includes a type and message value.”
↩︎ Sorting an error: fix the request or retry it“There was an issue with the format or content of your request.”
↩︎ Sorting an error: fix the request or retry it“For example, the resource was modified concurrently, or a value that must be unique is already in use.”
↩︎ Sorting an error: fix the request or retry it“Retry the request with exponential backoff; if the error persists, contact support with the request ID.”
↩︎ Sorting an error: fix the request or retry it“it is possible that the type values will grow over time.”
↩︎ Sorting an error: fix the request or retry it“The same identifier appears as the request_id field in error response bodies.”
↩︎ Sorting an error: fix the request or retry it“The error message starts with the position of the offending block (for example, messages.1.content.0)”
↩︎ Sorting an error: fix the request or retry it“an error can occur after the API returns a 200 response”
↩︎ Sorting an error: fix the request or retry it“Catch the SDK's typed classes rather than string-matching error messages, handling the most specific classes first.”
↩︎ What the SDK handles before your code sees an error“The SDK client accepts max_retries to configure or disable this behavior.”
↩︎ What the SDK handles before your code sees an error“A tier spend-cap 429 has no retry-after header and keeps failing until access resumes”
↩︎ What the SDK handles before your code sees an error“On the direct Claude API, Cloudflare returns this error before the request reaches the API servers.”
↩︎ Oversized requests and long-running calls“Consider using the streaming Messages API for long-running requests.”
↩︎ Oversized requests and long-running calls“The SDKs validate that your non-streaming Messages API requests are not expected to exceed a 10-minute timeout.”
↩︎ Oversized requests and long-running calls“Retry the request with exponential backoff; if the error persists, contact support with the request ID.”
↩︎ Key concept“If you exceed these limits, you'll receive a 413 request_too_large error.”
↩︎ Exam trap 3“an error can occur after the API returns a 200 response”
↩︎ Exam trap 4“Resolve the conflict, then retry the request.”
↩︎ Checkpoint“The official SDK automatically retries transient failures (such as connection errors, rate limits, and 5xx server errors) with exponential backoff, twice by default”
↩︎ Checkpoint - 2.https://platform.claude.com/docs/en/api/rate-limitsOfficial docs
“you will get a 429 error describing which rate limit was exceeded, along with a retry-after header indicating how long to wait.”
↩︎ Telling apart the 429s, and the 400 that looks like one“Short bursts of requests can exceed the limit and trigger rate limit errors.”
↩︎ Telling apart the 429s, and the 400 that looks like one“Claude Code requests over that workspace's limit can instead receive a 429 that carries a retry-after header.”
↩︎ Telling apart the 429s, and the 400 that looks like one“When usage reaches a spend limit you set, requests return HTTP 400 with error type invalid_request_error.”
↩︎ Exam trap 1“On the Messages API, error.details.error_code is enforced_spend_limit_reached. Use it to tell this response apart from a rate limit.”
↩︎ Exam trap 2“Retrying, including the SDK's automatic retries, fails until access resumes.”
↩︎ Prediction“The error type is rate_limit_error, the same as for a rate limit, but the response has no retry-after header.”
↩︎ Checkpoint - 3.
“Some customers might encounter 429 (rate_limit_error) errors following a sharp increase in API usage due to acceleration limits on the API.”
↩︎ Telling apart the 429s, and the 400 that looks like one