Claude API Python: Send, Stream and Handle Errors in Code
Install the Anthropic Python SDK, send your first Claude message, stream a reply and handle errors, with a script you can copy and run.

To use the Claude API from Python, install the official SDK with pip install anthropic, put your API key in the ANTHROPIC_API_KEY environment variable, and call client.messages.create(...) with a model, a max_tokens value and a list of messages. That's the whole first request. The rest of this page covers streaming, error handling and the mistakes that trip people up.
The SDK needs Python 3.10 or later. It's the official client, MIT licensed, and its source lives in the anthropic-sdk-python repository on GitHub. Anthropic's client SDK overview describes it as having sync and async clients and Pydantic models.
Install the Claude API Python SDK#
Create a virtual environment first so the package stays out of your system Python, then install:
python3 -m venv .venv
source .venv/bin/activate
pip install anthropic
The package is called anthropic on PyPI and in imports. If you're upgrading from a 0.x release, read the v1 migration guide linked from the repository README. v1.0 shipped on August 20, 2026 and removed some older surface (details in the gotchas below).
Set your Claude Python API key#
Create a key in the Claude Console and keep it out of your source code. The client reads ANTHROPIC_API_KEY from the environment by default, so exporting it is enough:
export ANTHROPIC_API_KEY="your-key-here"
The repository README passes the key explicitly with os.environ.get("ANTHROPIC_API_KEY") and notes that this is the default and can be omitted. I'd leave it omitted. Fewer lines, and no temptation to paste a literal key into a file you might commit.
If you want the key passed visibly, for example when you load it from a secrets manager, use the api_key argument:
import os
from anthropic import Anthropic
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
Using os.environ[...] instead of .get(...) makes a missing variable fail immediately with a KeyError rather than later with an authentication error.
Send your first message#
This is the smallest working example, adapted from the README:
from anthropic import Anthropic
client = Anthropic()
message = client.messages.create(
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
model="claude-opus-5-5",
)
print(message.content)
Three arguments are required here: model, max_tokens and messages. message.content is a list of content blocks, not a plain string. For a text-only reply, the text of the first block is message.content[0].text.
Choosing a model ID#
As of October 18, 2026, Anthropic's models overview lists these current Claude API IDs:
| Model | Claude API ID | Pricing (input / output) |
|---|---|---|
| Claude Fable 5.1 | claude-fable-5-1 |
$10 / MTok, $50 / MTok |
| Claude Opus 5.5 | claude-opus-5-5 |
$4 / MTok, $20 / MTok |
| Claude Sonnet 5.5 | claude-sonnet-5-5 |
$2 / MTok, $10 / MTok |
| Claude Haiku 5.5 | claude-haiku-5-5 |
From $0.10 / MTok, From $0.50 / MTok |
The overview says to start with Claude Opus 5.5 for most workloads. Check the models page and the pricing page before you commit, because IDs and prices change.
Build a multi-turn conversation#
The API doesn't remember anything between calls. To continue a conversation, send the whole history each time, alternating user and assistant messages:
from anthropic import Anthropic
client = Anthropic()
history = [{"role": "user", "content": "Name a Python web framework."}]
first = client.messages.create(
max_tokens=512,
messages=history,
model="claude-opus-5-5",
)
history.append({"role": "assistant", "content": first.content[0].text})
history.append({"role": "user", "content": "Now give me one reason to pick it."})
second = client.messages.create(
max_tokens=512,
messages=history,
model="claude-opus-5-5",
)
print(second.content[0].text)
Because every turn resends the history, input tokens grow with the conversation. That's worth remembering when you read the cost section below.
Stream a reply#
Without streaming, your code waits for the full answer. For anything a person watches in a terminal or a UI, streaming shows text as it's generated. Anthropic's SDK overview says each client has built-in support for streaming. The Python helper is client.messages.stream, used as a context manager:
from anthropic import Anthropic
client = Anthropic()
with client.messages.stream(
max_tokens=1024,
messages=[{"role": "user", "content": "Explain list comprehensions in two sentences."}],
model="claude-opus-5-5",
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
print()
flush=True matters. Without it, Python may buffer stdout and you'll see the text arrive in lumps. The README excerpt I'm quoting doesn't show the streaming helper, so confirm the current method names in the full Python SDK documentation if you upgrade across major versions.
Async client#
The SDK ships an async client as well. In a web handler or any asyncio code, use AsyncAnthropic and await the calls:
import asyncio
from anthropic import AsyncAnthropic
client = AsyncAnthropic()
async def main() -> None:
message = await client.messages.create(
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
model="claude-opus-5-5",
)
print(message.content[0].text)
asyncio.run(main())
Handle errors and retries#
The SDK overview lists retries and error handling as built in, so you don't need to write a retry loop for the common transient failures. What you do need is a clear split between errors that are worth reporting and errors that mean your code is wrong. Here's a complete script that sends a message, streams the reply and handles failures:
import sys
import anthropic
from anthropic import Anthropic
client = Anthropic() # reads ANTHROPIC_API_KEY
def ask(prompt: str) -> None:
try:
with client.messages.stream(
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
model="claude-opus-5-5",
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
print()
except anthropic.APIConnectionError as exc:
print(f"Could not reach the API: {exc}", file=sys.stderr)
sys.exit(1)
except anthropic.RateLimitError:
print("Rate limited. Slow down and try again later.", file=sys.stderr)
sys.exit(1)
except anthropic.APIStatusError as exc:
print(f"API returned HTTP {exc.status_code}: {exc.message}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
ask(" ".join(sys.argv[1:]) or "Say hello in one sentence.")
Save it as ask.py and run python ask.py "Why is the sky blue?". The order of the except clauses matters: RateLimitError is a more specific kind of status error, so it has to come before APIStatusError or it will never be reached.
A few practical notes on handling:
- A connection error means the request never got a response (DNS, network, timeout). A status error means the API answered with a 4xx or 5xx.
- A 400-level error that isn't a rate limit usually means your request is malformed. Retrying won't fix it; read the message.
- Don't catch
Exceptionand carry on. You'll hide bad API keys and bad parameters.
Estimate cost before you scale up#
Billing is per token, and the pricing page lists Claude Opus 5.5 at $4 / MTok for input and $20 / MTok for output as of October 18, 2026. A tiny calculator helps you sanity-check a prompt. The token counts below are made-up inputs; real counts come from the usage data on your responses.
INPUT_PER_MTOK = 4.0 # Claude Opus 5.5 input, $ / MTok
OUTPUT_PER_MTOK = 20.0 # Claude Opus 5.5 output, $ / MTok
def cost(input_tokens: int, output_tokens: int) -> float:
return (input_tokens * INPUT_PER_MTOK + output_tokens * OUTPUT_PER_MTOK) / 1_000_000
# A chat that resends its history: input grows every turn.
history = 0
total = 0.0
for turn in range(1, 6):
new_user, reply = 200, 300
history += new_user
total += cost(history, reply)
history += reply
print(f"turn {turn}: input={history - reply:>5} output={reply} running total=${total:.6f}")
The growth is the point. Multi-turn chats cost more per turn as the history lengthens. If you resend a large fixed prefix, the pricing page describes prompt caching as a way to cut that cost. Batch processing is another lever, with a 50% discount on input and output tokens, per the same page. If speed is what you're after, see Claude Opus 5.5 fast mode.
Common mistakes with the Claude Python SDK#
- Forgetting
max_tokens. It's required onmessages.create. There's no default to lean on. - Treating
message.contentas a string. It's a list of blocks. Index into it and read.text. - Passing
temperature,top_portop_k. The release notes say v1.0 removed these parameters from Messages methods. Code copied from older tutorials may fail on a current install. - Using the legacy Text Completions API. v1.0 also removed it. Use the Messages API.
- Forgetting to
awaiton the async client. With v1.0,.with_raw_responseresults on the async client needawait response.parse(). - Old Python. v1.0 requires Python 3.10 or later.
- Patching
httpxin tests. v1.0 moved the SDK's HTTP layer tohttpx2, an API-compatible fork. If your tracing or mocking tools patchhttpx, the release notes say to callhttpx2.alias_httpx()at startup. Customhttp_client,Timeoutand transport objects should now come fromhttpx2. - Hardcoding an old model ID. Retirement dates exist. The models overview lists a retirement of not sooner than September 22, 2027 for Claude Opus 5.5, so check it periodically.
- Committing the key. Use the environment variable and keep
.envfiles out of git.
Beyond the basics#
The README also documents OpenTelemetry support: the SDK reports each API call as a span when your app has registered a tracer provider. Pass open_telemetry=False to the client to turn spans off. Prompts and completions are not included unless you opt in. For tool use, the repository documents helpers (@beta_tool, a tool runner and ToolError) in its tools.md.
If you're comparing providers, the same patterns apply in Gemini API Python and How to Use the OpenAI API in Python.
Quick reference checklist#
- Python 3.10 or later, in a virtual environment.
pip install anthropicexport ANTHROPIC_API_KEY=...(the client reads it by default).Anthropic().messages.create(model=..., max_tokens=..., messages=[...])- Read the reply from
message.content[0].text. - Stream with
client.messages.stream(...)andstream.text_stream. - Catch
APIConnectionError, thenRateLimitError, thenAPIStatusError, in that order. - Resend the full history for multi-turn chats.
- Check the models overview and pricing page for current IDs and prices.
Quick answers
How do I install the Claude API Python library?
Run pip install anthropic in a virtual environment. The SDK requires Python 3.10 or later, and its source is on GitHub in the anthropics/anthropic-sdk-python repository.
How do I use my Claude API key in Python?
Set the ANTHROPIC_API_KEY environment variable and create Anthropic() with no arguments. The client reads that variable by default, or you can pass api_key= explicitly.
Where is the Claude Python API documentation?
The repository README links to the full Python SDK documentation at platform.claude.com/docs/en/api/sdks/python. The general SDK overview is at docs.claude.com/en/api/client-sdks.
Why does my old Claude Python code fail with temperature or top_p?
The v1.0 release on August 20, 2026 removed the temperature, top_p and top_k parameters from Messages methods. Remove them and check the v1 migration guide for other changes.
Did this solve it?
One tap. No account.
Sorry about that. What was missing?
from 0 readers
Saved you an hour? Send a tip.
USDC or ETH on Base, Ethereum, Arbitrum and more. Keeps the notes free and the examples running.
Network
0xc783bc5c3fa14cEC3f09A22284bC91C5AF0d59e1
On a computer? Scan the code with your phone wallet, or copy the address.
Pick the network your wallet or exchange uses. USDC works on all six; ETH on Base, Ethereum, Arbitrum and Optimism. Don't send on Tron or Solana: this address doesn't exist there. Tips are optional and unlock nothing; every guide stays free.
Discussion
Start the discussion
Next note