Claude AI for Developers: A Practical Guide to the Anthropic API
Claude is Anthropic's family of large language models, designed with safety and reliability as first-class constraints. If you've been building with OpenAI, Claude will feel familiar — but there are meaningful differences in how it handles context, tool use, and long-form reasoning that are worth understanding before you ship something to production.
This post covers the practical side: setting up the SDK, prompt design, tool use, caching, and patterns that actually hold up under real workloads.
Model Family Overview
| Model | Context Window | Best For |
|---|---|---|
| Opus 4.7 | 200k tokens | Complex reasoning, agentic workflows |
| Sonnet 4.6 | 200k tokens | Balanced cost/performance, most use cases |
| Haiku 4.5 | 200k tokens | High-throughput, latency-sensitive tasks |
Start with Sonnet for most things. Move to Opus for tasks that need multi-step reasoning or you're getting unsatisfying results. Use Haiku when you need to run inference at scale and latency matters.
Getting Started
pip install anthropic
# or
pnpm add @anthropic-ai/sdkBasic request in Python:
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[
{"role": "user", "content": "Explain prompt caching in two sentences."}
]
)
print(message.content[0].text)And in TypeScript:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [
{ role: "user", content: "Explain prompt caching in two sentences." },
],
});
console.log(message.content[0].text);System Prompts That Actually Work
Claude responds well to direct, specific system prompts. Avoid vague instructions like "be helpful." Instead, define the persona, constraints, and output format explicitly.
system = """You are a senior TypeScript engineer.
- Answer questions about TypeScript, React, and Node.js
- Provide working code examples
- If unsure, say so instead of guessing
- Format code in markdown code blocks with language tags
- Keep explanations concise — developers prefer examples over prose"""Claude follows multi-point instructions reliably. You can also use XML tags to structure complex prompts:
system = """
<persona>
You are a code reviewer focused on correctness and security.
</persona>
<rules>
- Flag any SQL injection, XSS, or SSRF vulnerabilities
- Note missing error handling
- Suggest specific fixes, not just "handle this error"
</rules>
<output_format>
Return a JSON array of findings:
[{"severity": "high|medium|low", "line": N, "issue": "...", "fix": "..."}]
</output_format>
"""Tool Use (Function Calling)
Tool use lets Claude call functions you define. The model decides when a tool is needed, you execute it, and return the result.
import anthropic
import json
client = anthropic.Anthropic()
tools = [
{
"name": "get_weather",
"description": "Get current weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
"units": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
]
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "What's the weather in Lima?"}]
)
# Claude returns a tool_use block
if response.stop_reason == "tool_use":
tool_call = next(b for b in response.content if b.type == "tool_use")
tool_input = tool_call.input
# Execute your function
result = get_weather(tool_input["city"], tool_input.get("units", "celsius"))
# Send result back
followup = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "What's the weather in Lima?"},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_call.id,
"content": json.dumps(result)
}]
}
]
)
print(followup.content[0].text)Prompt Caching
Prompt caching is Claude's most impactful cost optimization. When you mark part of your prompt with cache_control, Anthropic caches that prefix and reuses it across requests — reducing cost by up to 90% and latency by ~85% on cache hits.
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=[
{
"type": "text",
"text": LARGE_SYSTEM_PROMPT, # e.g., 10k token context doc
"cache_control": {"type": "ephemeral"}
}
],
messages=[
{"role": "user", "content": user_question}
]
)Cache TTL is 5 minutes by default. Extend it by including the cached block in each request within that window.
When to use it:
- Long system prompts (docs, code context, personas)
- RAG pipelines where context is reused across queries
- Multi-turn conversations with a fixed instruction block
Streaming
For UIs, streaming dramatically improves perceived responsiveness:
const stream = await client.messages.stream({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
});
for await (const chunk of stream) {
if (
chunk.type === "content_block_delta" &&
chunk.delta.type === "text_delta"
) {
process.stdout.write(chunk.delta.text);
}
}
const final = await stream.finalMessage();
console.log(
"Total tokens:",
final.usage.input_tokens + final.usage.output_tokens,
);Extended Thinking
For hard problems — logic puzzles, math, complex code generation — enable extended thinking:
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=16000,
thinking={
"type": "enabled",
"budget_tokens": 10000 # how much Claude can "think"
},
messages=[{"role": "user", "content": "Design a distributed rate limiter..."}]
)
for block in response.content:
if block.type == "thinking":
print("Reasoning:", block.thinking)
elif block.type == "text":
print("Answer:", block.text)Thinking tokens are billed but not cached. Reserve this for tasks where quality matters more than cost.
Multi-turn Conversations
Claude has no built-in memory — you maintain the conversation history yourself:
history = []
def chat(user_message: str) -> str:
history.append({"role": "user", "content": user_message})
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system="You are a helpful coding assistant.",
messages=history
)
assistant_message = response.content[0].text
history.append({"role": "assistant", "content": assistant_message})
return assistant_message
chat("How do I debounce a function in TypeScript?")
chat("Now show me how to test that with Jest.")For production, persist history in Redis or Postgres and load it per session.
Rate Limits and Error Handling
from anthropic import RateLimitError, APIStatusError
import time
def call_with_retry(messages, retries=3):
for attempt in range(retries):
try:
return client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=messages
)
except RateLimitError:
wait = 2 ** attempt
print(f"Rate limited, waiting {wait}s...")
time.sleep(wait)
except APIStatusError as e:
if e.status_code >= 500:
time.sleep(2 ** attempt)
else:
raise
raise Exception("Max retries exceeded")Key Takeaways
- Use Sonnet for most workloads, Opus for complex reasoning, Haiku for volume
- Prompt caching is the single biggest lever for cost reduction on repeated context
- Tool use is reliable and well-suited for agentic workflows
- Extended thinking is worth the cost for hard, single-shot tasks
- Maintain conversation history yourself — Claude has no session state
The Anthropic SDK is well-documented and actively maintained. Start with the official docs for the latest model capabilities.