authentication and setup

The Claude API is a REST API at api.anthropic.com. Every request needs an API key (from the Console, under Account Settings), sent as the x-api-key header, plus an anthropic-version header pinning the API version you're coding against. The official SDKs (Python, TypeScript, and others) set both automatically once the key is in the environment.
pip install anthropic | | installs the official Python SDK |'capi_setup1'
export ANTHROPIC_API_KEY=<your key> | | the SDK reads this automatically, so Anthropic() needs no arguments |'capi_setup2'

a minimal call

Every request to the Messages API is a list of turns (user/assistant) plus a model name and a token budget. A system parameter sets standing instructions that apply to the whole conversation, separate from the message list.

from anthropic import Anthropic

client = Anthropic()
response = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    system="You are a concise code reviewer.",
    messages=[
        {"role": "user", "content": "Review this function for bugs: ..."}
    ],
)
print(response.content[0].text)
            
The response's content is a list of blocks, not a single string — a plain text reply is one text block, but a response can carry several blocks (text interleaved with tool calls, for instance), so production code should iterate over response.content rather than assume content[0] is always the whole answer.

multi-turn conversations

The API is stateless — there's no server-side conversation to append to. Each request resends the entire history, with the assistant's previous reply added back in as an assistant turn.

messages = [{"role": "user", "content": "What's a good name for a red panda?"}]

first = client.messages.create(model="claude-sonnet-5", max_tokens=256, messages=messages)
messages.append({"role": "assistant", "content": first.content})
messages.append({"role": "user", "content": "Now suggest one for its sibling."})

second = client.messages.create(model="claude-sonnet-5", max_tokens=256, messages=messages)
            

tool use (function calling): the full round trip

Define a tool as a JSON schema (name, description, an input_schema for its arguments), pass it in the request, and Claude can respond with a tool_use block instead of (or alongside) plain text. Your code runs the actual function and sends the result back in the next request as a tool_result block — Claude never executes anything itself over the API, it only asks. This is the exact mechanism MCP and Claude Code's own tools are built on.

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, e.g. San Francisco, CA"}
        },
        "required": ["location"],
    },
}]
messages = [{"role": "user", "content": "What's the weather in San Francisco?"}]

# Claude replies with a tool_use block naming the tool and its arguments.
response = client.messages.create(
    model="claude-sonnet-5", max_tokens=1024, tools=tools, messages=messages,
)
tool_use = next(b for b in response.content if b.type == "tool_use")
print(f"Claude wants to call {tool_use.name} with {tool_use.input}")

# Run the tool yourself, then send the result back as a tool_result block.
weather = "15 degrees Celsius, partly cloudy"  # your actual lookup goes here
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": [
    {"type": "tool_result", "tool_use_id": tool_use.id, "content": weather}
]})

followup = client.messages.create(
    model="claude-sonnet-5", max_tokens=1024, tools=tools, messages=messages,
)
print(next(b for b in followup.content if b.type == "text").text)
            
Whether Claude reaches for a tool at all is steerable through the system prompt — "always call a tool before responding" pushes harder than the default tool_choice: auto; set tool_choice explicitly to force (or forbid) a call rather than relying on wording alone. Tool definitions and the tool_use/tool_result blocks they generate all count as input tokens — a large toolset adds real cost to every request, not just the ones that end up using it.

vision: sending images

An image content block sits alongside text in the same message — base64-encoded inline, by URL, or by referencing a file already uploaded through the Files API (cheaper for an image reused across many requests, since it isn't re-sent as base64 every time).

message = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    messages=[{
        "role": "user",
        "content": [
            {"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": image_b64}},
            {"type": "text", "text": "Describe this image."},
        ],
    }],
)
            
Claude sees images as 28×28-pixel patches, so cost scales with resolution, not file size: a 1000×1000px image costs roughly 1,296 "visual tokens." Oversized images get downscaled automatically before processing — resizing to what you actually need before sending controls cost more predictably than relying on that downscale.

streaming

For anything user-facing, streaming the response token-by-token (rather than waiting for the full completion) is the difference between a chat that feels responsive and one that feels frozen.

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

prompt caching

If the same large block of context (a system prompt, a big document, tool definitions) gets sent on every request, marking it with a cache breakpoint lets the API reuse that processing instead of redoing it from scratch each time — a cache write costs 1.25× the normal input price, but a cache read costs only 0.1×, so it pays for itself after the first reuse.

response = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    system=[{
        "type": "text",
        "text": huge_system_prompt,
        "cache_control": {"type": "ephemeral"},
    }],
    messages=messages,
)
print(response.usage)
# cache_creation_input_tokens on the first call, cache_read_input_tokens on later ones
            
GotchaWhat actually happens
Minimum cacheable lengthmost current models need roughly 1,024+ tokens before the breakpoint to cache at all; shorter prompts are silently sent uncached, no error
Breakpoint placementonly the exact marked block is cached — put it on the last static content, never on something that changes per request (like a timestamp)
Cache invalidationchanging tool definitions invalidates everything cached; changing tool_choice or adding/removing images only invalidates the messages, not the system prompt
Default TTL5 minutes, refreshed automatically on each hit; a 1-hour TTL is available at 2× the write cost for content reused less frequently

rate limits

Limits are set per organization, per model, across three dimensions — requests per minute (RPM), input tokens per minute (ITPM), and output tokens per minute (OTPM) — using a token-bucket scheme (capacity refills continuously, not in fixed resets). Exceeding any of them returns a 429 with a retry-after header telling you exactly how long to back off.
The detail worth knowing: for most models, only uncached input tokens count toward your ITPM limit — cache_read_input_tokens doesn't. Heavy prompt caching doesn't just cut cost, it effectively raises your real throughput ceiling too.

client sdks

Official SDKs exist for Python, TypeScript, Go, Java, C#, PHP, and Ruby — all handle header management, retries, and streaming for you, so reaching for the SDK over raw HTTP is almost always the right default unless you have a specific reason not to.

related topics

Choosing a Claude Model — picking a model per-request based on the tradeoff described there.
MCP — the standardized version of the tool-use pattern above.
Python Notes & Cheat Sheet — the language most Claude API integrations are written in.
Avoiding Usage Limits — the consumer-plan counterpart to the rate limits above, for Claude Code/Claude.ai sessions rather than API calls.

reference

platform.claude.com — API overview
platform.claude.com — tool use
platform.claude.com — vision
platform.claude.com — prompt caching
platform.claude.com — rate limits