CertSafari
    Anthropic Claude Certified Developer – Foundations (CCDV-F)· Lessons

    Domain 4 · Lesson 11/25

    Claude API error types and recovery strategies

    Debugging and Error Handling

    16 min read
    2.6% of exam
    3 sources
    Published 12 Oct 2026
    Docs as of 30 Sep 2026

    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.

    A 404 error body: error type, human-readable message and the request ID to logjson
    {
      "type": "error",
      "error": {
        "type": "not_found_error",
        "message": "The requested resource could not be found."
      },
      "request_id": "req_011CSHoEeqs5C35K2UUqR7Fy"
    }
    Claude API status codes, what each one means, and the recovery it calls for
    Status and typeWhat went wrongRecovery
    400 invalid_request_errorFormat or content of the requestChange the request; do not retry as-is
    401 authentication_errorAPI key malformed, revoked or expiredFix the key
    402 billing_errorBilling or payment informationCheck payment details in the Claude Console
    403 permission_errorKey lacks permission for the resourceCheck organization access and workspace settings
    404 not_found_errorResource not foundCheck the endpoint path and resource IDs
    409 conflict_errorRequest conflicts with the resource's current stateResolve the conflict, then retry
    413 request_too_largeRequest exceeds the maximum bytesShrink the payload below the endpoint limit
    429 rate_limit_errorRate limit or spend cap reachedDepends on which limit; see the last section
    500 api_errorUnexpected error inside Anthropic's systemsRetry with exponential backoff; contact support with the request ID if it persists
    504 timeout_errorRequest timed out while processingUse the streaming Messages API for long requests
    529 overloaded_errorAPI temporarily overloadedRetry 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. ? }")

    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?

    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?

    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?

    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?

    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.

    Maximum request size per endpoint; exceeding it returns 413 request_too_large
    EndpointMaximum request size
    Messages API32 MB
    Token Counting API32 MB
    Batch API256 MB
    Files API500 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?

    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.

    Quota-related responses, the signal that identifies each, and its recovery
    CauseStatus and typeDistinguishing signalRecovery
    Rate limit (RPM, ITPM, OTPM)429 rate_limit_errorMessage names the exceeded limit; retry-after header presentWait as retry-after says; the SDK honors it
    Acceleration limit429 rate_limit_errorFollows a sharp increase in API usageTemporary; back off and retry
    Usage tier monthly spend cap429 rate_limit_errorNo retry-after; error.details.error_code is enforced_spend_limit_reachedStop 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_errorMessage begins You have reached your specified API usage limitsRaise or remove the limit
    Claude Code workspace limit429retry-after header presentWait 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.

    A spend-cap 429: same error type as a rate limit, but error_code identifies it and no retry will succeedjson
    {
      "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.

    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?

    Sources23

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 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. 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. 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. 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.

      Covered in Sorting an error: fix the request or retry it

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 1.
      “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. 2.
      “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. 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

    Continue to page 2 of 2

    Tracing Claude failures to the integration or the model

    Spotted a mistake, or was something unclear? Tell us.