Site

Errors — Handling API Failures

Tutorial 9.0  •  AI / Learn

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:

9.1 The anthropic Exception Hierarchy

All errors raised by the SDK inherit from anthropic.APIError. The most important subclasses are:
ExceptionCause
AuthenticationErrorAPI key is missing, wrong, or expired
PermissionDeniedErrorKey exists but lacks access to the requested resource
RateLimitErrorToo many requests per minute; slow down and retry
APIConnectionErrorNetwork failure; the request never reached the server
APIStatusErrorServer-side error (HTTP 5xx); may be transient
BadRequestErrorInvalid 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:
# 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

TermMeaning
APIErrorBase class for all errors raised by the anthropic SDK
AuthenticationErrorRaised when the API key is missing, wrong, or expired
RateLimitErrorRaised when the request rate exceeds the per-minute limit
retryAttempting the same API call again after a failure
exponential backoffDoubling the wait time between each retry attempt
stop_reasonField on the response indicating why generation stopped ("end_turn" or "max_tokens")
max_retriesThe maximum number of retry attempts before giving up