Tutorials

Claude API: The Complete Developer Guide

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, extended 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 extended thinking
  • -Three models: Opus 4.8 ($5/$25 per MTok) for hard tasks and agents, Sonnet 4.6 ($3/$15) as the speed-quality balance, Haiku 4.5 ($1/$5) for high-volume operations
  • -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
  • -Prompt caching cuts the cost of repeated context by 10x (cache read = 10% of base price); Batches API gives a 50% discount on non-urgent work
  • -Adaptive thinking controls reasoning depth via `output_config.effort` (low/medium/high/max); the old `budget_tokens` is deprecated as of Opus/Sonnet 4.6

Claude API is the programmatic interface for working with Anthropic’s models. One endpoint — the Messages API — covers text, images, tool use, streaming, and extended 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.

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

Three current-generation models:

ModelAPI IDContextMax outputPrice (input/output per MTok)
Opus 4.8claude-opus-4-81M128k$5 / $25
Sonnet 4.6claude-sonnet-4-61M128k$3 / $15
Haiku 4.5claude-haiku-4-5200k64k$1 / $5

Opus 4.8 — the flagship. Maximum accuracy on code, reasoning, and agentic tasks. 1M tokens of context, 128k output. Pick it when a mistake is expensive.

Sonnet 4.6 — the workhorse. Fast, smart, 1.67x cheaper than Opus on output ($15 vs $25). Covers 90% of production workloads: chatbots, content generation, document analysis.

Haiku 4.5 — speed and economy. Classification, data extraction, moderation, bulk processing. 3x cheaper than Sonnet on output ($5 vs $15).

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
  • Extended thinking — access to the reasoning trace
  • Prompt caching — caching of repeated context
  • Batches — asynchronous batch processing at a 50% discount
  • Full control — temperature, stop sequences, metadata

Quick Start

Getting an API key

  1. Sign up at console.anthropic.com
  2. Go to Settings → API Keys
  3. Create a key (starts with sk-ant-)
  4. Top up your balance under Billing (minimum $5)

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

Installing the SDK

Python:

pip install anthropic

TypeScript:

npm install @anthropic-ai/sdk

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

export ANTHROPIC_API_KEY="sk-ant-api03-..."

The first request

Python:

import anthropic

client = anthropic.Anthropic()

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

print(message.content[0].text)

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 the difference between REST and GraphQL in three sentences." }
  ]
});

console.log(message.content[0].text);

The response lives in message.content — an array of content blocks. For plain text, it’s a single block with type: "text".

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-4-6",
    max_tokens=1024,
    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 = await client.messages.stream({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  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-4-8, claude-sonnet-4-6, claude-haiku-4-5
messagesarrayYesArray of messages with user and assistant roles
max_tokensintegerYesMax tokens in the response (Opus 4.8 / Sonnet 4.6: up to 128k, Haiku 4.5: up to 64k)
systemstring/arrayNoSystem prompt — instructions for the model
temperaturefloatNoRandomness: 0.0–1.0 (default: 1.0). Lower is more deterministic
top_pfloatNoNucleus sampling: 0.0–1.0. Alternative to temperature
top_kintegerNoSampling from the top-K tokens. For advanced cases
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-4-6",
    max_tokens=1024,
    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-4-6",
    max_tokens=1024,
    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.

Stop sequences

Custom strings that stop generation when produced:

message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    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-4-6",
    max_tokens=1024,
    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 the result back as a tool_result
  5. Claude produces the final answer (or calls the next tool)

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-4-6",
    max_tokens=1024,
    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
    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-4-6",
        max_tokens=1024,
        tools=tools,
        messages=messages
    )

    print(final_response.content[0].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-4-6",
  max_tokens: 1024,
  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-4-6",
    max_tokens: 1024,
    tools,
    messages
  });

  console.log((finalResponse.content[0] as Anthropic.TextBlock).text);
}

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
{"type": "tool", "name": "get_weather"}The model must call a specific tool
{"type": "none"}Tools are disabled
# Forcing a specific tool call
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "tool", "name": "get_weather"},
    messages=[{"role": "user", "content": "Tell me about Moscow."}]
)

Forced tool use (type: "tool") is handy for structured output: describe a “tool” with the JSON structure you need — Claude fills it in.

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

Extended thinking shows how Claude reasons before answering. The model breaks the problem into steps, checks hypotheses, catches mistakes — and only then answers.

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”

Parameters

With the release of Claude 4.6, Anthropic moved to adaptive thinking — the model decides for itself how much to think. The budget_tokens parameter is deprecated as of Opus 4.6 and Sonnet 4.6; on Opus 4.7, Opus 4.8, and newer, passing it returns a 400 Bad Request.

The new syntax is thinking: {type: "adaptive"}, with depth controlled via output_config.effort:

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=16000,
    thinking={"type": "adaptive"},
    output_config={"effort": "high"},  # low | medium | high (default) | 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. At low/medium, Claude may skip thinking on simple tasks; at max, it always thinks deeply.

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 Opus 4.7+ and Opus 4.8, thinking is hidden by default (display: "omitted") — the block comes back with an empty thinking field. On earlier Claude 4 models, the default is display: "summarized" (a short summary of the reasoning). Control display explicitly: thinking: {type: "adaptive", display: "summarized"} returns the gist of the reasoning, display: "omitted" hides it and reduces latency when streaming. Billing is for the full thinking tokens regardless of the display value.

Streaming with extended thinking

Python:

with client.messages.stream(
    model="claude-sonnet-4-6",
    max_tokens=16000,
    thinking={"type": "adaptive"},
    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)

Limitations and compatibility

  • On Opus 4.7, Opus 4.8, and newer, temperature, top_p, and top_k are not accepted at non-default values (400)
  • No prefill (starting the response with an assistant message)
  • For heavy tasks at effort: max, max_tokens of at least 64k is recommended
  • For heavy tasks, the Batches API is recommended

Cost

Thinking tokens are billed as output tokens. The cost depends on the amount actually spent (not on effort — that’s just a hint). Watch usage.output_tokens_details.thinking_tokens, not just usage.output_tokens.

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 price — a 10x savings on repeated context.

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: toolssystemmessages. A change at any level invalidates it and everything after it.

TTL: two options

TTLCache writeCache readWhen to use
5 minutes (default)1.25x base price0.1x base priceFrequent requests (more often than every 5 minutes)
1 hour2x base price0.1x base priceAgentic tasks, long sessions

Minimum size for caching

ModelMinimum tokens
Opus 4.81024
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-4-6",
    max_tokens=1024,
    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

In multi-turn conversations, the SDK places cache breakpoints on its own. For most chat scenarios, explicit markup isn’t needed.

Vision (Multimodality)

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

Supported formats

JPEG, PNG, GIF, WebP. Maximum size — 20 MB per image.

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-4-6",
    max_tokens=1024,
    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."
                }
            ]
        }
    ]
)

print(message.content[0].text)

URL

message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    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

Cost depends on size. Images are scaled automatically — Claude doesn’t process pixels 1:1. Rough figures:

  • Small image (up to 384x384): ~170 tokens
  • Medium (up to 1024x1024): ~1600 tokens
  • Large (up to 2048x2048): ~6400 tokens

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-4-6",
  max_tokens: 1024,
  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."
        }
      ]
    }
  ]
});

console.log(message.content[0].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, input_json_delta
content_block_stopEnd of a content block
message_deltaFinal data: stop_reason, usage
message_stopEnd of the message

Python SDK — stream helper

import anthropic

client = anthropic.Anthropic()

with client.messages.stream(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    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, use stream.events.

TypeScript SDK — stream

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

const client = new Anthropic();

const stream = await client.messages.stream({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  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-4-6",
    max_tokens=1024,
    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.

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-4-6",
                "max_tokens": 1024,
                "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-4-6",
                "max_tokens": 1024,
                "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":
            print(f"{entry.custom_id}: {entry.result.message.content[0].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-4-6",
        max_tokens: 1024,
        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") {
    console.log(`${entry.custom_id}: ${(entry.result.message.content[0] as Anthropic.TextBlock).text}`);
  }
}

Extended output in Batches

The Batches API supports extended output: up to 300k tokens for Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6 with the beta header output-300k-2026-03-24. Useful for generating long documents, books, and reports.

Pricing and Optimization

Price table

ModelInputOutputCache write (5m)Cache readCache write (1h)
Opus 4.8$5$25$6.25 (1.25x)$0.50 (0.1x)$10 (2x)
Sonnet 4.6$3$15$3.75 (1.25x)$0.30 (0.1x)$6 (2x)
Haiku 4.5$1$5$1.25 (1.25x)$0.10 (0.1x)$2 (2x)

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

All prices are per million tokens (MTok).

Optimization strategies

1. Pick the model that matches the task

Don’t run Opus 4.8 on classification — Haiku handles it at 1/25 of the cost. Sonnet covers most production workloads. Reach for Opus when a mistake is expensive.

2. Prompt caching for repeated context

If the system prompt and context documents stay the same, cache them. A cache read costs 10% of the base price. At 100k tokens of cached context:

  • Without cache: 100k × $3/MTok = $0.30 per request (Sonnet)
  • With cache: 100k × $0.30/MTok = $0.03 per request
  • Savings: 90% 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

Don’t set max_tokens: 128000 if the answer takes 200 tokens. Claude stops before the limit, but an inflated value slows down processing.

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

As of 2026, native JSON mode is available through output_config.format — GA, no beta headers needed. It’s an alternative to forced tool use: the API guarantees a valid JSON output matching your schema.

import anthropic
import json

client = anthropic.Anthropic()

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

data = json.loads(response.content[0].text)
# {"name": "Ivan Petrov", "age": 35, "role": "senior developer"}

When to use which:

  • output_config.format — simpler, doesn’t require a tooling context, fits most cases
  • Forced tool use — when you’re already using tools in the same request, or need more complex routing logic

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.

import anthropic

client = anthropic.Anthropic()

response = client.beta.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    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
  • Available on Claude API, Claude Platform on AWS, and Microsoft Foundry; not supported on Bedrock or Google Cloud
  • 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="sk-ant-api03-...")  # key hardcoded

Structured output (JSON mode)

The recommended approach is output_config.format (covered in the dedicated section above). For compatibility with older code or trickier cases — two classic approaches:

Approach 1: Prompt instruction + prefill

message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "Extract data from this text: 'Ivan Petrov, 35, senior developer'. Return JSON with fields name, age, role."},
        {"role": "assistant", "content": "{"}  # prefill — Claude continues the JSON
    ]
)
# The result starts with { — parse it as JSON
result = "{" + message.content[0].text

Approach 2: Forced tool use

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

message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "tool", "name": "extract_person"},
    messages=[
        {"role": "user", "content": "Ivan Petrov, 35, senior developer"}
    ]
)

# Guaranteed JSON in tool_use.input
tool_block = next(b for b in message.content if b.type == "tool_use")
person = tool_block.input  # {"name": "Ivan Petrov", "age": 35, "role": "senior developer"}

Forced tool use is more reliable — Claude always returns valid JSON matching the schema.

Rate limits

Limits depend on the model and plan. Main metrics:

  • Requests per minute (RPM) — number of requests per minute
  • Tokens per minute (TPM) — number of tokens per minute
  • Tokens per day (TPD) — daily limit

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
  2. Batch processing via the Batches API — doesn’t count against RPM/TPM limits
  3. Multiple workspaces to isolate limits between projects
  4. Cache hits don’t count against rate limits — 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-4-6",
    max_tokens=1024,
    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, three models, a set of powerful features.

To get started: install the SDK, get a key, send your first request. Sonnet 4.6 covers 90% of tasks.

For production: prompt caching on repeated context, streaming for UX, Haiku for bulk operations, Batches for eval pipelines.

For hard tasks: adaptive thinking (thinking: {type: "adaptive"} + 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 4.8 when accuracy is critical.

Structured outputs: output_config.format — native JSON mode with no beta headers, GA since 2026.

Full documentation — docs.anthropic.com. 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 at console.anthropic.com, go to Settings → API Keys, and create a key. The key is tied to a workspace. Top up your balance under Billing — the minimum deposit is $5. Keys start with sk-ant-.
How much does Claude API cost?
Prices per million tokens (MTok): Opus 4.8 — $5 input / $25 output, Sonnet 4.6 — $3 / $15, Haiku 4.5 — $1 / $5. Prompt caching cuts the cost of re-reading cached content to 10% of the base price. 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, extended thinking, prompt caching, batches. You can embed Claude into any application, automate processing, and scale it.
Which Claude model should I pick?
Opus 4.8 (`claude-opus-4-8`) — for agents, hard coding tasks, and anything that needs deep reasoning (1M context, 128k output). Sonnet 4.6 (`claude-sonnet-4-6`) — the workhorse model: fast, smart, cheaper than Opus (1M context, 128k output). Haiku 4.5 (`claude-haiku-4-5`) — for classification, data extraction, and high-volume operations (200k context, 64k output).