Claude API with Python — Complete Examples

Calling the Claude API from Python takes three lines: install the anthropic package, set your API key, and call client.messages.create(). This claude api python guide walks through everything I actually use in production — client setup, streaming, tool use, structured outputs, and error handling — with code that works against the current 2026 API. Every example below runs as-is on the latest SDK; no deprecated parameters, no outdated model IDs.

TL;DR: Install with pip install anthropic, export ANTHROPIC_API_KEY, and call client.messages.create(). Use client.messages.stream() for anything user-facing. Current models don’t accept temperature — steer with prompts and the effort parameter instead. Catch typed exceptions like anthropic.RateLimitError rather than parsing error strings. The SDK retries 429s and 5xx errors automatically.

Setting Up the Claude API Python SDK

The official SDK requires Python 3.8+ and installs from PyPI:

pip install anthropic

You’ll need an API key from the Claude Console — I covered the signup and billing steps in the Claude API key guide, so here I’ll assume you have one. Export it as an environment variable rather than hardcoding it:

export ANTHROPIC_API_KEY="sk-ant-..."

The client picks the key up automatically, so initialization is a one-liner:

import anthropic

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY from the environment

There’s also an anthropic.AsyncAnthropic() client with an identical surface if your app runs on asyncio — every method below has an await-able twin.

Your First Messages Request

Everything in the Claude API goes through the Messages endpoint. Three parameters are required: model, max_tokens, and messages:

message = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    system="You are a concise technical assistant.",
    messages=[
        {"role": "user", "content": "Explain Python's GIL in two sentences."}
    ],
)
print(message.content[0].text)
print(message.usage.input_tokens, message.usage.output_tokens)

The response object carries a content list (usually one text block), a stop_reason (end_turn, max_tokens, tool_use, or refusal), and a usage object you’ll want for cost tracking. Which model ID to pass depends on your budget and workload:

ModelAPI IDPrice (in/out per MTok)Best for
Claude Fable 5claude-fable-5$10 / $50Hardest reasoning, long agent runs
Claude Opus 4.8claude-opus-4-8$5 / $25Demanding coding and analysis
Claude Sonnet 5claude-sonnet-5$3 / $15 (intro $2 / $10 to Aug 31, 2026)Default speed/quality balance
Claude Haiku 4.5claude-haiku-4-5$1 / $5Classification, high-volume tasks

One 2026 gotcha worth flagging early: temperature, top_p, and top_k are removed on Opus 4.7+, Sonnet 5, and Fable 5 — sending them returns a 400. Steer style through the system prompt, and control reasoning depth with output_config={"effort": "low"} through "max". If you want Claude to think before answering, pass thinking={"type": "adaptive"} — the old fixed budget_tokens config is gone on current models.

Streaming Responses in Python

For anything a human watches, stream. The SDK’s context-manager helper is much nicer than raw server-sent events:

with client.messages.stream(
    model="claude-sonnet-5",
    max_tokens=2048,
    messages=[{"role": "user", "content": "Write a haiku about type hints."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

    final = stream.get_final_message()  # complete Message object with usage

stream.text_stream yields plain text deltas as they arrive; get_final_message() hands you the assembled response afterwards, so you get live output and the full object without doing bookkeeping yourself. Streaming isn’t only cosmetic: for large max_tokens values (above roughly 16K), non-streaming requests risk HTTP timeouts, so the SDK expects you to stream long generations. Current models support up to 128K output tokens when streamed.

Tool Use: Let Claude Call Your Functions

Tool use is where the API stops being a chatbot and starts being an application platform. You describe your functions with JSON Schema; Claude replies with a tool_use block when it wants one called; you execute it and send the result back. Here’s the full loop, which I use as the skeleton for most agents I build:

import json

tools = [{
    "name": "get_weather",
    "description": "Get the current weather for a city. Call this whenever the user asks about weather conditions.",
    "input_schema": {
        "type": "object",
        "properties": {
            "city": {"type": "string", "description": "City name, e.g. Berlin"}
        },
        "required": ["city"],
    },
}]

def get_weather(city: str) -> str:
    return json.dumps({"city": city, "temp_c": 21, "conditions": "clear"})

messages = [{"role": "user", "content": "What's the weather in Berlin?"}]

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    tools=tools,
    messages=messages,
)

while response.stop_reason == "tool_use":
    tool_results = []
    for block in response.content:
        if block.type == "tool_use":
            result = get_weather(**block.input)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": result,
            })
    messages.append({"role": "assistant", "content": response.content})
    messages.append({"role": "user", "content": tool_results})
    response = client.messages.create(
        model="claude-opus-4-8",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )

print(response.content[0].text)

Two details matter. First, always append the entire response.content back as the assistant turn — dropping the tool_use blocks breaks the conversation. Second, if Claude requests several tools in one turn, return all the tool_result blocks in a single user message. If you’d rather not hand-write this loop, the SDK ships a beta tool runner (client.beta.messages.tool_runner with the @beta_tool decorator) that drives it for you. For broader patterns around coding with Claude, my Claude for coding workflow guide covers how tool-driven agents fit into a real development setup.

Structured Outputs: Guaranteed JSON

Before structured outputs existed, every Python developer wrote a “please respond in JSON” prompt and a try/except around json.loads(). You no longer need either. Pass a schema via output_config and the API constrains the response to match it:

message = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    messages=[{
        "role": "user",
        "content": "Extract: 'Ada Lovelace, [email protected], Enterprise plan'",
    }],
    output_config={
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "email": {"type": "string"},
                    "plan": {"type": "string"},
                },
                "required": ["name", "email", "plan"],
                "additionalProperties": False,
            },
        }
    },
)

data = json.loads(message.content[0].text)  # always valid against the schema

Every object in the schema needs "additionalProperties": False, and recursive schemas aren’t supported. If you work with Pydantic, client.messages.parse() accepts a model class directly and returns a validated instance — my preferred route for extraction pipelines. Note the older top-level output_format parameter is deprecated; use output_config.

Error Handling with Typed Exceptions

The SDK raises a specific exception class per HTTP status, so never string-match error messages. Catch from most specific to least:

import anthropic

try:
    message = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hello"}],
    )
except anthropic.AuthenticationError:
    print("Bad or missing API key (401)")
except anthropic.NotFoundError:
    print("Check the model ID (404)")
except anthropic.RateLimitError as e:
    wait = e.response.headers.get("retry-after", "60")
    print(f"Rate limited (429) — retry after {wait}s")
except anthropic.APIStatusError as e:
    print(f"API error {e.status_code}: {e.message}")
except anthropic.APIConnectionError:
    print("Network failure before a response arrived")

The main classes: BadRequestError (400), AuthenticationError (401), PermissionDeniedError (403), NotFoundError (404), RateLimitError (429), and InternalServerError (500+), all inheriting from APIStatusError. APIConnectionError covers network failures. The client already retries 429s, 5xx errors, and connection failures twice with exponential backoff — tune it with anthropic.Anthropic(max_retries=5). If you’re hitting 429s regularly, understand your quota first: I broke down the tier system in the Claude API rate limits guide.

Two Cheap Wins: Prompt Caching and Token Counting

Before you ship, add two lines of hygiene. First, prompt caching: if every request re-sends the same long system prompt or tool definitions, mark the end of the stable prefix with a cache_control breakpoint and subsequent requests bill those tokens at roughly a tenth of the input price:

message = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    system=[{
        "type": "text",
        "text": LONG_SYSTEM_PROMPT,
        "cache_control": {"type": "ephemeral"},
    }],
    messages=[{"role": "user", "content": user_question}],
)
print(message.usage.cache_read_input_tokens)  # nonzero on the second call

Second, measure prompts with client.messages.count_tokens(model=..., messages=...) before you commit to a design — it’s free, model-accurate, and beats any character-count heuristic. Between the two, most of my Python projects cut their input bill dramatically before any clever optimization; the numbers behind that claim are in the pricing breakdown.

Common Mistakes I See in Python Integrations

  • Sending temperature to current models. Code copied from 2024 tutorials 400s on Opus 4.7+, Sonnet 5, and Fable 5. Delete the sampling parameters.
  • Using budget_tokens for thinking. Replaced by thinking={"type": "adaptive"} on current models; the old shape is rejected.
  • Reading content[0].text blindly. When thinking or tool use is active, the first block may not be text. Iterate and check block.type.
  • Counting tokens with tiktoken. That’s OpenAI’s tokenizer and undercounts Claude tokens. Use client.messages.count_tokens().
  • Not streaming long outputs. Non-streaming calls with max_tokens above ~16K risk HTTP timeouts.
  • Hardcoding the API key. Keys end up in git history. Use environment variables or a secrets manager.

FAQ

How do I install the Claude API Python SDK?

Run pip install anthropic. The package requires Python 3.8 or newer and includes both a synchronous client (anthropic.Anthropic) and an async client (anthropic.AsyncAnthropic). Set your key in the ANTHROPIC_API_KEY environment variable and the client picks it up automatically.

Is the Claude API free to use with Python?

No. The API is pay-as-you-go and billed separately from Claude Pro subscriptions. Pricing ranges from $1 per million input tokens on Haiku 4.5 to $10 per million on Fable 5. New Console accounts typically get a small credit grant to experiment with.

Which Claude model should I use for Python projects?

Start with claude-sonnet-5 — it balances speed, quality, and cost, and has an introductory price of $2 per million input tokens through August 31, 2026. Move up to claude-opus-4-8 for demanding coding or reasoning work, and down to claude-haiku-4-5 for high-volume classification.

Why does my temperature parameter return a 400 error?

The temperature, top_p, and top_k parameters were removed on Opus 4.7 and later, Sonnet 5, and Fable 5. Requests that include them are rejected. Steer output style through the system prompt and control reasoning depth with the effort setting instead.

How does the Python SDK handle rate limits?

When you exceed a limit, the API returns a 429 with a retry-after header and the SDK raises anthropic.RateLimitError. The client automatically retries 429s and 5xx errors twice with exponential backoff; you can raise this with the max_retries client option.

Next Steps

You now have the full Python toolkit: setup, messages, streaming, tools, structured outputs, and errors. From here, browse the rest of our Claude API developer hub for pricing and quota deep-dives, run your budget through the Claude API cost calculator before you scale traffic, or — if your stack is JavaScript — jump to the companion Claude API Node.js guide, which mirrors every example here in TypeScript.

ClaudeAI.Guide Editorial Team

ClaudeAI.Guide Editorial Team

Independent editorial team behind ClaudeAI.Guide — an unofficial, third-party reference that is not affiliated with, endorsed by, or sponsored by Anthropic, PBC. We cover Anthropic’s Claude AI assistant from a practitioner’s perspective: hands-on tutorials, practical prompts, model comparisons (Claude Opus, Sonnet, Haiku), API walkthroughs, and honest reviews grounded in our own daily use. Everything we publish is tested in real workflows and verified at the time of writing, with no affiliate-driven hype. “Claude” and “Anthropic” are trademarks of Anthropic, PBC, used here for descriptive, nominative fair-use purposes only.

Articles: 72