Skip to content
, ,

Claude API Python Tutorial: From First Call to Production

9–13 minutes
Skeptical woman in a rust blazer in a sunny park holding a card with the Claude logo, hook text: Claude API Python guide

This Claude API Python tutorial takes you from an empty folder to a working app. You will set up a key, send messages, stream replies, call tools, read images and PDFs, cache prompts, run batches and handle errors.

Every code sample uses the official anthropic Python SDK and the current model IDs from Anthropic’s docs.

TLDR

  • Install: pip install anthropic. The latest SDK is 2.6.0 (November 5, 2026) and needs Python 3.10+.
  • Key: create one in the Claude Console and set ANTHROPIC_API_KEY.
  • Models: claude-opus-5-5, claude-sonnet-5-5 and claude-haiku-5-5. All have a 1M token context window.
  • Core call: client.messages.create(model=..., max_tokens=..., messages=[...]).
  • Save money: prompt caching cuts repeat input cost, and the Batch API is 50% off.
  • Errors: the SDK retries 429, 5xx and connection errors twice by default.

Get an API Key and Install the SDK

  1. Create a Console account. Sign up at platform.claude.com and add billing or credits.
  2. Make a key. Open the API keys page in the Console and create a new key. Copy it now, because you cannot view it again.
  3. Create a project folder. Run mkdir claude-app && cd claude-app, then python3 -m venv .venv and activate it.
  4. Install the SDK. Run pip install anthropic.
  5. Set the key. On macOS or Linux run export ANTHROPIC_API_KEY="your-api-key-here". In PowerShell use $env:ANTHROPIC_API_KEY = "your-api-key-here".
  6. Test it. Save the first example below as app.py and run python app.py.

Warning: never paste your key into code you commit or into front-end JavaScript. Keep it in an environment variable or a secrets manager. Anyone with the key can spend your credits.

Which Model ID to Use

These are the current models from Anthropic’s models overview. Prices are per million tokens (MTok).

Model API ID Input / Output Best for
Claude Opus 5.5 claude-opus-5-5 $4 / $20 Anthropic’s suggested start for most work, agents and coding
Claude Sonnet 5.5 claude-sonnet-5-5 $2 / $10 Best mix of speed and intelligence
Claude Haiku 5.5 claude-haiku-5-5 From $0.10 / $0.50 High-volume, low-latency jobs like classification
Claude Fable 5.1 claude-fable-5-1 $10 / $50 Hardest reasoning and long agent runs

All four have a 1M token context window and up to 128K output tokens. Full pricing, including cache and batch rates, is on our Claude API pricing page. For the small model, see the Claude Haiku 5.5 guide.

Send Your First Message

The Messages API is the main endpoint. You send a list of messages and get back a list of content blocks.

import anthropic

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY

message = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Explain recursion in two sentences."}],
)

for block in message.content:
    if block.type == "text":
        print(block.text)

print(message.usage)  # input_tokens, output_tokens, cache fields
  • model: one of the IDs above.
  • max_tokens: the most tokens Claude may write. It is required.
  • messages: a list of user and assistant turns.
  • stop_reason: on the response, such as end_turn, max_tokens or tool_use.

For async code, use AsyncAnthropic and await client.messages.create(...). The arguments are the same.

System Prompts and Multi-Turn Chats

A system prompt sets Claude’s role and rules. It goes in the top-level system field, not inside messages.

history = []

def chat(user_text):
    history.append({"role": "user", "content": user_text})
    reply = client.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=1024,
        system="You are a friendly Python tutor. Keep answers under 120 words.",
        messages=history,
    )
    text = "".join(b.text for b in reply.content if b.type == "text")
    history.append({"role": "assistant", "content": reply.content})
    return text

print(chat("What is a list comprehension?"))
print(chat("Show me one with a condition."))

The API is stateless. You send the whole history each turn, which is why caching and message threads matter for long chats.

Streaming Responses

Streaming prints text as it is generated, so users see the reply start at once. The SDK’s messages.stream() helper is the easiest way.

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

print()
print(final.usage)

Note: the SDK raises a ValueError if a non-streaming request is expected to take longer than about 10 minutes. For big max_tokens values, always stream.

Tool Use (Function Calling)

Tool use lets Claude ask your code to run a function. You describe the tool with a JSON schema, Claude returns a tool_use block, you run it, and you send back a tool_result.

import json

tools = [{
    "name": "get_weather",
    "description": "Get the current weather for a given location.",
    "input_schema": {
        "type": "object",
        "properties": {"location": {"type": "string", "description": "City and state"}},
        "required": ["location"],
    },
}]
messages = [{"role": "user", "content": "What's the weather in San Francisco?"}]

resp = client.messages.create(model="claude-sonnet-5-5", max_tokens=1024,
                              tools=tools, messages=messages)

while resp.stop_reason == "tool_use":
    messages.append({"role": "assistant", "content": resp.content})
    results = []
    for block in resp.content:
        if block.type == "tool_use":
            output = json.dumps({"temp_c": 15, "sky": "partly cloudy"})  # your real lookup
            results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
    messages.append({"role": "user", "content": results})
    resp = client.messages.create(model="claude-sonnet-5-5", max_tokens=1024,
                                  tools=tools, messages=messages)

print(next(b.text for b in resp.content if b.type == "text"))

Let the SDK run the loop

The SDK also has a beta tool runner. Decorate a normal function with @beta_tool, pass it to client.beta.messages.tool_runner(...), and loop over the runner. It calls your tools and sends results back for you.

Anthropic also offers server tools that run on its side, such as web search, which you add with one entry in tools.

Vision: Send Images

Claude can read JPEG, PNG, GIF and WebP images. Send them as a URL, as base64 or as an uploaded file ID.

import base64

with open("chart.png", "rb") as f:
    img_b64 = base64.standard_b64encode(f.read()).decode("utf-8")

message = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=1024,
    messages=[{
        "role": "user",
        "content": [
            {"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": img_b64}},
            {"type": "text", "text": "What trend does this chart show?"},
        ],
    }],
)
print(next(b.text for b in message.content if b.type == "text"))

For a public image, swap the source for {"type": "url", "url": "https://..."}. Put the image before your question for the best results.

Read PDFs

PDFs use a document block. Claude reads both the text and the visual parts, such as charts and tables.

with open("report.pdf", "rb") as f:
    pdf_b64 = base64.standard_b64encode(f.read()).decode("utf-8")

message = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=2048,
    messages=[{
        "role": "user",
        "content": [
            {"type": "document", "source": {"type": "base64", "media_type": "application/pdf", "data": pdf_b64}},
            {"type": "text", "text": "Summarize the key findings in 5 bullets."},
        ],
    }],
)
print(next(b.text for b in message.content if b.type == "text"))

You can also pass {"type": "url", "url": ...}, or upload once with client.files.upload(...) and reuse the file_id. Pages count as tokens, so long PDFs cost more.

Prompt Caching

Caching stores the start of your prompt, such as a long system prompt or document, so later calls read it cheaply. The simplest way is automatic caching with one top-level field.

response = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=1024,
    cache_control={"type": "ephemeral"},  # automatic caching
    system=LONG_SYSTEM_PROMPT,
    messages=[{"role": "user", "content": "Answer using the policy above."}],
)
print(response.usage.cache_creation_input_tokens, response.usage.cache_read_input_tokens)
  • Lifetime: 5 minutes by default, refreshed each time the cache is used. A 1-hour TTL costs more.
  • Write cost: 1.25x the base input price for the 5-minute cache.
  • Read cost: 0.1x base input on most models, and 0.05x on Opus 5.5 and Sonnet 5.5.
  • Minimum size: 512 tokens on the current models. Shorter prompts are not cached and no error is returned.

For a deeper dive, read our prompt caching guide.

Message Threads (Beta)

Message threads keep a conversation on Anthropic’s servers between turns. After the first call, you send only the new message plus the ID of the last response.

thread = client.beta.messages.thread(
    model="claude-sonnet-5-5",
    max_tokens=1024,
    cache_control={"type": "ephemeral"},
    system="You are a support agent for Acme. Keep replies short.",
)

thread.send({"role": "user", "content": "Hi! I have a question about an order."})
second = thread.send({"role": "user", "content": "How long does standard shipping take?"})
print(next(b.text for b in second.content if b.type == "text"))

The helper uses the message-threads-2026-08-12 beta. A thread expires 5 minutes after its last request, and a continue call then returns a 404 with thread_not_found, so you replay the full chat. Our message threads guide covers the raw request format.

Batch Processing

The Message Batches API runs many requests in the background at 50% off. It suits evaluations, bulk tagging and overnight jobs.

import time

batch = client.messages.batches.create(requests=[
    {"custom_id": f"review-{i}",
     "params": {"model": "claude-haiku-5-5", "max_tokens": 200,
                "messages": [{"role": "user", "content": f"Classify the sentiment: {text}"}]}}
    for i, text in enumerate(["Love it!", "Too slow.", "It's fine."])
])

while client.messages.batches.retrieve(batch.id).processing_status != "ended":
    time.sleep(60)

for result in client.messages.batches.results(batch.id):
    if result.result.type == "succeeded":
        print(result.custom_id, next(b.text for b in result.result.message.content if b.type == "text"))
  • Size: up to 100,000 requests or 256 MB per batch.
  • Speed: most batches finish within 1 hour. A batch expires if it is not done in 24 hours.
  • Results: available for 29 days after the batch is created.

Error Handling and Retries

The SDK raises typed exceptions, so you can handle each case cleanly.

import anthropic

client = anthropic.Anthropic(max_retries=4, timeout=60.0)

try:
    msg = client.messages.create(model="claude-sonnet-5-5", max_tokens=512,
                                 messages=[{"role": "user", "content": "Hello"}])
except anthropic.RateLimitError:
    print("429: slow down and retry later")
except anthropic.APIConnectionError as e:
    print("Network problem:", e.__cause__)
except anthropic.APIStatusError as e:
    print("API error", e.status_code, e.response)
Status SDK exception Usual fix
400 BadRequestError Check the request shape and model ID
401 AuthenticationError Check ANTHROPIC_API_KEY
403 PermissionDeniedError Your key lacks access to that resource
404 NotFoundError Wrong model ID, or an expired thread
429 RateLimitError Back off and retry, or raise your tier
529 / 5xx InternalServerError Overloaded or server error: retry with backoff

By default the SDK retries connection errors, 408, 409, 429 and 5xx twice with exponential backoff, and requests time out after 10 minutes. Log message._request_id so Anthropic support can trace a failing call. More fixes are in our 429 and 529 errors guide.

Putting It Together: A Production Checklist

Once the basics work, a few habits keep a Claude app fast, cheap and reliable. Use this list before you ship.

  • Pick the smallest model that passes: test your prompts on Haiku 5.5 or Sonnet 5.5 first, and move up to Opus 5.5 only where quality needs it.
  • Stream anything user-facing: people see the first words right away instead of waiting for the whole reply.
  • Cache the stable part: put long system prompts, tool lists and documents first, then add cache_control.
  • Batch what can wait: nightly reports, data cleanup and evaluations cost half as much through the Batch API.
  • Cap output: set a sensible max_tokens for each task so one runaway reply cannot drain your budget.
  • Log the basics: store _request_id, the model, stop_reason and usage for every call.
  • Plan for failure: keep SDK retries on, and show a friendly message when a 429 or 529 lasts longer than your retries.
  • Protect the key: call the API only from your server, and use separate keys for development and production.

These habits head off the most common surprise bills and outages.

Troubleshooting: Common Problems and Fixes

  • “Could not resolve authentication method”: the key is not set in the shell that runs Python. Export it again or pass api_key= to the client.
  • 404 not_found_error on the model: the model ID has a typo. Use the exact IDs, such as claude-sonnet-5-5.
  • Reply is cut off: stop_reason is max_tokens. Raise max_tokens and stream long outputs.
  • Cache never hits: the prompt is under the minimum size, or something before the cached part changes each call, such as a timestamp.
  • Tool loop never ends: make sure every tool_use block gets a matching tool_result with the same tool_use_id.
  • Image or PDF rejected: check the media_type matches the file, and that base64 is a plain string with no data URL prefix.
  • Costs higher than expected: check usage on each response, and track spend on the Console usage pages.

Frequently Asked Questions

How do I install the Claude API for Python?

Run pip install anthropic on Python 3.10 or newer. Then set ANTHROPIC_API_KEY and create a client with anthropic.Anthropic().

Is the Claude API free?

No. You pay per token, with prices that depend on the model. A Claude.ai Pro or Max plan does not include API usage.

What is the model ID for Claude Sonnet 5.5?

It is claude-sonnet-5-5. Opus 5.5 is claude-opus-5-5 and Haiku 5.5 is claude-haiku-5-5.

How do I stream Claude’s answer in Python?

Use with client.messages.stream(...) as stream: and loop over stream.text_stream. Call stream.get_final_message() at the end for usage data.

Can Claude read PDFs through the API?

Yes. Send a document block with a base64 PDF, a URL or an uploaded file ID. Claude reads text, tables and charts.

How much does batch processing save?

The Message Batches API costs 50% less than standard calls. Most batches finish within an hour, with a 24-hour limit.

Does the Python SDK retry failed requests?

Yes. It retries connection errors, 408, 409, 429 and 5xx errors twice by default. Change this with max_retries.

Sources

Free weekly email

Get new AI guides every week

One short weekly email with our newest local AI, ComfyUI and AI tool guides. No spam, no pop-ups, just the good stuff.

Free. Unsubscribe anytime in one click. We only use your email to send this newsletter. See our privacy policy.

Skeptical woman in a rust blazer in a sunny park holding a card with the Claude logo, hook text: Claude API Python guide