How to Handle and Differentiate Various Error Types from the Anthropic API
The Anthropic Python SDK converts every HTTP error into a typed exception hierarchy, allowing you to catch specific errors like RateLimitError or inspect the body.type attribute of APIStatusError to implement precise error handling and retry logic.
The anthropics/anthropic-sdk-python repository provides a robust exception architecture that maps every API error response to a typed Python exception. When working with the Claude API, understanding how to handle and differentiate various error types from the API is essential for building resilient applications that can retry transient failures or abort on authentication issues.
Understanding the Error Payload Structure
When the Anthropic API returns an error, the JSON body follows a consistent structure containing an error object with type and message fields:
{
"error": {
"type": "rate_limit_error",
"message": "This request would exceed the rate limit for your organization..."
}
}
The SDK models this payload using Pydantic classes defined in src/anthropic/types/shared/. The ErrorResponse class in error_response.py serves as the top-level container, while ErrorObject in error_object.py acts as a discriminated union of all concrete error types. Each specific error—such as RateLimitError, AuthenticationError, or InvalidRequestError—inherits from the generated BaseModel and includes a literal type field that uniquely identifies the condition.
The SDK Exception Hierarchy
All API errors raise exceptions defined in src/anthropic/_exceptions.py, organized into a clear inheritance tree:
AnthropicError (base)
└─ APIError (holds request, message, and optional body)
└─ APIStatusError (raised for any 4xx/5xx response)
├─ BadRequestError (400)
├─ AuthenticationError (401)
├─ PermissionDeniedError (403)
├─ NotFoundError (404)
├─ ConflictError (409)
├─ RequestTooLargeError (413)
├─ UnprocessableEntityError (422)
├─ RateLimitError (429)
├─ ServiceUnavailableError (503)
├─ OverloadedError (529)
├─ DeadlineExceededError (504)
└─ InternalServerError (≥500)
APIStatusError serves as the parent for all HTTP error status codes. Each subclass corresponds to a specific status code, allowing you to catch granular errors without parsing JSON manually.
How Status Codes Map to Exceptions
The mapping logic resides in the _make_status_error method inside src/anthropic/_client.py. When the underlying HTTP client (httpx) returns a response with is_error set to True (status ≥ 400), the SDK:
- Extracts the status code from the response
- Instantiates the corresponding
APIStatusErrorsubclass based on the code mapping - Parses the JSON body into the appropriate
ErrorObjectmodel (e.g.,RateLimitErrormodel) and stores it in the exception'sbodyattribute
This architecture means every exception contains both the HTTP-level information (status code, headers) and the strongly-typed API error model.
Differentiating Errors in Practice
Catching Specific Exception Classes
For clear control flow, import and catch the specific exception classes that correspond to the errors you expect. This approach works for both synchronous and asynchronous clients:
from anthropic import Anthropic, RateLimitError, AuthenticationError
client = Anthropic()
try:
response = client.messages.create(
model="claude-3-5-sonnet-20240620",
max_tokens=1024,
messages=[{"role": "user", "content": "Explain quantum computing."}]
)
print(response.content)
except RateLimitError as e:
# Access the request ID for support tickets
print(f"Rate limited. Request ID: {e.request_id}")
# Implement exponential back-off before retrying
except AuthenticationError as e:
print(f"Authentication failed: {e.message}")
# Do not retry; log and alert the team
RateLimitError and other concrete exceptions expose message and request_id attributes directly, making it easy to log diagnostic information without drilling into the response body.
Inspecting Error Types via the Response Body
When you need to handle multiple error types dynamically or want a single catch-all with conditional logic, catch APIStatusError and inspect the body attribute:
from anthropic import Anthropic, APIStatusError
client = Anthropic()
try:
client.messages.create(
model="claude-3-opus-20240229",
max_tokens=512,
messages=[{"role": "user", "content": "What is the meaning of life?"}]
)
except APIStatusError as err:
# body is the parsed ErrorObject model (e.g., AuthenticationError, InvalidRequestError)
err_type = getattr(err.body, "type", "unknown")
if err_type == "authentication_error":
print("🔐 Invalid API key. Check your ANTHROPIC_API_KEY environment variable.")
elif err_type == "invalid_request_error":
print("❌ Request validation failed. Review your message payload.")
elif err_type == "rate_limit_error":
print("⏱️ Rate limit hit. Backing off before retry...")
elif err_type == "overloaded_error":
print("⚡ Service overloaded. Retry after a longer pause.")
else:
print(f"Unhandled API error ({err_type}): {err.message}")
This pattern avoids importing numerous exception classes and lets you branch based on the literal type string defined in the API specification.
Handling Errors in Async Applications
The async client (AsyncAnthropic) mirrors the synchronous exception hierarchy exactly. You can use the same exception classes with async/await syntax:
import asyncio
from anthropic import AsyncAnthropic, AuthenticationError, OverloadedError
async def main() -> None:
client = AsyncAnthropic()
try:
await client.messages.create(
model="claude-3-sonnet-20240229",
max_tokens=256,
messages=[{"role": "user", "content": "Summarize general relativity."}]
)
except AuthenticationError:
print("🛡️ Bad API key – aborting workflow.")
raise # Re-raise to stop execution
except OverloadedError:
print("⚡ Service temporarily overloaded – scheduling retry.")
# Implement back-off and retry logic here
except Exception as exc:
print(f"⚠️ Unexpected error: {exc}")
asyncio.run(main())
Retry Strategies and Error Classification
Not all errors warrant a retry. Use the following classification when implementing retry logic:
-
Safe to retry with back-off:
RateLimitError(429) – Retry after respecting theRetry-AfterheaderOverloadedError(529) – Anthropic-specific capacity issue; retry with exponential back-offServiceUnavailableError(503) – Temporary server unavailability
-
Do not retry without corrective action:
AuthenticationError(401) – Indicates an invalid or expired API keyPermissionDeniedError(403) – Insufficient permissions for the requested resourceNotFoundError(404) – Resource does not exist; retrying will not helpBadRequestError(400) orUnprocessableEntityError(422) – Malformed request payload
When inspecting body.type, treat "invalid_request_error" and "authentication_error" as terminal failures, while "rate_limit_error" and "overloaded_error" signal temporary conditions suitable for retry.
Summary
- The SDK parses API error JSON into typed
ErrorObjectmodels defined insrc/anthropic/types/shared/ APIStatusErrorsubclasses insrc/anthropic/_exceptions.pymap HTTP status codes to specific Python exceptions- The
_make_status_errormethod insrc/anthropic/_client.pyperforms the status-code-to-exception translation - You can handle errors by catching concrete exception classes (e.g.,
RateLimitError) or by inspectingerr.body.typeon a genericAPIStatusError RateLimitErrorandOverloadedErrorare generally safe to retry, whileAuthenticationErrorand request validation errors require configuration changes
Frequently Asked Questions
What is the difference between APIStatusError and AnthropicError?
AnthropicError is the base class for all SDK-related exceptions, including network failures and configuration errors. APIStatusError is a specific subclass of APIError (which inherits from AnthropicError) that represents HTTP 4xx and 5xx responses from the Anthropic API. If you catch APIStatusError, you are specifically handling error responses from the API server, not connection issues or SDK validation errors.
How do I access the raw JSON error response?
Every APIStatusError exposes the parsed response body through its body attribute, which contains an instance of the appropriate ErrorObject model (such as RateLimitError or AuthenticationError). While the exception's message attribute provides a human-readable description, err.body gives you typed access to fields like type and message from the original JSON. There is no public attribute for the raw JSON string; the SDK deserializes it immediately upon receiving the response.
Which Anthropic API errors should I retry?
You should retry RateLimitError (HTTP 429) and OverloadedError (HTTP 529) after implementing an exponential back-off strategy. These indicate temporary capacity or rate-limiting issues. Do not retry AuthenticationError (401), PermissionDeniedError (403), NotFoundError (404), or request validation errors (400, 422), as these indicate client-side misconfigurations or invalid parameters that will fail identically on subsequent attempts.
Why do some exception classes share names with error models?
The SDK maintains two parallel hierarchies: exception classes in src/anthropic/_exceptions.py (which are raised) and error models in src/anthropic/types/shared/ (which are Pydantic models for JSON deserialization). For example, RateLimitError exists as both an exception class (for raising/catching) and as a Pydantic model (for parsing the API response). The exception's body attribute holds an instance of the corresponding model, linking the two systems together.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →