"I followed the docs exactly, so why am I getting an error?" — If you've spent your first few days with the Claude API staring at red text in your terminal, you're not alone. The gap between documentation and real-world behavior trips up almost everyone in the beginning.
This guide covers the 7 errors Python developers most commonly hit when getting started. Each section is structured around the actual error message you'll see, so you can read this alongside your terminal output and get unstuck fast.
Before You Start: Verify Your Environment
Before diving into specific errors, confirm your setup is sane:
# Check installed version
pip show anthropic
# Python 3.8+ is recommended
python --versionThe SDK is updated frequently, and using an old version can cause mismatches with current documentation — method names and parameter behavior can differ. Start fresh with the latest version:
pip install -U anthropicError 1: AuthenticationError — API Key Not Set Correctly
Example error:
anthropic.AuthenticationError: Error code: 401 - {'type': 'error', 'error': {'type': 'authentication_error', 'message': 'invalid x-api-key'}}
This is the most common first-day error. There are three typical causes.
Cause A: Hardcoding your API key in source code
# ❌ Don't do this
client = anthropic.Anthropic(api_key="sk-ant-api03-...")Hardcoding API keys risks accidental exposure when pushing to GitHub. GitHub's secret scanning will flag it.
✅ Use environment variables instead:
import anthropic
import os
# Read from environment variable
client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))# Set in your shell (temporary)
export ANTHROPIC_API_KEY="sk-ant-api03-..."Even better: if ANTHROPIC_API_KEY is set in your environment, you can omit api_key entirely — the SDK reads it automatically:
# If ANTHROPIC_API_KEY is set, this just works
client = anthropic.Anthropic()Cause B: .env file isn't being loaded
If you're using a .env file, you need to explicitly load it:
from dotenv import load_dotenv
load_dotenv() # Don't forget this line
import anthropic
client = anthropic.Anthropic()Error 2: RateLimitError (429) — Hitting the Rate Limit
Example error:
anthropic.RateLimitError: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': 'Number of request tokens has exceeded your per-minute rate limit'}}
The Claude API limits how many tokens you can send per minute. Higher-tier models like claude-opus-4-6 have stricter limits on lower usage tiers.
Fix: Implement exponential backoff
If you're sending multiple requests in a loop, you need retry logic that waits before retrying on 429:
import anthropic
import time
client = anthropic.Anthropic()
def call_with_retry(prompt: str, max_retries: int = 3) -> str:
"""Wrapper with retry logic for rate limit errors"""
for attempt in range(max_retries):
try:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
except anthropic.RateLimitError:
if attempt == max_retries - 1:
raise
wait_seconds = 2 ** attempt # 1s → 2s → 4s
print(f"Rate limited. Retrying in {wait_seconds}s...")
time.sleep(wait_seconds)
result = call_with_retry("Explain Python decorators")
print(result)For large batch workloads, the Batch API is worth considering — it processes requests asynchronously and is much less susceptible to rate limits.
Error 3: BadRequestError (400) — Wrong Model Name or Message Format
Example error:
anthropic.BadRequestError: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'model: value is not a valid enumeration member'}}
This usually means your model string is wrong. Model names change over time, and using a deprecated name causes this error.
✅ Valid model names (as of May 2026):
MODEL_CLAUDE_OPUS = "claude-opus-4-6" # Most capable
MODEL_CLAUDE_SONNET = "claude-sonnet-4-6" # Balanced (recommended)
MODEL_CLAUDE_HAIKU = "claude-haiku-4-5-20251001" # Fast and affordableDefine model names as constants so you only need to update them in one place when new versions are released.
Another 400 error: malformed messages array
# ❌ Messages must end with role "user"
messages = [
{"role": "user", "content": "Hi"},
{"role": "assistant", "content": "Hello!"} # Ending with assistant causes 400
]The messages array must always end with "role": "user". The API alternates between user and assistant turns, and it expects user input to trigger a new response.
Error 4: TypeError/IndexError — Parsing the Response Incorrectly
This isn't a network error — it's a code mistake that happens once you get a successful response but then mishandle it.
❌ Common mistakes:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}]
)
# ❌ response is a Message object, not a string
print(response) # prints the object repr
# ❌ .text doesn't exist on the Message object
print(response.text) # AttributeError✅ Correct way to access the text:
# response.content is a list (can contain multiple blocks)
# For a plain text response, access the first block's .text
print(response.content[0].text)
# Check why the response ended
print(response.stop_reason) # "end_turn", "max_tokens", "stop_sequence", etc.
# Check token usage
print(f"Input tokens: {response.usage.input_tokens}")
print(f"Output tokens: {response.usage.output_tokens}")If you're using tool calls, response.content may contain multiple blocks. Check the type before accessing:
for block in response.content:
if block.type == "text":
print("Text:", block.text)
elif block.type == "tool_use":
print("Tool call:", block.name, block.input)Error 5: Mixing Sync and Async Clients
A common "streaming doesn't work" problem comes from using the wrong client type.
❌ Trying to use an async stream with the sync client:
import anthropic
client = anthropic.Anthropic() # Sync client
# ❌ Sync client has no async stream method
async with client.messages.stream(...) as stream: # AttributeError
pass✅ Sync streaming (for regular scripts):
import anthropic
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Explain async/await in Python"}]
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)✅ Async streaming (for FastAPI, asyncio, etc.):
import anthropic
import asyncio
client = anthropic.AsyncAnthropic() # Note: AsyncAnthropic, not Anthropic
async def main():
async with client.messages.stream(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Explain async/await in Python"}]
) as stream:
async for text in stream.text_stream:
print(text, end="", flush=True)
asyncio.run(main())The rule is simple: anthropic.Anthropic() for synchronous code, anthropic.AsyncAnthropic() for async code.
Error 6: OverloadedError (529) — Server Overloaded
Example error:
anthropic.InternalServerError: Error code: 529 - {'type': 'error', 'error': {'type': 'overloaded_error', 'message': 'Overloaded'}}
This means Anthropic's servers are under heavy load — nothing wrong with your code. But without retry logic, your script will just fail.
Treat 529 the same as 429: back off and retry.
import anthropic
import time
client = anthropic.Anthropic()
def call_with_retry(prompt: str, max_retries: int = 5) -> str:
"""Handles both rate limit (429) and overload (529) errors"""
retryable_errors = (anthropic.RateLimitError, anthropic.InternalServerError)
for attempt in range(max_retries):
try:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
except retryable_errors as e:
if attempt == max_retries - 1:
raise
wait = min(2 ** attempt, 60) # Cap at 60 seconds
print(f"{type(e).__name__}: retrying in {wait}s ({attempt + 1}/{max_retries})")
time.sleep(wait)In production, reading the Retry-After header from the response gives a more precise wait time.
Error 7: Context Window Exceeded (400)
Example error:
anthropic.BadRequestError: Error code: 400 - {'message': 'prompt is too long: 220000 tokens > 200000 maximum'}
This happens when you accumulate too much conversation history or send large documents.
# ❌ Storing unlimited conversation history will eventually fail
conversation_history = []
while True:
user_input = input("You: ")
conversation_history.append({"role": "user", "content": user_input})
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=conversation_history # Grows indefinitely
)✅ Trim history to a rolling window:
MAX_HISTORY_PAIRS = 20 # Keep only the most recent 20 exchanges
def trim_history(history: list, max_pairs: int = MAX_HISTORY_PAIRS) -> list:
"""Trim conversation history to the most recent N user/assistant pairs"""
if len(history) <= max_pairs * 2:
return history
return history[-(max_pairs * 2):]
conversation_history = []
while True:
user_input = input("You: ")
conversation_history.append({"role": "user", "content": user_input})
conversation_history = trim_history(conversation_history)
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=conversation_history
)
reply = response.content[0].text
conversation_history.append({"role": "assistant", "content": reply})
print(f"Claude: {reply}")If you need to retain long context, Prompt Caching can dramatically reduce both token usage and cost.
A Quick Diagnostic Reference
Keep this handy when an error pops up:
- 401 → Check API key setup and environment variable loading
- 429 → Add backoff retry logic; consider Batch API for bulk work
- 400 (model) → Update to the current model name string
- 400 (messages) → Ensure messages array ends with role "user"
- TypeError → Use
response.content[0].textto extract text - 529 → Add backoff retry logic (same as 429)
- 400 (context) → Implement conversation history trimming
Once you've hit each of these a couple of times, they become second nature. The patterns in this guide cover the vast majority of errors you'll see in early development — and having retry logic in place from the start will save you a lot of frustration down the road.
For deeper reference, the Anthropic Python SDK source code is worth browsing — the exception class hierarchy in particular helps you write smarter error handling.