Messages API Intro
What the Messages API Is
The Messages API is the main way to interact with Claude programmatically. You send a list of conversation messages, and Claude returns the next message in that conversation.
POST https://api.anthropic.com/v1/messages
In the Python SDK, it is the client.messages resource:
message = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
)
Almost every feature in this tutorial is part of the Messages API, controlled by request parameters:
| Feature | How it is enabled in the Messages API |
|---|---|
| Conversations | Sending earlier turns in messages |
| System prompts | The system parameter |
| Streaming | client.messages.stream(...) or stream=True |
| Tool use | The tools parameter |
| Images and PDFs | Image and document blocks in messages |
| Thinking | The thinking parameter |
| Effort and structured output | The output_config parameter |
| Prompt caching | cache_control markers |
Lessons 11–20 already used the Messages API for single requests. This section (Lessons 21–30) builds a complete understanding of it: its design, its message structure, roles, and multi-turn conversations.
The Core Idea: Messages In, Message Out
The Messages API models every interaction as a conversation:
Input: [user] → [assistant] → [user] → … → [user]
│
▼
Output: the next [assistant] message
- Input: a list of alternating
userandassistantmessages, ending with ausermessage. - Output: one new
assistantmessage, theMessageobject you have been reading since Lesson 14.
Why this design: a single question is simply a conversation with one turn. A chatbot is the same request with more turns. A tool workflow is a conversation in which some turns contain tool calls and tool results. One consistent structure covers all of these cases. Once you understand messages and roles, every advanced feature fits into the same model.
Statelessness
The Messages API is stateless: it does not remember previous requests. Every request must include all the context Claude needs, including earlier turns of the conversation.
# Request 1
client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "My favorite language is Python."}],
)
# Request 2: Claude does NOT know about request 1
client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "What is my favorite language?"}],
)
To give Claude memory of a conversation, your application keeps the history and sends it each time (Lesson 24).
Earlier turns do not have to come from Claude. The API accepts synthetic assistant messages that your code writes. That is useful for examples, tests, and controlled conversation setups (Lesson 23).
Note: The documentation also describes message threads (beta), in which the API keeps the conversation so that later requests send only new messages. A thread expires 5 minutes after its last request. This tutorial uses the standard stateless approach, which works everywhere and gives you full control.
Anatomy of a Request
A request combines required and optional parameters.
Required Parameters
| Parameter | Type | Purpose | Lesson |
|---|---|---|---|
model | str | Which Claude model answers | 12 |
max_tokens | int | Upper limit on generated tokens, including thinking | 13 |
messages | list | The conversation so far, ending with a user message | 18, 23–25 |
Common Optional Parameters
| Parameter | Purpose | Lesson |
|---|---|---|
system | Instructions, role, and rules for the whole conversation | 17, 32 |
stop_sequences | Custom strings that stop generation | 48 |
metadata | Request metadata, such as {"user_id": "..."} for an opaque end-user identifier | 179 |
tools, tool_choice | Tools Claude may call, and how it should choose | 81–90 |
thinking | Thinking configuration, such as summarized thinking display | 151–153 |
output_config | Output settings such as effort and structured-output format | 13, 101–110 |
stream | Receive the response incrementally | 51–60 |
You do not need to learn all of these now. The important point is that the same endpoint handles everything from a simple greeting to a multi-tool agent, and new capabilities are added as new parameters.
Request Options That Are Not API Parameters
The SDK also accepts a few per-request options that control how the request is sent, not what Claude does:
| Option | Purpose |
|---|---|
timeout | Timeout for this request (Lesson 10) |
extra_headers | Additional HTTP headers, such as anthropic-workspace-id (Lesson 6) |
extra_body | Additional raw JSON fields for features the SDK version does not type yet |
Anatomy of a Response
The response is a Message object (Lesson 14):
| Field | Meaning |
|---|---|
id | Unique message ID |
role | Always "assistant" |
model | The model that answered |
content | A list of content blocks: text, thinking, tool use, and others (Lesson 16) |
stop_reason / stop_details | Why generation ended (Lesson 20) |
usage | Token counts for billing and monitoring (Lesson 30) |
The response is the next assistant turn. In a conversation, you append it to your history and add the next user message (Lessons 24–25).
Related Endpoints
The Messages API has companion endpoints that share the same request format:
| SDK call | Purpose |
|---|---|
client.messages.create(...) | Generate a response |
client.messages.stream(...) | Generate a response with streaming helpers |
client.messages.count_tokens(...) | Count the input tokens of a request without generating anything (Lesson 46) |
client.messages.batches.create(...) | Submit many Messages requests for asynchronous processing at reduced cost (Lessons 131–140) |
Because they share the same structure, a request you build for create() can be counted with count_tokens() or submitted in a batch with very few changes.
Messages API vs. Other Options
| Option | What it is | Use it for |
|---|---|---|
| Messages API | Direct access to Claude models with full control over each request | Custom applications, chatbots, pipelines, and your own agent loops |
| Claude Managed Agents | A pre-built, configurable agent harness that runs in Anthropic's managed infrastructure | Long-running tasks and asynchronous agent work, without building the loop yourself |
| Text Completions API | A legacy API that predates the Messages API | Nothing new: do not use it for new projects |
This tutorial focuses on the Messages API. It is the foundation for everything else, including tool use and agents.
Practical Example: Exploring the Request Parameters
This script sends variations of a request to show how optional parameters change the result, while the core structure stays the same.
"""explore_messages_api.py: see how optional parameters shape a response."""
from anthropic import Anthropic
from anthropic.types import Message
client = Anthropic()
BASE_REQUEST = {
"model": "claude-opus-5-5",
"max_tokens": 2048,
"messages": [{"role": "user", "content": "List some uses of Python, one per line."}],
}
VARIATIONS = {
"plain": {},
"with system prompt": {
"system": "Answer in exactly three short lines, no extra text.",
},
"with stop sequence": {
"system": "Number each line, starting at 1.",
"stop_sequences": ["3."],
},
"with low effort": {
"output_config": {"effort": "low"},
},
}
def get_text(message: Message) -> str:
return "".join(block.text for block in message.content if block.type == "text")
for name, extra in VARIATIONS.items():
response = client.messages.create(**BASE_REQUEST, **extra)
print(f"=== {name} ===")
print(get_text(response).strip())
print(
f"[stop_reason={response.stop_reason}, stop_sequence={response.stop_sequence}, "
f"output_tokens={response.usage.output_tokens}]\n"
)
Example output (shortened; wording varies):
=== plain ===
Web development (Django, Flask, FastAPI)
Data analysis and visualization
Machine learning and AI
...
[stop_reason=end_turn, stop_sequence=None, output_tokens=212]
=== with system prompt ===
Web development
Data science
Automation scripts
[stop_reason=end_turn, stop_sequence=None, output_tokens=64]
=== with stop sequence ===
1. Web development
2. Data analysis
[stop_reason=stop_sequence, stop_sequence=3., output_tokens=41]
=== with low effort ===
Web development
Data analysis
Automation
...
[stop_reason=end_turn, stop_sequence=None, output_tokens=58]
How It Works
BASE_REQUESTholds the three required parameters that every variation shares.VARIATIONSmaps a label to the optional parameters to add."plain": {}adds nothing, which gives a baseline.client.messages.create(**BASE_REQUEST, **extra)unpacks both dictionaries into keyword arguments. This pattern keeps the shared request in one place. Lesson 22 uses it extensively.- The system prompt variation shows how instructions shape format and length.
- The stop sequence variation stops as soon as Claude writes
3.. The response reportsstop_reason=stop_sequenceand which sequence matched. The matched text itself is not included in the output. - The low-effort variation usually uses fewer output tokens, because the model thinks less.
Common Mistakes
Expecting the API to Remember Conversations
Fix: send the full relevant history with every request (Lesson 24).
Ending messages with an Assistant Turn
What happens: a 400 error on current models, which do not support prefill.
Fix: always end with a user message (Lesson 18).
Using Old Examples with Removed Parameters
What happens: code copied from older tutorials sends temperature, top_p, or top_k, which API version 2026-10-21 rejects (Lesson 19).
Fix: remove them, and use prompting, structured outputs, and validation instead.
Creating Requests Without Checking the Response Type
What happens: code assumes every response is a finished text answer.
Fix: handle content blocks by type (Lesson 16) and check stop reasons (Lesson 20).
Best Practices
- Think in conversations: every request is a conversation, even a single question.
- Keep shared request settings in one place and add per-request variations, as in the example.
- Learn the required parameters first (
model,max_tokens,messages), then add optional features one at a time. - Use
count_tokens()to estimate the cost of large requests before sending them. - Prefer the Messages API for anything you build with fine-grained control. Consider Managed Agents for long-running autonomous tasks.
Exercise
- Add a variation named
"with metadata"that passesmetadata={"user_id": "student-42"}. Does the response change? Why might metadata still be useful? - Add a variation that combines a system prompt and low effort. Compare its
output_tokenswith the other variations. - Write two separate requests that demonstrate statelessness: tell Claude a fact in the first, and ask about it in the second. Then combine both turns into one request (with a short assistant reply in between) and compare.
The response content does not change, because metadata is not shown to the model as instructions. The
user_idis an opaque identifier that helps associate requests with your end users, for example when investigating abuse reports. Never put names, emails, or other personal data in it. Use an internal ID instead.Example variation:
"system + low effort": {
"system": "Answer in exactly three short lines, no extra text.",
"output_config": {"effort": "low"},
},
It usually produces the fewest output tokens, because both the answer and the thinking are shorter.
- In separate requests, the second answer cannot know the fact. In the combined request:
messages = [
{"role": "user", "content": "My favorite language is Python."},
{"role": "assistant", "content": "Great choice!"},
{"role": "user", "content": "What is my favorite language?"},
]
Claude answers "Python", because the earlier turns are part of the input.