This Claude API tutorial takes you from an empty folder to a working Python integration — install, authentication, your first call, streaming, and production-grade error handling — in about twenty minutes. Every code block below runs as-is against the current API (July 2026), using current model IDs and current request shapes. No stale temperature parameters, no deprecated thinking budgets — just the patterns I use in code that’s actually deployed.
TL;DR:
pip install anthropic, exportANTHROPIC_API_KEY, then callclient.messages.create()with a model ID,max_tokens, and a messages list. Read text frommessage.content, stream anything long withclient.messages.stream(), and catch the SDK’s typed exceptions rather than string-matching errors. Start onclaude-sonnet-5.
Prerequisites
You need Python 3.8+ and an Anthropic API key. If you don’t have a key yet, the Claude AI API key guide covers Console signup, billing, and key generation in five minutes — come back here once echo $ANTHROPIC_API_KEY prints something. TypeScript developer? The flow is identical with npm install @anthropic-ai/sdk; I’ll note the differences at the end.
Step 1: Install the SDK and set your key
python -m venv .venv && source .venv/bin/activate
pip install anthropic
export ANTHROPIC_API_KEY="sk-ant-your-key-here"
The SDK reads ANTHROPIC_API_KEY automatically — you should never pass the key in code. For projects, put it in a .env file (git-ignored) and load it with python-dotenv.
Step 2: Your first Claude API call
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[
{"role": "user", "content": "Write a haiku about version control."}
],
)
print(message.content[0].text)
print(f"Tokens: {message.usage.input_tokens} in, {message.usage.output_tokens} out")
Three parameters are required: model (use claude-sonnet-5 as your default — the tiers are compared in the complete Claude API guide), max_tokens (a hard cap on output length), and messages (the conversation so far, alternating user and assistant roles). Run it and you’ll get a haiku plus exact token counts — get in the habit of logging that usage object now, because it’s how you’ll track spend later.
Step 3: Understand the response object
The response’s content field is a list of blocks, not a string. Today your requests return a single text block, but the moment you enable tool use or adaptive thinking, other block types appear first — so index-zero shortcuts eventually break. The robust pattern costs two lines:
text = "".join(block.text for block in message.content if block.type == "text")
if message.stop_reason == "max_tokens":
print("Warning: response was truncated — raise max_tokens")
Also check stop_reason: end_turn means a natural finish, max_tokens means truncation, and tool_use means Claude is asking you to run a tool.
Step 4: Add a system prompt and conversation history
The system prompt sets persistent behavior and lives in its own system parameter, not in messages. Multi-turn conversation is just the message list growing — the API is stateless, so you resend history each turn:
history = []
def chat(user_input: str) -> str:
history.append({"role": "user", "content": user_input})
message = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system="You are a concise senior Python reviewer. Answer in under 120 words.",
messages=history,
)
reply = message.content[0].text
history.append({"role": "assistant", "content": reply})
return reply
print(chat("What does the walrus operator do?"))
print(chat("Show me a one-line example.")) # remembers the previous turn
Step 5: Stream responses like a real app
Waiting three seconds for a blank screen kills UX, and non-streaming requests with large max_tokens can hit HTTP timeouts. Streaming solves both — tokens render as they generate:
with client.messages.stream(
model="claude-sonnet-5",
max_tokens=2048,
messages=[{"role": "user", "content": "Explain asyncio in three paragraphs."}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
final = stream.get_final_message() # complete message object, with usage
The context manager handles the server-sent events plumbing; get_final_message() gives you the assembled response afterward for logging. My rule: anything above ~4K expected output tokens gets streamed, no exceptions.
Step 6: Handle errors properly
The SDK raises a typed exception per HTTP status and automatically retries rate limits and server errors twice with backoff. Catch specific classes, most specific first:
import anthropic
try:
message = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
except anthropic.AuthenticationError:
raise SystemExit("Bad or missing API key — check ANTHROPIC_API_KEY")
except anthropic.RateLimitError:
print("Rate limited after SDK retries — queue and retry later")
except anthropic.APIStatusError as e:
print(f"API error {e.status_code}: {e.message}")
except anthropic.APIConnectionError:
print("Network problem — check connectivity and retry")
A 400 at this stage usually means a request-shape mistake — the classic is copying temperature=0.7 from an old tutorial, which current models (Opus 4.8, Sonnet 5) reject outright. If you’re hitting 429s regularly, that’s a tier question, covered in the Claude API rate limits guide.
Step 7: Turn on adaptive thinking for hard problems
Everything so far returns fast, direct answers. For genuinely hard problems — debugging a race condition, multi-step math, planning a refactor — you can let Claude reason before it answers. On current models this is adaptive: you don’t set a token budget, Claude decides when and how much to think, and you tune overall depth with the effort parameter:
message = client.messages.create(
model="claude-opus-4-8",
max_tokens=16000,
thinking={"type": "adaptive"},
output_config={"effort": "high"}, # low | medium | high | xhigh | max
messages=[{"role": "user", "content": "Why does this test fail intermittently? ..."}],
)
for block in message.content:
if block.type == "text":
print(block.text)
Two things to notice. The response may now contain thinking blocks before the text block — which is exactly why Step 3 taught you to filter by block type instead of grabbing index zero. And thinking tokens count against max_tokens and your bill, so reserve high effort for requests that earn it; low effort on Sonnet 5 is plenty for routine work.
What will this Claude API tutorial cost to run?
Almost nothing — and it’s worth understanding why. Every example on this page together sends well under 10,000 input tokens and generates a few thousand output tokens. On Sonnet 5’s introductory pricing ($2 per million input tokens, $10 per million output), the whole session costs a fraction of a cent. That’s the practical upside of logging message.usage from your very first call: you’ll know your real per-request cost before you ever ship, instead of estimating it from an invoice later.
Bonus: the same tutorial in TypeScript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic(); // reads ANTHROPIC_API_KEY
const message = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Write a haiku about version control." }],
});
const block = message.content[0];
if (block.type === "text") console.log(block.text);
Same request shape, same response structure, same error classes under Anthropic.*. The streaming helper is client.messages.stream() with a finalMessage() method. For a full JavaScript walkthrough — Express endpoints, streaming to the browser, and production patterns — see the Claude API Node.js guide.
Common mistakes in a first Claude API integration
- Copying pre-2026 sample code.
temperature,top_p, and fixed thinking budgets (budget_tokens) all return 400s on current models. If you want deeper reasoning, usethinking={"type": "adaptive"}instead. - Hardcoding the API key. It works until the commit. Environment variable from minute one.
- Treating
contentas a string. It’s a list of typed blocks — filter fortype == "text". - Forgetting the API is stateless. Claude doesn’t remember your last request; you must resend the conversation history every turn.
- Setting
max_tokenstoo low. Truncated JSON and half-finished code blocks are usually just a stingy cap. 1024 is a sane floor for chat; go much higher (with streaming) for generation tasks. - No usage logging. Wire
message.usageinto your logs today; your future self reading the invoice will thank you. Rates are broken down in the Claude API pricing guide.
Next steps
You now have the complete request-response loop: authenticated calls, system prompts, multi-turn history, streaming, and typed error handling. From here, go deeper with Claude API with Python for tool use, vision, and async patterns, or explore practical workflows in Claude for coding. The full endpoint reference lives in Anthropic’s official documentation, and everything else on this site’s API track is indexed at the API hub.
FAQ
What do I need before starting this Claude API tutorial?
Python 3.8 or newer, an Anthropic Console account at platform.claude.com, and an API key stored in the ANTHROPIC_API_KEY environment variable. The SDK installs with pip install anthropic, and the whole setup takes about five minutes.
Which model should a beginner use with the Claude API?
Start with claude-sonnet-5. It offers near-Opus quality at 60% of Opus output price (40% at introductory rates), with introductory pricing of $2 per million input tokens through August 31, 2026. Switch specific routes to claude-haiku-4-5 for cheap high-volume tasks or claude-opus-4-8 for the hardest reasoning.
Why does my old sample code return a 400 error?
Most likely it sends temperature, top_p, or a thinking budget_tokens value. Those parameters are removed on current models like Opus 4.8 and Sonnet 5 and the API rejects them. Delete the sampling parameters and use adaptive thinking instead of fixed budgets.
Does the Claude API remember previous messages?
No, the Messages API is stateless. Your application keeps the conversation history and resends it in the messages array on every call. Prompt caching makes resending long histories cheap, since cached prefixes are billed at a tenth of the normal input rate.
When should I use streaming instead of a normal request?
Stream whenever a user is watching the output or whenever you expect long responses. Streaming renders tokens as they generate and avoids HTTP timeouts that can affect non-streaming requests with large max_tokens values. The Python SDK handles it with client.messages.stream().
