CoreMesh

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.

Claude API Python: Send, Stream and Handle Errors in Code

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:

bash
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:

bash
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:

python
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:

python
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:

python
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:

python
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:

python
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:

python
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 Exception and 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.

python
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 on messages.create. There's no default to lean on.
  • Treating message.content as a string. It's a list of blocks. Index into it and read .text.
  • Passing temperature, top_p or top_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 await on the async client. With v1.0, .with_raw_response results on the async client need await response.parse().
  • Old Python. v1.0 requires Python 3.10 or later.
  • Patching httpx in tests. v1.0 moved the SDK's HTTP layer to httpx2, an API-compatible fork. If your tracing or mocking tools patch httpx, the release notes say to call httpx2.alias_httpx() at startup. Custom http_client, Timeout and transport objects should now come from httpx2.
  • 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 .env files 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 anthropic
  • export 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(...) and stream.text_stream.
  • Catch APIConnectionError, then RateLimitError, then APIStatusError, 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.

Saved you an hour? Send a tip.

USDC or ETH on Base, Ethereum, Arbitrum and more. Keeps the notes free and the examples running.

Base

Network

0xc783bc5c3fa14cEC3f09A22284bC91C5AF0d59e1
Open in wallet · 5 USDC

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

Indra Wijaya

Builds and runs AI agents, bots and content pipelines, and writes down what works and what broke.

About · Email · RSS

Keep reading

New notes, no spam.

One post at a time, only when there's something worth reading. Follow in any feed reader.

Subscribe via RSS