The Claude API Python tutorial most developers need in 2026 is shorter than you’d expect — your first working API call is 10 lines of code. The part that takes longer is understanding the response object, getting system prompts right, handling the streaming correctly, and avoiding the three parameter changes in Claude Sonnet 5 that silently break code written for Sonnet 4.x.

This guide covers all of it. By the end you’ll have working code for every major Claude API pattern: basic calls, multi-turn conversations, streaming, tool use, and production error handling with cost tracking. Everything runs against claude-sonnet-5 — the current production default at $2 per million input tokens through August 31, 2026.
Setup: API Key and SDK in 2 Minutes
Step 1 — Get your API key
Go to console.anthropic.com, create an account, and generate an API key under API Keys. New accounts include free credits. Copy the key once — the console doesn’t show it again.
Step 2 — Install the SDK
pip install anthropic python-dotenv
Step 3 — Store the key safely
# .env file in your project root — never commit this to git
ANTHROPIC_API_KEY=sk-ant-your-key-here
# .gitignore
.env
Your First Claude API Python Call
import os
import anthropic
from dotenv import load_dotenv
load_dotenv()
client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
message = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[
{"role": "user", "content": "Explain what an API is in two sentences."}
]
)
print(message.content[0].text)
Run this and you’ll see Claude’s response in your terminal. That’s the complete minimal Claude API Python call. Now let’s understand what came back.
Reading the response object correctly
print(message.model) # "claude-sonnet-5"
print(message.stop_reason) # "end_turn" = completed normally
print(message.usage.input_tokens) # tokens you sent (= your cost)
print(message.usage.output_tokens) # tokens in the response (= your cost)
print(message.content[0].text) # the actual response text
print(message.content[0].type) # "text" for standard responses
Two fields matter most: stop_reason tells you why Claude stopped — end_turn means it finished normally, max_tokens means it was cut off and you need a higher limit. usage is how you track costs — essential before you put anything into production.
System Prompts: The Most Important Parameter
A system prompt sets Claude’s role, constraints, and output format for the entire conversation. It’s the highest-leverage single parameter in any Claude API Python integration — the difference between a generic response and a response that matches your product exactly.
message = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system="""You are a senior Python engineer doing code review.
When reviewing code:
- Start with a one-line verdict: APPROVE, REQUEST_CHANGES, or NEEDS_DISCUSSION
- List specific issues with line numbers
- End with the single highest-priority fix
Keep your review under 200 words.""",
messages=[
{"role": "user", "content": "Review this code:\n\ndef get_user(id):\n return db.query(f'SELECT * FROM users WHERE id={id}')"}
]
)
print(message.content[0].text)
Three system prompt patterns that consistently produce better Claude API responses:
- Define a role with context: “You are a [role] with [specific expertise]” gives Claude a consistent perspective to respond from.
- Specify the output format: Telling Claude exactly what structure you expect (bullet points, JSON, numbered steps) makes the output far easier to parse.
- Set explicit constraints: Word limits, what to include, what to exclude. Claude follows precise constraints reliably.
Multi-Turn Conversations
Claude doesn’t have memory between API calls. To maintain a conversation, you pass the full history with every request. The SDK doesn’t manage this for you — you build the list yourself.
import os
import anthropic
from dotenv import load_dotenv
load_dotenv()
client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
def chat():
"""A simple multi-turn CLI chatbot using the Claude API."""
history = []
print("Claude — type 'quit' to exit\n")
while True:
user_input = input("You: ").strip()
if user_input.lower() in ("quit", "exit", "q"):
break
if not user_input:
continue
# Add user message to history
history.append({"role": "user", "content": user_input})
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system="You are a helpful assistant. Be concise.",
messages=history
)
assistant_text = response.content[0].text
# Add Claude's response to history for the next turn
history.append({"role": "assistant", "content": assistant_text})
print(f"Claude: {assistant_text}\n")
# Track token usage per turn
print(f"[Tokens: in={response.usage.input_tokens} out={response.usage.output_tokens}]")
if __name__ == "__main__":
chat()
The pattern is always the same: append the user message, make the API call, append Claude’s response to the same list, repeat. The context grows with each turn — which is why token tracking per turn matters. A 10-turn conversation with 500 tokens per response is using 5,000 output tokens before you’ve done anything interesting.
Streaming: Token-by-Token Output
Streaming makes long responses feel fast by printing each token as it’s generated rather than waiting for the full response. For any user-facing application with responses longer than a sentence, streaming is the default choice.
import os
import anthropic
from dotenv import load_dotenv
load_dotenv()
client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
def stream_response(prompt: str) -> str:
"""Stream Claude's response token by token, return the full text."""
full_response = ""
with client.messages.stream(
model="claude-sonnet-5",
max_tokens=2048,
messages=[{"role": "user", "content": prompt}]
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
full_response += text
print() # newline after streaming completes
return full_response
# Usage
result = stream_response(
"Write a step-by-step explanation of how HTTPS works."
)
print(f"\n[Total characters: {len(result)}]")
The stream.text_stream iterator yields only the text tokens, automatically filtering out thinking blocks if extended thinking is active. For non-streaming use cases (batch processing, background jobs), omit the stream context manager and use client.messages.create() as shown earlier.
The Three Sonnet 5 Breaking Changes From Sonnet 4.x
If you’ve used the Claude API with Sonnet 4.6 or earlier and are migrating to Sonnet 5, three parameter changes produce silent failures or 400 errors:
- Do not pass
temperature. Sonnet 5 uses an effort-level system instead of temperature. Passingtemperatureto a Sonnet 5 call returns a 400 error. Remove it entirely or replace it with the effort toggle (see below). - Effort toggle, not temperature: To control reasoning intensity, pass the thinking parameter with a budget:
thinking={"type": "enabled", "budget_tokens": 5000}for extended reasoning. For most tasks, omit this entirely — the default reasoning level is appropriate. - Model string: Use
claude-sonnet-5. Theclaude-sonnet-4-6string still works for that model — but if you’re migrating, the strings are not interchangeable aliases.
# WRONG — returns 400 error with claude-sonnet-5
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
temperature=0.7, # ← causes 400 error
messages=[{"role": "user", "content": "Hello"}]
)
# CORRECT — omit temperature entirely
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}]
)
# CORRECT — use thinking param for high-reasoning tasks
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=8000,
thinking={"type": "enabled", "budget_tokens": 5000},
messages=[{"role": "user", "content": "Solve this step by step: ..."}]
)
Production Error Handling and Cost Tracking
Two additions that every Claude API Python integration needs before it sees production traffic: structured error handling and per-call cost tracking.
import os
import anthropic
from dotenv import load_dotenv
from dataclasses import dataclass
load_dotenv()
client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
# Sonnet 5 pricing (introductory, valid through August 31, 2026)
PRICE_INPUT_PER_MTK = 2.0 # $2.00 per million input tokens
PRICE_OUTPUT_PER_MTK = 10.0 # $10.00 per million output tokens
@dataclass
class APIResult:
text: str
input_tokens: int
output_tokens: int
cost_usd: float
stop_reason: str
def call_claude(
prompt: str,
system: str = "",
max_tokens: int = 1024
) -> APIResult | None:
"""
Call Claude API with full error handling and cost tracking.
Returns None on failure — always check the return value.
"""
try:
kwargs = {
"model": "claude-sonnet-5",
"max_tokens": max_tokens,
"messages": [{"role": "user", "content": prompt}]
}
if system:
kwargs["system"] = system
response = client.messages.create(**kwargs)
# Extract text safely
text = ""
for block in response.content:
if hasattr(block, "text"):
text += block.text
# Calculate cost
input_cost = (response.usage.input_tokens / 1_000_000) * PRICE_INPUT_PER_MTK
output_cost = (response.usage.output_tokens / 1_000_000) * PRICE_OUTPUT_PER_MTK
total_cost = input_cost + output_cost
return APIResult(
text=text,
input_tokens=response.usage.input_tokens,
output_tokens=response.usage.output_tokens,
cost_usd=total_cost,
stop_reason=response.stop_reason
)
except anthropic.AuthenticationError:
print("ERROR: Invalid API key. Check your ANTHROPIC_API_KEY.")
return None
except anthropic.RateLimitError:
print("ERROR: Rate limit exceeded. Add retry logic or reduce request rate.")
return None
except anthropic.APIStatusError as e:
print(f"ERROR: API error {e.status_code}: {e.message}")
return None
except Exception as e:
print(f"ERROR: Unexpected error: {e}")
return None
# Usage
result = call_claude(
prompt="What are the top 3 Python best practices for API integrations?",
system="You are a senior Python developer. Be specific and concise."
)
if result:
print(result.text)
print(f"\n--- Usage ---")
print(f"Tokens: {result.input_tokens} in / {result.output_tokens} out")
print(f"Cost: ${result.cost_usd:.6f}")
print(f"Stop reason: {result.stop_reason}")
else:
print("Call failed — check the error above.")
The APIResult dataclass makes it easy to log costs to a database, aggregate them per user or per session, and alert when a single call exceeds a cost threshold. Running this in production without cost tracking is how teams discover they’ve spent $200 on a single batch job.
For the complete Claude API reference, authentication options, and SDK changelog, see Anthropic’s official API documentation.
The Builder’s Takeaway
The Claude API Python integration pattern is: install the SDK, initialize the client with your key, build a messages list, call client.messages.create(), and read response.content[0].text. Everything else — system prompts, multi-turn history, streaming, error handling, cost tracking — layers onto that foundation. The three Sonnet 5 breaking changes (no temperature, effort toggle, new model string) are the only migration friction from 4.x. Once past those, Sonnet 5’s 1M context window, adaptive reasoning, and introductory pricing make it the right default for almost any Python application adding Claude in 2026. The next step after this guide is adding tool use — which turns the Claude API from a text generator into an agent that can take actions in your codebase and beyond.
Continue in This Series
- How to Build an AI Agent With Python — the next step: add tools, memory, and a loop to what you built here
- LangGraph vs CrewAI — when you’re ready for a framework above the raw API calls
- Sonnet 5 Migration — the complete breaking change list from Sonnet 4.6 to Sonnet 5
- Model Fallback Routing — never hardcode
claude-sonnet-5: the multi-provider fallback chain for production - How to Deploy AI Agents — the complete production checklist once your Claude API integration is working
This post is part of The Agentic Protocol’s Work series — the connective infrastructure layer beneath every autonomous pipeline. See also: How to Build an AI Agent With Python.