Skip to main content

Claude SDK Error Handling and Retries: APIError Types, max_retries, and Timeout Config

A practical guide to how the Claude API's Python and TypeScript SDKs classify errors into typed exceptions, which failures get retried automatically, and how to customize retry and timeout behavior.

By
🌐 This article was machine-translated and may contain inaccuracies. Read the Korean original if in doubt.

When a request to the Claude API (Anthropic's interface for calling Claude) fails, the official Python and TypeScript SDKs raise differently named exceptions depending on the type of failure, and automatically retry some of them for you. This guide covers which errors map to which exception, which failures get retried automatically, and how to customize the retry count and timeout (the maximum time the SDK waits for a response), with runnable code examples. Reading and applying it should take about 10 minutes.

🟢 Model references match the current lineup · model notice · Fable subscription
🟢 Model references match the current lineup · Claude Opus 5.5 / Claude Sonnet 5 / Claude Haiku 4.5 (higher tier: Claude Fable 5.1). This notice changes only when Anthropic ships a new model.

Fable 5 and 5.1 subscription (updated September 7, 2026): Claude Fable 5.1, released September 1, 2026, is the current Fable model and Fable 5 is now legacy. Plan terms are the same for both — Max and Team Premium plans include Fable at up to 50% of the weekly usage limit; Pro and Team Standard use usage credits (

🟢 Model references match the current lineup · Claude Opus 5.5 / Claude Sonnet 5 / Claude Haiku 4.5 (higher tier: Claude Fable 5.1). This notice changes only when Anthropic ships a new model.
0/M input, $50/M output tokens). The one-time
🟢 Model references match the current lineup · Claude Opus 5.5 / Claude Sonnet 5 / Claude Haiku 4.5 (higher tier: Claude Fable 5.1). This notice changes only when Anthropic ships a new model.
00 credit applied only to the Fable 5 transition and is not offered for 5.1. Some coding and debugging requests may be answered by an Opus model due to a security classifier (both models). See the Fable 5.1 guide and the Fable 5 availability guide for details.

API request Error occurs Is it retryable? Yes — connection error, 408, 409 429, or 500+ No — 400, 401, 403, 404, 422, etc. Retried with exponential backoff (2 times by default) Typed exception raised immediately Exception raised once retries are exhausted

What you'll be able to do after this

By the end of this guide, you'll be able to tell exactly which Claude API errors get retried automatically and adjust the retry count (max_retries) and timeout to fit your situation. It assumes you've already made a successful client.messages.create() call.

Before you start

  • Python 3.9 or later, or Node.js 20 LTS or later — the minimum version each SDK requires.
  • SDK installed — pip install anthropic for Python, npm install @anthropic-ai/sdk for TypeScript.
  • A Claude API key set as the ANTHROPIC_API_KEY environment variable, and at least one successful messages.create() call already made.

Terms in plain language

  • APIError — the top-level exception class the SDK uses to represent a failed request.
  • APIStatusError — raised when the API responds with a clear HTTP status code such as 400, 404, or 500; it has status-code-specific subclasses.
  • APIConnectionError — raised when the SDK couldn't even reach the server, for example because of a network problem (no status code involved).
  • Exponential backoff — a retry strategy where the wait time between attempts gets longer each time.
  • max_retries — the option that sets the maximum number of automatic retries.
  • timeout — the option that sets the maximum time to wait for a response.
  • with_options() / second argument — a way to apply different settings to a single request instead of the whole client. Python uses with_options(); TypeScript uses the request method's second argument.

How do APIError types differ?

When the Claude API returns an error, the Python and TypeScript SDKs raise a differently named typed exception based on the HTTP status code, and both languages use identical class names.

Status codeException classMeaning
400BadRequestErrorSomething is wrong with the request's format or content
401AuthenticationErrorThe API key is invalid or expired
403PermissionDeniedErrorNo permission to use the requested resource
404NotFoundErrorThe requested resource wasn't found
409ConflictErrorThe request conflicts with the resource's current state
422UnprocessableEntityErrorThe request content can't be processed
429RateLimitErrorThe account has hit a rate limit
500+InternalServerErrorAn internal error on Anthropic's servers
N/AAPIConnectionErrorThe server couldn't be reached at all (e.g. network issue)

In Python, catch the most specific exceptions first.

import anthropic

try:
    message = client.messages.create(
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hello, Claude"}],
        model="claude-sonnet-5",
    )
except anthropic.APIConnectionError as e:
    print("The server could not be reached")
    print(e.__cause__)
except anthropic.RateLimitError as e:
    print("A 429 status code was received; back off and retry later")
except anthropic.APIStatusError as e:
    print("Another non-200-range status code was received")
    print(e.status_code)

In TypeScript, use err instanceof to branch the same way.

const message = await client.messages
  .create({
    max_tokens: 1024,
    messages: [{ role: "user", content: "Hello, Claude" }],
    model: "claude-sonnet-5"
  })
  .catch((err) => {
    if (err instanceof Anthropic.APIError) {
      console.log(err.status); // e.g. 429
      console.log(err.name);   // e.g. RateLimitError
    } else {
      throw err;
    }
  });

Caution: if you catch the top-level APIError class first, any more specific branch below it (for example, handling RateLimitError differently) will never run. Always catch the most specific exception first.

Which errors get retried automatically?

Connection errors, 408 Request Timeout, 409 Conflict, 429 Rate Limit, and 500+ internal errors are automatically retried by the SDK with exponential backoff, up to 2 times by default. If the server sends a retry-after header, the SDK honors it when timing the wait. Errors outside this list (such as 400, 401, 403, 404, or 422) raise an exception immediately with no retry.

Step 1 · SDK defaults 2 retries max · 10-minute timeout, when nothing else is set Step 2 · Options at client creation e.g. new Anthropic({ maxRetries: 0, timeout: 20000 }) — applies to every request Step 3 · Per-request override (highest priority) Python: with_options(...) · TypeScript: second argument { }

You can change the retry count as a client-wide default, or override it for a single request.

# Python — change the client-wide default
client = Anthropic(
    max_retries=0,  # default is 2
)

# Override for a single request
client.with_options(max_retries=5).messages.create(
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello, Claude"}],
    model="claude-sonnet-5",
)
// TypeScript — change the client-wide default
const client = new Anthropic({
  maxRetries: 0 // default is 2
});

// Override for a single request
await client.messages.create(
  {
    max_tokens: 1024,
    messages: [{ role: "user", content: "Hello, Claude" }],
    model: "claude-sonnet-5"
  },
  { maxRetries: 5 }
);

What's the default timeout, and how do I change it?

Both the Python and TypeScript SDKs default to a 10-minute timeout. On timeout, Python raises APITimeoutError while TypeScript raises APIConnectionTimeoutError — and either way, a timed-out request is still subject to the default retry policy described above (retried up to 2 times automatically).

# Python — change the client-wide default
import httpx
from anthropic import Anthropic

client = Anthropic(
    timeout=20.0,  # 20 seconds (default is 10 minutes)
)

# More granular control (separate connect/read/write timeouts)
client = Anthropic(
    timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
)

# Override for a single request
client.with_options(timeout=5.0).messages.create(
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello, Claude"}],
    model="claude-sonnet-5",
)
// TypeScript — change the client-wide default
const client = new Anthropic({
  timeout: 20 * 1000 // 20 seconds (default is 10 minutes)
});

// Override for a single request
await client.messages.create(
  {
    max_tokens: 1024,
    messages: [{ role: "user", content: "Hello, Claude" }],
    model: "claude-sonnet-5"
  },
  { timeout: 5 * 1000 }
);

If you set a large max_tokens value without streaming (receiving the response incrementally as it's generated), the TypeScript SDK automatically scales the timeout up to 60 minutes using this formula.

const minimum = 10 * 60;
const calculated = (60 * 60 * maxTokens) / 128_000;
return calculated < minimum ? minimum * 1000 : calculated * 1000;

Caution: if a non-streaming request is expected to take longer than roughly 10 minutes, Python raises a ValueError upfront and TypeScript throws an equivalent error to block the request before it's sent. Pass stream=True (stream: true in TypeScript) to enable streaming, or explicitly raise the timeout value, to lift this pre-check.

Common issues and fixes

1. Repeated "could not connect" errors
Cause: APIConnectionError has no status code — it means the SDK couldn't reach the server at all, often due to network, firewall, or proxy issues. Fix: the SDK already retries automatically, but if it keeps happening, check your base_url configuration and any corporate proxy or firewall settings first.

2. Requests with a large max_tokens failing before they even start
Cause: setting a large max_tokens without streaming pushes the expected duration past roughly 10 minutes, so the SDK blocks the request upfront. Fix: use client.messages.stream() to stream the response, or explicitly raise the timeout option.

3. A dedicated 429 (RateLimitError) branch never runs
Cause: catching a parent class like APIError or APIStatusError first prevents any more specific exception branch beneath it from executing. Fix: catch the most specific exception (such as RateLimitError) first, and put parent classes last.

Going further

Once this is in place, it's worth logging the request-id response header alongside any failure. Every response object exposes it directly as _request_id, and having it ready speeds up any conversation with Anthropic support. If you regularly work with long-running requests, it's also worth evaluating a switch to the streaming Messages API to sidestep timeouts altogether.

Frequently asked questions

Q. How many times are requests retried by default?
The default is 2 retries. Only connection errors, 408, 409, 429, and 500+ errors are retried, using exponential backoff between attempts, and you can raise or disable this with the max_retries option (0 disables it).

Q. What exception is raised when the default timeout is exceeded?
Python raises APITimeoutError, and TypeScript raises APIConnectionTimeoutError. Either way, the failed request is still covered by the default retry policy and gets retried automatically.

Q. Do I need to write my own retry logic for RateLimitError (429)?
No, you don't need to — the SDK already retries it automatically. If it keeps recurring, the real fix is reducing your request rate or checking your usage limits, not adding more retry code.

Q. Can I use a different timeout or retry count for just one request?
Yes — you can leave the client-wide defaults untouched and override settings for a single request only. Python uses with_options(), and TypeScript uses the request method's second argument.

Was this helpful?

Keep reading