9.0 What This Teaches
API calls fail for many reasons: bad keys, network problems, rate limits, server
errors. An app that does not handle these crashes on the first failure. This tutorial
covers:
- The
anthropic exception hierarchy and what each class means
- Catching specific error types with
try/except
- Retry logic for rate-limit errors
- Exponential backoff to avoid hammering the API
- Detecting truncated responses via
stop_reason
9.1 The anthropic Exception Hierarchy
All errors raised by the SDK inherit from anthropic.APIError. The most
important subclasses are:
| Exception | Cause |
AuthenticationError | API key is missing, wrong, or expired |
PermissionDeniedError | Key exists but lacks access to the requested resource |
RateLimitError | Too many requests per minute; slow down and retry |
APIConnectionError | Network failure; the request never reached the server |
APIStatusError | Server-side error (HTTP 5xx); may be transient |
BadRequestError | Invalid parameters; fix the request before retrying |
Catching the base APIError handles all of them at once, but you lose the
ability to react differently to each type.
9.2 Catching Errors
Wrap every messages.create call in a try/except block.
Catch specific types first, then fall back to the base class:
# catch_errors.py - basic error handling for a single API call.
import anthropic
client = anthropic.Anthropic()
try:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=256,
messages=[{"role": "user", "content": "Hello"}]
)
print(response.content[0].text)
except anthropic.AuthenticationError:
print("ERROR: API key is invalid or missing. Check ANTHROPIC_API_KEY.")
except anthropic.RateLimitError:
print("ERROR: Rate limit hit. Wait a moment and try again.")
except anthropic.APIConnectionError:
print("ERROR: Network problem. Check your internet connection.")
except anthropic.APIError as e:
# Catch-all for any other Anthropic API error
print(f"API error: {e}")
9.3 RateLimitError and Retry
Rate limits are counted per minute. If you exceed them, the API returns a
RateLimitError. The simplest fix is to sleep 60 seconds and retry.
Limit retries so the script does not loop forever:
# retry_simple.py - retry up to 3 times on rate limit.
import anthropic
import time
client = anthropic.Anthropic()
def call_with_simple_retry(prompt, max_retries=3):
for attempt in range(max_retries):
try:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=256,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
except anthropic.RateLimitError:
if attempt < max_retries - 1:
print(f"Rate limit hit. Waiting 60 seconds (attempt {attempt + 1})...")
time.sleep(60)
else:
print("Rate limit hit. No more retries.")
raise
result = call_with_simple_retry("What is a Python generator?")
print(result)
9.4 Exponential Backoff
A fixed 60-second sleep is often too long for the first retry and may still be too
short after many failures. Exponential backoff doubles the wait time on each attempt:
1 second, then 2, then 4. This reduces load on the API while still recovering quickly
from brief rate-limit spikes:
# retry_backoff.py - exponential backoff retry.
import anthropic
import time
client = anthropic.Anthropic()
def call_with_retry(prompt, max_retries=3):
"""Call the API with exponential backoff on RateLimitError."""
for attempt in range(max_retries):
try:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=256,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
except anthropic.RateLimitError:
wait = 2 ** attempt # 1s, 2s, 4s
if attempt < max_retries - 1:
print(f"Rate limit. Retrying in {wait}s...")
time.sleep(wait)
else:
raise
except anthropic.AuthenticationError:
raise # no point retrying a bad key
result = call_with_retry("Explain Python list comprehensions.")
print(result)
Note that AuthenticationError re-raises immediately. There is no value
in waiting when the key itself is the problem.
9.5 AuthenticationError
An AuthenticationError means the key is absent, wrong, or expired.
Retrying will not help - fix the key first. Give the user a clear message that
tells them what to check:
# auth_check.py - handle a bad key with a helpful message.
import anthropic
client = anthropic.Anthropic()
try:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=64,
messages=[{"role": "user", "content": "Hello"}]
)
print(response.content[0].text)
except anthropic.AuthenticationError:
print("Authentication failed.")
print("Check that ANTHROPIC_API_KEY is set and contains a valid key.")
print("Get a key at https://console.anthropic.com")
9.6 Handling Incomplete Responses
If max_tokens is too small, the model stops mid-response. The
stop_reason field tells you why the model stopped:
"end_turn" - the model finished naturally
"max_tokens" - the output was cut off by the token limit
# check_truncation.py - detect and report truncated output.
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=32, # intentionally small to trigger truncation
messages=[{"role": "user", "content": "Write a long explanation of recursion."}]
)
text = response.content[0].text
print(text)
if response.stop_reason == "max_tokens":
print("\n[Response was truncated. Increase max_tokens to get the full answer.]")
Always check stop_reason when completeness matters, such as when
generating code or structured data.
9.7 Exercise
Exercise
Take the verify script from Tutorial 2 (or write a simple one-turn API call) and
wrap it in the call_with_retry function from section 9.4. Then
temporarily set ANTHROPIC_API_KEY to an invalid value (e.g.
sk-bad-key) and run the script. Confirm that it raises
AuthenticationError immediately without waiting. Restore the correct
key and confirm the call succeeds.
9.8 Common Mistakes
Not catching errors at all
# Bad - any API failure crashes the app with an unhandled exception.
response = client.messages.create(model="claude-sonnet-4-6", max_tokens=256,
messages=[{"role": "user", "content": "Hello"}])
print(response.content[0].text)
An unhandled APIError prints a stack trace and exits. Wrap calls in
try/except so the app can report a clean error or retry.
Retrying AuthenticationError
# Bad - retrying a bad key wastes time and always fails.
for attempt in range(3):
try:
response = client.messages.create(...)
break
except anthropic.APIError: # catches AuthenticationError too
time.sleep(2 ** attempt)
Catch AuthenticationError separately and re-raise it immediately.
No amount of waiting fixes a wrong key.
Using a fixed sleep time instead of backoff
# Bad - fixed sleep may still hit the rate limit on the retry.
except anthropic.RateLimitError:
time.sleep(5) # always 5 seconds, regardless of how many retries
# Good - double the wait on each attempt.
except anthropic.RateLimitError:
time.sleep(2 ** attempt)
A fixed wait that is too short will fail again on the next retry. Exponential
backoff gives the API time to recover.
9.9 Key Terms
| Term | Meaning |
| APIError | Base class for all errors raised by the anthropic SDK |
| AuthenticationError | Raised when the API key is missing, wrong, or expired |
| RateLimitError | Raised when the request rate exceeds the per-minute limit |
| retry | Attempting the same API call again after a failure |
| exponential backoff | Doubling the wait time between each retry attempt |
| stop_reason | Field on the response indicating why generation stopped ("end_turn" or "max_tokens") |
| max_retries | The maximum number of retry attempts before giving up |