Claude Certification Guides
CCDV-F/Domain 29 min read

Lesson 2.1

The Messages API

The anatomy of a Messages API request and response, the rules for roles and content blocks, and the fields you must read on every reply.

Everything you build with Claude goes through one endpoint: POST /v1/messages. Tools, vision, caching, structured output and streaming are all options on this single call. The exam expects you to know the required fields, the shape of a response, and the handful of rules that produce 400 errors when broken.

The request

A minimal request has three required fields: model, max_tokens and messages.

{
  "model": "claude-opus-5-5",
  "max_tokens": 1024,
  "system": "You are a concise technical writer.",
  "messages": [
    {"role": "user", "content": "Explain idempotency in one paragraph."}
  ]
}
  • model is a model ID or alias. IDs change as models are released, so read them from the models page on docs.anthropic.com rather than memorising them.
  • max_tokens is the hard ceiling on output tokens. It is enforced, not a target; if the reply hits it, stop_reason becomes max_tokens and the output is truncated.
  • messages is the conversation. The first message must have role: "user", and roles are user or assistant. Consecutive same-role messages are merged into one turn.
  • system is a top-level field, not a message. It sets persona, rules and context for the whole conversation.

Optional fields you will meet repeatedly: temperature (randomness, where the model supports it), stop_sequences (strings that end generation), tools and tool_choice, metadata.user_id (an opaque identifier for abuse detection), stream, and thinking for extended reasoning on models that support it.

Content: strings or blocks

content can be a plain string or an array of typed blocks. A string is shorthand for one text block. Blocks are how you send images, documents and tool results, and how Claude returns text, tool calls and thinking.

{"role": "user", "content": [
  {"type": "image", "source": {"type": "url", "url": "https://example.com/chart.png"}},
  {"type": "text", "text": "What trend does this chart show?"}
]}

Response blocks you must recognise: text, tool_use (with id, name, input), and thinking. Request-only blocks include image, document and tool_result.

The response

{
  "id": "msg_01XFDUDYJgAACzvnptvVoYEL",
  "type": "message",
  "role": "assistant",
  "model": "claude-opus-5-5",
  "content": [{"type": "text", "text": "Idempotency means..."}],
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "usage": {"input_tokens": 31, "output_tokens": 98,
            "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}
}

Read three things on every response. content is always an array, so iterate and branch on type rather than assuming content[0].text. stop_reason tells you whether the turn finished (end_turn), wants tools (tool_use), was cut off (max_tokens), hit a stop_sequence, paused (pause_turn) or was declined (refusal). usage is your billing record and the only reliable way to verify prompt caching.

Calling it from the SDKs

import anthropic
client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY

resp = client.messages.create(
    model=MODEL, max_tokens=1024,
    system="You are a concise technical writer.",
    messages=[{"role": "user", "content": "Explain idempotency in one paragraph."}],
)
print(next(b.text for b in resp.content if b.type == "text"))
print(resp.usage.input_tokens, resp.usage.output_tokens)
const resp = await client.messages.create({
  model: MODEL, max_tokens: 1024,
  system: "You are a concise technical writer.",
  messages: [{ role: "user", content: "Explain idempotency in one paragraph." }],
});
for (const block of resp.content) if (block.type === "text") console.log(block.text);

With raw HTTP you send x-api-key, anthropic-version: 2023-06-01 and content-type: application/json headers. The SDKs add these for you.

Multi-turn conversations

The API is stateless. To continue a conversation you resend the entire history with the new user turn appended:

history = [
    {"role": "user", "content": "My name is Priya."},
    {"role": "assistant", "content": "Nice to meet you, Priya."},
    {"role": "user", "content": "What is my name?"},
]

Appending the previous resp.content array (not just its text) as the assistant turn preserves tool calls and other blocks that later turns may depend on.

Common validation errors

MistakeResult
First message is assistant400 invalid_request_error
Missing max_tokens400
Empty messages array400
Unknown or unavailable model ID404 not_found_error
system placed inside messages as a role on older models400

Exam focus

  • model, max_tokens and messages are required; system is a separate top-level field, not a message role.
  • The first message must be from user; the API merges consecutive same-role messages.
  • content is an array of typed blocks in responses; always branch on type.
  • max_tokens is an enforced ceiling, and stop_reason: "max_tokens" means the output was truncated.
  • The API is stateless: resend the full history each turn, appending the full assistant content array.
  • usage reports input, output and cache token counts and is the source of truth for cost.

Check yourself

5 questions

  1. 1.A developer sends this request body to POST /v1/messages and receives a 400 invalid_request_error: { "model": "<current-model-id>", "messages": [{"role": "user", "content": "Hello"}] } Which required parameter is missing?

  2. 2.Coming from another provider, a developer writes: messages = [ {"role": "system", "content": "You are a terse SQL assistant."}, {"role": "user", "content": "List all tables."} ] What happens when this is sent to the Messages API?

  3. 3.A response from the API looks like: {"id": "msg_01", "type": "message", "role": "assistant", "content": [{"type": "text", "text": "There are 12 tables."}], "stop_reason": "end_turn", "usage": {"input_tokens": 40, "output_tokens": 9}} The developer's code does console.log(response.content.text) and prints undefined. What is wrong?

  4. 4.A developer calls the API with raw HTTP: curl https://api.anthropic.com/v1/messages \ -H "Authorization: Bearer sk-ant-api03-..." \ -H "content-type: application/json" \ -d '{"model": "...", "max_tokens": 256, "messages": [...]}' They get a 401 authentication_error. Which change fixes the request?

  5. 5.A request sets stop_sequences: ["</answer>"]. The response has stop_reason "stop_sequence" and stop_sequence "</answer>". What happened?