Claude API: The Complete Developer Guide

By Updated

What is Claude API?

Claude API is Anthropic's programmatic interface for integrating Claude models into applications. It runs through the Messages API: you send an array of messages (user/assistant), you get the model's response back. It supports text, vision, tool use, streaming, adaptive thinking, and prompt caching. Available via REST API, the Python SDK, and the TypeScript SDK.

TL;DR

  • -Claude API runs through the Messages API - a single endpoint for text, images, tool use, streaming, and thinking
  • -Four current models: Opus 5.5 ($4/$20 per MTok) for agents and hard tasks, Sonnet 5.5 ($2/$10) as the speed-quality balance, Haiku 5.5 (from $0.10/$0.50) for high-volume operations, Fable 5.1 ($10/$50) for the most demanding reasoning
  • -Tool use turns Claude into an executor: the model generates JSON calls to your functions, you return the result - the loop repeats until the task is done
  • -A prompt cache read costs 10% of the base input price (5% on Opus 5.5 and Sonnet 5.5, 2.5% on Fable 5.1); the Batches API gives a 50% discount on non-urgent work
  • -On current models thinking is on by default and its depth is set with `output_config.effort` (low/medium/high/xhigh/max); `budget_tokens`, `temperature`/`top_p`/`top_k`, and assistant prefill return a 400

Claude API is the programmatic interface for working with Anthropic’s models. One endpoint - the Messages API - covers text, images, tool use, streaming, and thinking. Python SDK, TypeScript SDK, raw REST - three ways to integrate.

This article is a complete reference. From the first request to advanced techniques: prompt caching, batches, vision. Every example is working code you can copy and run.

If Claude sits behind an endpoint in your product, define that public contract separately. The REST API design guide covers naming, errors, versioning, and OpenAPI generation for the layer your clients call.

What Claude API Is

The Messages API - a single endpoint

All work with Claude goes through one endpoint: POST https://api.anthropic.com/v1/messages. You send an array of messages with user and assistant roles, and the model generates the next message in the conversation.

This isn’t a GPT-3-style completions API - the Messages API is built around conversational structure. Every request carries the full conversation history. No state is kept between calls.

Models

Four current models (as of November 2026):

ModelAPI IDContextMax outputPrice (input/output per MTok)
Fable 5.1claude-fable-5-11M128k$10 / $50
Opus 5.5claude-opus-5-51M128k$4 / $20
Sonnet 5.5claude-sonnet-5-51M128k$2 / $10
Haiku 5.5claude-haiku-5-51M128k$0.10 / $0.50 for prompts up to 100K tokens, $0.50 / $2.50 above

Opus 5.5 - the main flagship, built for long-running agentic coding and knowledge work. Anthropic’s docs suggest starting with it for most workloads. Its default effort is medium, so set the level explicitly when a task needs more depth.

Sonnet 5.5 - the workhorse: the best combination of speed and intelligence for everyday coding, agent, and enterprise workloads. Half the price of Opus 5.5 on both input and output ($2/$10 vs $4/$20).

Haiku 5.5 - speed and economy. Classification, extraction, routing, subagent tasks. On prompts up to 100K tokens it is 20x cheaper than Sonnet 5.5; above that threshold every price for the request is 5x higher.

Fable 5.1 - the most capable model in the lineup, for demanding reasoning and long-horizon agentic work. The slowest and most expensive; reach for it when Opus 5.5 at higher effort still falls short on your evals.

Opus 5, Sonnet 5, Haiku 4.5, Fable 5, and the Opus/Sonnet 4.x models are now legacy: still available, with retirement dates listed on the model deprecations page. Coming from Sonnet 4.6, Haiku 4.5, or older models, re-count your tokens: Claude 4.7 and later models use a newer tokenizer that produces roughly 1x to 1.35x as many tokens for the same text (about 30% more on average, per the pricing page).

How it differs from Claude.ai

Claude.ai is a chat interface. The API gives you what chat doesn’t:

  • System prompts - precise control over model behavior
  • Tool use - the model calls your functions
  • Streaming - token-by-token output
  • Thinking - a summary of the model’s reasoning and control over its depth via effort
  • Prompt caching - caching of repeated context
  • Batches - asynchronous batch processing at a 50% discount
  • Full control - effort, stop sequences, metadata

Quick Start

Getting an API key

  1. Sign up for the Claude Console at platform.claude.com
  2. Go to Settings → API Keys
  3. Create a key
  4. Add credits under Billing

The key is tied to a workspace. One workspace - one set of keys, limits, and billing.

For local development you can skip the key: install the ant CLI and run ant auth login - the SDKs pick up those credentials automatically.

Installing the SDK

Python:

pip install anthropic

TypeScript:

npm install @anthropic-ai/sdk

The examples assume the current major versions (anthropic 2.x for Python, @anthropic-ai/sdk 1.x for TypeScript). They send API version 2026-10-21 by default.

The SDK picks up the key from the ANTHROPIC_API_KEY environment variable:

export ANTHROPIC_API_KEY="your-api-key"

The first request

Python:

import anthropic

client = anthropic.Anthropic()

message = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    messages=[
        {"role": "user", "content": "Explain the difference between REST and GraphQL in three sentences."}
    ]
)

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

TypeScript:

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const message = await client.messages.create({
  model: "claude-sonnet-5-5",
  max_tokens: 4096,
  messages: [
    { role: "user", content: "Explain the difference between REST and GraphQL in three sentences." }
  ]
});

for (const block of message.content) {
  if (block.type === "text") {
    console.log(block.text);
  }
}

The response lives in message.content - an array of content blocks. On current models thinking is on by default, so one or more thinking blocks can come before the text block. Pick blocks by type, not by position: code like content[0].text breaks on these responses.

Streaming - real-time output

Streaming delivers tokens as they’re generated - the user sees output immediately.

Python:

import anthropic

client = anthropic.Anthropic()

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

TypeScript:

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const stream = client.messages.stream({
  model: "claude-sonnet-5-5",
  max_tokens: 4096,
  messages: [
    { role: "user", content: "Write a binary search function in TypeScript." }
  ]
});

for await (const event of stream) {
  if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
    process.stdout.write(event.delta.text);
  }
}

Messages API: a full breakdown

Request parameters

ParameterTypeRequiredDescription
modelstringYesModel ID: claude-opus-5-5, claude-sonnet-5-5, claude-haiku-5-5, claude-fable-5-1
messagesarrayYesArray of messages with user and assistant roles
max_tokensintegerYesHard ceiling on output, thinking included (current models: up to 128k; the SDK requires streaming above 21,333)
systemstring/arrayNoSystem prompt - instructions for the model
thinkingobjectNoThinking mode: {"type": "adaptive"} is the default on current models; display controls what the thinking blocks contain
output_configobjectNoeffort (reasoning depth and token spend) and format (structured outputs)
temperature, top_p, top_k--Don’t send them: non-default values return a 400 on the 5.5 models and Fable 5.1, and starting with API version 2026-10-21 any value returns a 400 on every model. The current SDK major versions no longer have these parameters
stop_sequencesarrayNoStrings that stop generation when produced
streambooleanNoStreamed output via Server-Sent Events
metadataobjectNouser_id for tracking and abuse detection

System prompt

The system prompt sets the role and behavior rules. It’s passed as a separate parameter, not through messages.

Python:

message = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    system="You are a senior Python developer. Answer concisely. Code should include types and docstrings.",
    messages=[
        {"role": "user", "content": "Write a retry decorator with exponential backoff."}
    ]
)

The system prompt is a string or an array of content blocks. The array form is needed for prompt caching:

system=[
    {
        "type": "text",
        "text": "You are a senior Python developer. Answer concisely.",
        "cache_control": {"type": "ephemeral"}
    }
]

Multi-turn conversations

Claude doesn’t keep state between requests. To continue a conversation, send the full history:

Python:

messages = [
    {"role": "user", "content": "What is dependency injection?"},
    {"role": "assistant", "content": "Dependency injection is a pattern where an object's dependencies are supplied from the outside instead of being created internally..."},
    {"role": "user", "content": "Show an example in Python using a dataclass."}
]

message = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    messages=messages
)

Each message is an object with role (user or assistant) and content. Roles alternate. Two consecutive messages from the same role get merged by the API. The last message must not be an assistant turn: Claude 4.6 and later models reject a prefilled assistant response with a 400.

Stop sequences

Custom strings that stop generation when produced:

message = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    stop_sequences=["```", "END"],
    messages=[
        {"role": "user", "content": "Write an nginx JSON configuration."}
    ]
)

# message.stop_reason == "stop_sequence"
# message.stop_sequence == "```"

Useful for structured output: the model generates up to a specific marker, and you parse the result.

Metadata

user_id in metadata is used for abuse tracking. Add it in production:

message = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    metadata={"user_id": "user_abc123"},
    messages=[
        {"role": "user", "content": "Hello!"}
    ]
)

Use hashes or UUIDs - don’t pass email addresses or real names.

Tool Use (Function Calling)

Tool use is how you call your own functions from Claude. You describe tools via JSON Schema. The model decides when and which one to call, and constructs the parameters. You execute the call and return the result.

Defining tools

A tool is a name, a description, and a JSON Schema for its input parameters:

tools = [
    {
        "name": "get_weather",
        "description": "Get the current weather for a given city.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "City name, e.g.: Moscow"
                },
                "units": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature unit"
                }
            },
            "required": ["city"]
        }
    }
]

Descriptions matter. Claude reads them to decide whether to call a tool. The more precise the description, the better the choice.

The tool_use → tool_result loop

The loop looks like this:

  1. You send a request with tools
  2. Claude returns a tool_use block with the tool name and parameters
  3. You execute the call
  4. You send back the assistant message unchanged (including its thinking blocks) plus the result as a tool_result
  5. Claude produces the final answer (or calls the next tool)

Step 4 matters on current models: the API rejects edited, reordered, or partially dropped thinking blocks with a 400. Append response.content as is, without filtering blocks by type.

Full example: a weather bot

Python:

import anthropic
import json

client = anthropic.Anthropic()

tools = [
    {
        "name": "get_weather",
        "description": "Get the current weather for a given city.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City"},
                "units": {"type": "string", "enum": ["celsius", "fahrenheit"]}
            },
            "required": ["city"]
        }
    }
]

def get_weather(city: str, units: str = "celsius") -> dict:
    """Stub — in production this would call a real API."""
    return {"city": city, "temperature": 22, "units": units, "condition": "cloudy"}

# Step 1: send the request
messages = [{"role": "user", "content": "What's the weather in Moscow?"}]

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

# Step 2: handle tool_use
if response.stop_reason == "tool_use":
    # Find the tool_use block in the response
    tool_block = next(b for b in response.content if b.type == "tool_use")

    # Execute the call
    result = get_weather(**tool_block.input)

    # Step 3: return the result (the assistant message goes back unchanged, thinking blocks included)
    messages.append({"role": "assistant", "content": response.content})
    messages.append({
        "role": "user",
        "content": [
            {
                "type": "tool_result",
                "tool_use_id": tool_block.id,
                "content": json.dumps(result)
            }
        ]
    })

    # Step 4: get the final answer
    final_response = client.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=4096,
        tools=tools,
        messages=messages
    )

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

TypeScript:

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const tools: Anthropic.Tool[] = [
  {
    name: "get_weather",
    description: "Get the current weather for a given city.",
    input_schema: {
      type: "object" as const,
      properties: {
        city: { type: "string", description: "City" },
        units: { type: "string", enum: ["celsius", "fahrenheit"] }
      },
      required: ["city"]
    }
  }
];

function getWeather(city: string, units = "celsius") {
  return { city, temperature: 22, units, condition: "cloudy" };
}

const messages: Anthropic.MessageParam[] = [
  { role: "user", content: "What's the weather in Moscow?" }
];

const response = await client.messages.create({
  model: "claude-sonnet-5-5",
  max_tokens: 4096,
  tools,
  messages
});

if (response.stop_reason === "tool_use") {
  const toolBlock = response.content.find(
    (b): b is Anthropic.ToolUseBlock => b.type === "tool_use"
  )!;

  const result = getWeather(
    (toolBlock.input as any).city,
    (toolBlock.input as any).units
  );

  messages.push({ role: "assistant", content: response.content });
  messages.push({
    role: "user",
    content: [
      {
        type: "tool_result",
        tool_use_id: toolBlock.id,
        content: JSON.stringify(result)
      }
    ]
  });

  const finalResponse = await client.messages.create({
    model: "claude-sonnet-5-5",
    max_tokens: 4096,
    tools,
    messages
  });

  for (const block of finalResponse.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
}

On Opus 5.5 and Sonnet 5.5, the short notes the model writes between tool calls come back as thinking blocks rather than text, and they are empty by default. If your UI shows that progress, set thinking: {"type": "adaptive", "display": "summarized"} (or display: "updates" with the beta header thinking-display-updates-2026-08-18).

tool_choice - controlling tool invocation

ValueBehavior
{"type": "auto"}The model decides on its own - call or not (default)
{"type": "any"}The model must call at least one tool. Returns a 400 on Opus 5.5, Sonnet 5.5, and Fable 5.1
{"type": "tool", "name": "get_weather"}The model must call a specific tool. Returns a 400 on Opus 5.5, Sonnet 5.5, and Fable 5.1
{"type": "none"}Tools are disabled

Forced tool use still works on Haiku 5.5 and the legacy models:

# Forcing a specific tool call (Haiku 5.5)
response = client.messages.create(
    model="claude-haiku-5-5",
    max_tokens=4096,
    tools=tools,
    tool_choice={"type": "tool", "name": "get_weather"},
    messages=[{"role": "user", "content": "Tell me about Moscow."}]
)

On Opus 5.5, Sonnet 5.5, and Fable 5.1, use tool_choice: {"type": "auto"}, name the tool in the prompt, and set strict: true on the tool so its arguments always match the schema. If all you need is JSON in a given structure, use structured outputs.

Parallel tool calls

Claude can call several tools in a single turn. The response contains multiple tool_use blocks - handle all of them and return all the results:

# If Claude called 3 tools — return 3 results
tool_results = []
for block in response.content:
    if block.type == "tool_use":
        result = execute_tool(block.name, block.input)
        tool_results.append({
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": json.dumps(result)
        })

messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})

To disable parallel calls: tool_choice: {"type": "auto", "disable_parallel_tool_use": true}.

Extended Thinking

Thinking lets Claude reason before answering. The model breaks the problem into steps, checks hypotheses, catches mistakes - and only then answers. The API never returns the raw chain of thought: at most you get a summary of it.

When to turn it on

  • Complex math and logic problems
  • Multi-step code analysis
  • Tasks with a non-obvious solution
  • Any situation where “think longer” means “answer better”

On the current lineup the question is less “turn it on” and more “how deep”: thinking is on by default, and you tune it with effort.

Parameters

With the release of Claude 4.6, Anthropic moved to adaptive thinking - the model decides for itself how much to think. The manual mode thinking: {type: "enabled", budget_tokens: N} returns a 400 Bad Request on every current model.

What each current model does with the thinking field:

ModelWithout thinkingTurning thinking offDefault effort
Opus 5.5adaptiveNot possible: {"type": "disabled"} returns a 400medium
Fable 5.1adaptiveNot possible: {"type": "disabled"} returns a 400high
Sonnet 5.5adaptivedisabled returns a 400; {"type": "between_tools"} turns off up-front thinking at effort high or belowhigh
Haiku 5.5adaptive{"type": "disabled"} at effort high or belowmedium

All four accept all five effort levels: low, medium, high, xhigh, max. Depth is controlled via output_config.effort:

response = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=16000,
    thinking={"type": "adaptive", "display": "summarized"},
    output_config={"effort": "high"},  # low | medium | high | xhigh | max
    messages=[
        {"role": "user", "content": "Prove that there are infinitely many primes p where p mod 4 == 3."}
    ]
)

effort is a soft signal, not a hard token limit. The hard limit is max_tokens, and it covers thinking plus answer text. To cut cost or latency on a thinking workload, lower effort first; to give the model more depth, raise it. On simple requests Claude may skip thinking entirely - then the response has no thinking block at all.

Reading thinking blocks

The response contains thinking and text blocks:

for block in response.content:
    if block.type == "thinking":
        print(f"Reasoning: {block.thinking}")
    elif block.type == "text":
        print(f"Answer: {block.text}")

On every current model (Fable 5.1, Opus 5.5, Sonnet 5.5, Haiku 5.5) the default is display: "omitted": the block comes back with an empty thinking field and only a signature. On Opus 4.6, Sonnet 4.6, and earlier models the default was display: "summarized". To see the gist of the reasoning, set thinking: {type: "adaptive", display: "summarized"}, as in the example above. display: "omitted" speeds up the first text token when streaming. Billing covers the full thinking tokens regardless of the display value.

Streaming with extended thinking

Python:

with client.messages.stream(
    model="claude-sonnet-5-5",
    max_tokens=16000,
    thinking={"type": "adaptive", "display": "summarized"},
    output_config={"effort": "high"},
    messages=[{"role": "user", "content": "Implement a red-black tree in Python."}],
) as stream:
    for event in stream:
        if event.type == "content_block_delta":
            if event.delta.type == "thinking_delta":
                print(event.delta.thinking, end="", flush=True)
            elif event.delta.type == "text_delta":
                print(event.delta.text, end="", flush=True)

Without display: "summarized", the thinking_delta events arrive with an empty string, and the user sees a pause before the answer.

Limitations and compatibility

  • Don’t send temperature, top_p, or top_k: on Fable 5.1 and the 5.5 models non-default values return a 400, and starting with API version 2026-10-21 any value does
  • No prefill: Claude 4.6 and later models reject a prefilled final assistant message with a 400
  • Forced tool use (tool_choice any/tool) returns a 400 on Opus 5.5, Sonnet 5.5, and Fable 5.1
  • max_tokens covers thinking plus answer; at effort: xhigh or max, start from 64k
  • With max_tokens above 21,333 the SDK requires streaming: use .stream() and .get_final_message()
  • For very long outputs, the Batches API offers up to 300k tokens (see below)

Cost

Thinking tokens are billed as output tokens, even when the thinking text isn’t returned to you. The cost depends on the amount actually spent (effort steers it, but doesn’t cap it). usage.output_tokens is the billed number, and it includes thinking, so it won’t match the length of the visible text.

The display: "omitted" option hides thinking blocks in the response and reduces latency, but not cost - the tokens are still billed either way.

Prompt Caching

Prompt caching preserves processed tokens across requests. A cache read costs 10% of the base input price on most models, 5% on Opus 5.5 and Sonnet 5.5, and 2.5% on Fable 5.1. A comparison with OpenAI, Gemini and DeepSeek and a break-even formula are in how prompt caching works across providers.

How it works

You mark blocks with a cache_control flag. Anthropic caches the result. The next request with the same content is a cache hit - the tokens aren’t re-processed.

The cache is hierarchical: tools → system → messages. A change at any level invalidates it and everything after it. Changing the thinking settings or the effort level also starts a new cache prefix.

TTL: two options

TTLCache writeCache readWhen to use
5 minutes (default)1.25x base price0.1x base price (0.05x on Opus 5.5 and Sonnet 5.5, 0.025x on Fable 5.1)Frequent requests (more often than every 5 minutes)
1 hour2x base priceSame as aboveAgentic tasks, long sessions

Minimum size for caching

ModelMinimum tokens
Fable 5.1, Opus 5.5, Sonnet 5.5, Haiku 5.5, Opus 5512
Sonnet 5, Opus 4.8, Sonnet 4.61024
Haiku 4.54096

Content shorter than the minimum won’t get cached - the request still processes without errors, just without a cache.

Example: caching a large document

Python:

import anthropic

client = anthropic.Anthropic()

# A large document — cache it in the system prompt
long_document = open("technical_spec.md").read()

response = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    system=[
        {
            "type": "text",
            "text": "You are a technical analyst. Answer questions about the document below.",
        },
        {
            "type": "text",
            "text": long_document,
            "cache_control": {"type": "ephemeral"}  # 5-minute cache
        }
    ],
    messages=[
        {"role": "user", "content": "What non-functional requirements are described in the document?"}
    ]
)

# Check the cache
usage = response.usage
print(f"Cache write: {usage.cache_creation_input_tokens}")
print(f"Cache read: {usage.cache_read_input_tokens}")
print(f"Uncached: {usage.input_tokens}")

A second request with the same system prompt is a cache hit - cache_read_input_tokens will be non-zero.

1-hour cache

system=[
    {
        "type": "text",
        "text": long_document,
        "cache_control": {"type": "ephemeral", "ttl": "1h"}
    }
]

Use the 1-hour cache in agentic scenarios where more than 5 minutes pass between requests.

Automatic caching

There’s no caching by default - without cache_control, nothing gets cached. The simplest option, which Anthropic’s docs recommend as the starting point, is a single top-level cache_control on the request: the API places the breakpoint on the last cacheable block itself and moves it forward as the conversation grows. It takes one of the 4 available breakpoint slots.

Vision (Multimodality)

Claude accepts images in messages. Two ways: base64 and URL.

Supported formats

JPEG, PNG, GIF, WebP (for animations, only the first frame is used). Maximum size - 10 MB per image (base64-encoded) on the Claude API, 5 MB on Amazon Bedrock. Maximum dimensions - 8000x8000 px.

Base64

Python:

import anthropic
import base64

client = anthropic.Anthropic()

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

message = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": image_data
                    }
                },
                {
                    "type": "text",
                    "text": "Analyze this UI screenshot. Find UX problems."
                }
            ]
        }
    ]
)

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

URL

message = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "url",
                        "url": "https://example.com/diagram.png"
                    }
                },
                {
                    "type": "text",
                    "text": "Describe this architecture diagram."
                }
            ]
        }
    ]
)

Token count for images

Claude processes an image in 28×28-pixel patches: it costs ⌈width / 28⌉ × ⌈height / 28⌉ tokens. Larger images are downscaled first. All current models are in the high-resolution tier (Claude 4.7 and later): up to a 2576 px long edge and 4,784 tokens per image; older models stop at 1568 px and 1,568 tokens. Examples on current models:

  • 200x200 px: 64 tokens
  • 1000x1000 px: 1,296 tokens
  • 1920x1080 px: 2,691 tokens
  • 3840x2160 px: downscaled to 2576x1449, 4,784 tokens

The same image can cost up to roughly 3x more tokens than on a standard-tier model. If you don’t need the detail, downsample before sending. For an exact count, use the Token Counting API - a separate endpoint that counts tokens without generating a response.

TypeScript example: analyzing a screenshot

import Anthropic from "@anthropic-ai/sdk";
import { readFileSync } from "fs";

const client = new Anthropic();

const imageData = readFileSync("screenshot.png").toString("base64");

const message = await client.messages.create({
  model: "claude-sonnet-5-5",
  max_tokens: 4096,
  messages: [
    {
      role: "user",
      content: [
        {
          type: "image",
          source: {
            type: "base64",
            media_type: "image/png",
            data: imageData
          }
        },
        {
          type: "text",
          text: "What's shown in the screenshot? List all UI elements."
        }
      ]
    }
  ]
});

for (const block of message.content) {
  if (block.type === "text") {
    console.log(block.text);
  }
}

Streaming

Server-Sent Events

With stream: true, the response arrives as a stream of SSE events. Main event types:

EventDescription
message_startStart of the message, carries metadata
content_block_startStart of a content block (text, tool_use, thinking)
content_block_deltaContent delta: text_delta, thinking_delta, signature_delta, input_json_delta
content_block_stopEnd of a content block
message_deltaFinal data: stop_reason, usage
message_stopEnd of the message

On current models the stream usually opens with a thinking block, so don’t treat the first content_block_start as text - branch on the block type.

Python SDK - stream helper

import anthropic

client = anthropic.Anthropic()

with client.messages.stream(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    messages=[{"role": "user", "content": "Write an async HTTP client in Python."}]
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

# After completion — access the full message
final_message = stream.get_final_message()
print(f"\nTokens: {final_message.usage.input_tokens} in / {final_message.usage.output_tokens} out")

stream.text_stream yields text only. For low-level access to events, iterate over the stream object itself.

TypeScript SDK - stream

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const stream = client.messages.stream({
  model: "claude-sonnet-5-5",
  max_tokens: 4096,
  messages: [{ role: "user", content: "Write an async HTTP client in TypeScript." }]
});

for await (const event of stream) {
  if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
    process.stdout.write(event.delta.text);
  }
}

const finalMessage = await stream.finalMessage();
console.log(`\nTokens: ${finalMessage.usage.input_tokens} in / ${finalMessage.usage.output_tokens} out`);

Streaming with tool use

With streaming, tool parameters arrive as input_json_delta - fragments of JSON:

with client.messages.stream(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    tools=tools,
    messages=[{"role": "user", "content": "What's the weather in Moscow?"}]
) as stream:
    for event in stream:
        if event.type == "content_block_start" and event.content_block.type == "tool_use":
            print(f"Tool call: {event.content_block.name}")
        elif event.type == "content_block_delta":
            if event.delta.type == "input_json_delta":
                print(f"  parameters: {event.delta.partial_json}", end="")
            elif event.delta.type == "text_delta":
                print(event.delta.text, end="", flush=True)

Accumulate partial_json until content_block_stop, then parse the full JSON.

Batches API

The Batches API is asynchronous batch processing at a 50% discount. Requests don’t hold an HTTP connection open: you submit a batch and pick up the results when they’re ready. A batch holds up to 100,000 requests or 256 MB; most batches finish within an hour, and results stay available for 29 days.

When to use it

  • Bulk processing: classifying thousands of documents
  • Eval pipelines: running a benchmark suite
  • Content generation: batch-generating descriptions
  • Any task where latency doesn’t matter

Creating a batch request

Python:

import anthropic

client = anthropic.Anthropic()

batch = client.messages.batches.create(
    requests=[
        {
            "custom_id": "request-1",
            "params": {
                "model": "claude-sonnet-5-5",
                "max_tokens": 4096,
                "messages": [
                    {"role": "user", "content": "Classify this ticket: 'Card payment isn't working'. Categories: billing, technical, feature_request."}
                ]
            }
        },
        {
            "custom_id": "request-2",
            "params": {
                "model": "claude-sonnet-5-5",
                "max_tokens": 4096,
                "messages": [
                    {"role": "user", "content": "Classify this ticket: 'I want a PDF export'. Categories: billing, technical, feature_request."}
                ]
            }
        }
    ]
)

print(f"Batch ID: {batch.id}")
print(f"Status: {batch.processing_status}")

Retrieving results

# Check the status
batch = client.messages.batches.retrieve(batch.id)

if batch.processing_status == "ended":
    # Read the results
    for entry in client.messages.batches.results(batch.id):
        if entry.result.type == "succeeded":
            text = "".join(b.text for b in entry.result.message.content if b.type == "text")
            print(f"{entry.custom_id}: {text}")
        else:
            print(f"{entry.custom_id}: error — {entry.result.type}")

TypeScript

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const batch = await client.messages.batches.create({
  requests: [
    {
      custom_id: "request-1",
      params: {
        model: "claude-sonnet-5-5",
        max_tokens: 4096,
        messages: [
          { role: "user", content: "Summarize: 'Claude API supports streaming, tool use, and prompt caching.'" }
        ]
      }
    }
  ]
});

console.log(`Batch ID: ${batch.id}`);

// Later — retrieve results
const results = await client.messages.batches.results(batch.id);
for await (const entry of results) {
  if (entry.result.type === "succeeded") {
    const text = entry.result.message.content
      .filter((b): b is Anthropic.TextBlock => b.type === "text")
      .map((b) => b.text)
      .join("");
    console.log(`${entry.custom_id}: ${text}`);
  }
}

Extended output in Batches

The Batches API supports extended output: up to 300k tokens for Opus 5.5, Sonnet 5.5, Haiku 5.5, Opus 5, Sonnet 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6 with the beta header output-300k-2026-03-24 (via client.beta.messages.batches.create). Useful for generating long documents, books, and reports. A single 300k-token generation can take over an hour, so keep the 24-hour batch processing window in mind.

Pricing and Optimization

For choosing between Opus and Sonnet by task and budget, see the separate comparison.

Price table

ModelInputOutputCache write (5m)Cache readCache write (1h)
Fable 5.1$10$50$12.50 (1.25x)$0.25 (0.025x)$20 (2x)
Opus 5.5$4$20$5 (1.25x)$0.20 (0.05x)$8 (2x)
Sonnet 5.5$2$10$2.50 (1.25x)$0.10 (0.05x)$4 (2x)
Haiku 5.5 (prompt up to 100K tokens)$0.10$0.50$0.125 (1.25x)$0.01 (0.1x)$0.20 (2x)
Haiku 5.5 (prompt over 100K tokens)$0.50$2.50$0.625 (1.25x)$0.05 (0.1x)$1 (2x)
Opus 5 (legacy)$5$25$6.25 (1.25x)$0.50 (0.1x)$10 (2x)
Sonnet 5 (legacy)$2$10$2.50 (1.25x)$0.20 (0.1x)$4 (2x)
Haiku 4.5 (legacy)$1$5$1.25 (1.25x)$0.10 (0.1x)$2 (2x)

The Batches API gives a 50% discount across every model.

For Haiku 5.5, the prompt length counts all input tokens, cache reads and writes included; a request over the threshold pays the higher prices for everything, output too. The other current models charge the same per-token rate across the full 1M context.

Fast mode on Opus 5.5 (speed: "fast", Claude API) costs $8 / $40 and isn’t available with the Batches API.

All prices are per million tokens (MTok). Source: Anthropic pricing.

Optimization strategies

1. Pick the model that matches the task

Don’t run Opus 5.5 on classification - Haiku 5.5 handles it at 1/40 of the price (on prompts up to 100K tokens). Sonnet 5.5 covers everyday workloads at half the Opus price. Reach for Opus 5.5 when a mistake is expensive, and for Fable 5.1 only when Opus 5.5 at higher effort still falls short.

2. Prompt caching for repeated context

If the system prompt and context documents stay the same, cache them. On Sonnet 5.5 a cache read costs 5% of the base price. At 100k tokens of cached context:

  • Without cache: 100k × $2/MTok = $0.20 per request (Sonnet 5.5)
  • With cache: 100k × $0.10/MTok = $0.01 per request
  • Savings: 95% on input tokens

3. Batches for non-urgent work

Eval pipelines, bulk classification, dataset generation - latency doesn’t matter. The Batches API gives a 50% discount.

4. Streaming for UX

Streaming doesn’t save on tokens, but the first character appears within seconds - the user isn’t left waiting. A must for chat interfaces.

5. Control max_tokens and effort

max_tokens is a hard ceiling for thinking plus answer: set it too low and the response gets cut off. To spend fewer tokens, lower effort - it scales the whole response down, thinking included. Re-run your effort sweep when you switch models: Opus 5.5 defaults to medium, Sonnet 5.5 to high.

6. Monitor usage

Every response contains usage:

{
  "input_tokens": 2095,
  "output_tokens": 503,
  "cache_creation_input_tokens": 0,
  "cache_read_input_tokens": 2000
}

Log it. Compute cost with the formula:

cost = (input_tokens * input_price + output_tokens * output_price
        + cache_creation * write_price + cache_read * read_price) / 1_000_000

Structured Outputs

Native JSON mode is available through output_config.format - GA, no beta headers needed, supported on every current model. The API guarantees a valid JSON output matching your schema.

import anthropic
import json

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    output_config={
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "age": {"type": "integer"},
                    "role": {"type": "string"}
                },
                "required": ["name", "age", "role"],
                "additionalProperties": False
            }
        }
    },
    messages=[
        {"role": "user", "content": "Ivan Petrov, 35, senior developer"}
    ]
)

text = next(b.text for b in response.content if b.type == "text")
data = json.loads(text)
# {"name": "Ivan Petrov", "age": 35, "role": "senior developer"}

In Python, client.messages.parse() with a Pydantic model is even simpler - the SDK builds the schema, validates the response, and returns the object in parsed_output (example in the JSON mode section below).

When to use which:

  • output_config.format - simpler, doesn’t require a tooling context, fits most cases
  • Strict tool use (strict: true on the tool) - when you’re already using tools in the same request and need schema-valid arguments. Forced tool_choice returns a 400 on Opus 5.5, Sonnet 5.5, and Fable 5.1, so on those models the tool is called via auto plus an instruction in the prompt

MCP Connector

The MCP Connector lets you connect remote MCP servers directly from the Messages API - no separate MCP client needed. Useful for agentic scenarios: the model calls tools from your MCP services itself.

Requires the beta header mcp-client-2025-11-20. On the Claude API there’s also the newer mcp-client-2026-09-15: it includes everything the previous one does and adds pinning of the server’s tool list.

import anthropic

client = anthropic.Anthropic()

response = client.beta.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    mcp_servers=[
        {
            "type": "url",
            "url": "https://your-mcp-server.example.com/mcp",
            "name": "my-tools",
            "authorization_token": "YOUR_TOKEN"  # optional
        }
    ],
    tools=[
        {
            "type": "mcp_toolset",
            "mcp_server_name": "my-tools"  # server name from mcp_servers
        }
    ],
    messages=[
        {"role": "user", "content": "Complete the task using the available tools."}
    ],
    betas=["mcp-client-2025-11-20"]
)

Tool configuration lives in the tools array as an mcp_toolset object. You can restrict the tool set via an allowlist/denylist:

tools=[
    {
        "type": "mcp_toolset",
        "mcp_server_name": "my-tools",
        "default_config": {"enabled": False},   # allowlist: disable everything by default
        "configs": {
            "tool1": {"enabled": True},          # enable only what's needed
            "tool2": {"enabled": True}
        }
    }
]

Important limitations:

  • Only MCP tool calls are supported - prompts and resources are not
  • The server must be publicly reachable over HTTP (Streamable HTTP or SSE); local STDIO servers can’t be connected directly
  • Available in beta on the Claude API, Claude Platform on AWS, Claude Models on Google Cloud (Anthropic managed), and Microsoft Foundry; not supported on Amazon Bedrock
  • Tool results come from an external system - apply the same trust measures you’d apply to user input (risk of indirect prompt injection)

Best Practices

Error handling and retries

Standard HTTP codes. Main errors:

CodeCauseAction
400Invalid requestCheck the parameters
401Invalid API keyCheck ANTHROPIC_API_KEY
429Rate limitRetry with exponential backoff
529API overloadedRetry after 30-60 seconds
500Server errorRetry with backoff

The Python SDK retries automatically:

# The SDK automatically retries 429 and 5xx
client = anthropic.Anthropic(
    max_retries=3,  # default: 2
    timeout=60.0     # seconds
)

For custom logic:

import time
from anthropic import RateLimitError, APIStatusError

def call_with_retry(client, max_retries=3, **kwargs):
    for attempt in range(max_retries):
        try:
            return client.messages.create(**kwargs)
        except RateLimitError:
            wait = 2 ** attempt  # 1s, 2s, 4s
            print(f"Rate limited, waiting {wait}s...")
            time.sleep(wait)
        except APIStatusError as e:
            if e.status_code >= 500:
                time.sleep(2 ** attempt)
                continue
            raise
    raise Exception("All retries exhausted")

API key security

  • Store keys in environment variables, not in code
  • Use separate keys for dev/staging/production
  • Rotate keys regularly
  • One workspace per project (isolates billing and limits)
  • In CI/CD - use secrets (GitHub Secrets, Vault)
# Correct
client = anthropic.Anthropic()  # picks up ANTHROPIC_API_KEY

# Wrong
client = anthropic.Anthropic(api_key="your-api-key")  # key hardcoded

Structured output (JSON mode)

The recommended approach is output_config.format (covered in the dedicated section above). The old prefill trick (ending messages with an assistant turn {) no longer works: Claude 4.6 and later models reject it with a 400. Two other approaches:

Approach 1: client.messages.parse() with a Pydantic model

from pydantic import BaseModel

class Person(BaseModel):
    name: str
    age: int
    role: str

response = client.messages.parse(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    messages=[
        {"role": "user", "content": "Extract data from this text: 'Ivan Petrov, 35, senior developer'."}
    ],
    output_format=Person,
)

person = response.parsed_output  # Person(name='Ivan Petrov', age=35, role='senior developer')

The SDK turns the model into a JSON Schema, sends it as output_config.format, and validates the response.

Approach 2: Strict tool use

tools = [{
    "name": "extract_person",
    "description": "Extract data about a person.",
    "strict": True,
    "input_schema": {
        "type": "object",
        "properties": {
            "name": {"type": "string"},
            "age": {"type": "integer"},
            "role": {"type": "string"}
        },
        "required": ["name", "age", "role"],
        "additionalProperties": False
    }
}]

message = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    tools=tools,
    messages=[
        {"role": "user", "content": "Call extract_person for this text: Ivan Petrov, 35, senior developer"}
    ]
)

# With strict: true, tool_use.input always matches the schema
tool_block = next((b for b in message.content if b.type == "tool_use"), None)
if tool_block:
    person = tool_block.input  # {"name": "Ivan Petrov", "age": 35, "role": "senior developer"}

strict: true guarantees schema-valid arguments, but with tool_choice: auto the model decides whether to call the tool, so name it in the prompt and check that the block is there. On Haiku 5.5 and the legacy models you can still force the call with tool_choice: {"type": "tool", "name": "extract_person"}.

Rate limits

Limits depend on the model and the organization’s usage tier. Main metrics:

  • Requests per minute (RPM) - number of requests per minute
  • Input tokens per minute (ITPM) - only uncached input counts: cache_read_input_tokens don’t count toward it
  • Output tokens per minute (OTPM) - generated tokens; max_tokens doesn’t affect it

Response headers carry the current limits:

anthropic-ratelimit-requests-limit: 1000
anthropic-ratelimit-requests-remaining: 999
anthropic-ratelimit-requests-reset: 2026-04-09T12:00:00Z
anthropic-ratelimit-tokens-limit: 80000
anthropic-ratelimit-tokens-remaining: 79500

Strategies:

  1. Exponential backoff on 429 - the SDK does this automatically; if the response has a retry-after header, wait that many seconds
  2. Batch processing via the Batches API - it has its own limits and doesn’t affect Messages API rate limits
  3. Multiple workspaces to isolate limits between projects
  4. Cache reads don’t count against ITPM - another reason to cache

Logging and monitoring

Every response contains an id (msg_...) and usage. Log both:

import logging

logger = logging.getLogger("claude_api")

response = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    messages=[{"role": "user", "content": "Hello"}]
)

logger.info(
    "API call",
    extra={
        "message_id": response.id,
        "model": response.model,
        "input_tokens": response.usage.input_tokens,
        "output_tokens": response.usage.output_tokens,
        "stop_reason": response.stop_reason,
    }
)

In production, wire up observability: Langfuse, LangSmith, or your own pipeline. Track latency, cost per request, and distribution across models.

Wrapping Up

Claude API is one endpoint, four current models, a set of powerful features.

To get started: install the SDK, get a key, send your first request. Anthropic suggests starting with Opus 5.5; Sonnet 5.5 is half the price and faster.

For production: prompt caching on repeated context, streaming for UX, Haiku 5.5 for bulk operations, Batches for eval pipelines. Read response blocks by type and drop temperature, prefill, and forced tool_choice from older code.

For hard tasks: adaptive thinking with output_config.effort for reasoning, tool use for integrating with external systems, MCP Connector for connecting to remote MCP servers directly from the API, Opus 5.5 when accuracy is critical, Fable 5.1 when even that falls short.

Structured outputs: output_config.format - native JSON mode with no beta headers.

Full documentation - platform.claude.com/docs. SDKs: anthropic-sdk-python, anthropic-sdk-typescript. Examples and recipes - anthropic-cookbook.

Frequently Asked Questions

How do I get a Claude API key?
Sign up for the Claude Console at platform.claude.com and create a key under Settings → API Keys. The key is tied to a workspace. Add credits under Billing. For local development you can skip the key: after `ant auth login` the SDKs pick up the CLI credentials automatically.
How much does Claude API cost?
Prices per million tokens (MTok): Opus 5.5 - $4 input / $20 output, Sonnet 5.5 - $2 / $10, Haiku 5.5 - $0.10 / $0.50 for prompts up to 100K tokens and $0.50 / $2.50 above that, Fable 5.1 - $10 / $50. A prompt cache read costs 10% of the base input price on most models (5% on Opus 5.5 and Sonnet 5.5, 2.5% on Fable 5.1). The Batches API gives a 50% discount.
How is Claude API different from Claude.ai?
Claude.ai is a chat interface for end users. The API gives you full control: system prompts, tool use (function calling), streaming, thinking and effort settings, prompt caching, batches. You can embed Claude into any application, automate processing, and scale it.
Which Claude model should I pick?
Opus 5.5 (`claude-opus-5-5`) - for long-running agentic coding and knowledge work; Anthropic recommends starting with it for most workloads. Sonnet 5.5 (`claude-sonnet-5-5`) - the best combination of speed and intelligence at half the Opus price. Haiku 5.5 (`claude-haiku-5-5`) - for classification, extraction, routing, and other high-volume tasks. Fable 5.1 (`claude-fable-5-1`) - for demanding reasoning and long-horizon agentic work when Opus 5.5 falls short. All four have a 1M-token context window and up to 128k output tokens.