BYOKchat Blog

Anthropic Messages API Explained

A developer-focused guide to Anthropic's Messages API: content blocks, system instructions, tool use, streaming, thinking, usage, and client architecture.

· 6 min read

On this page
  1. The basic request shape
  2. Treat content as typed data
  3. System instructions are not ordinary messages
  4. Multi-turn conversations are client-managed by default
  5. Tool use is represented in the assistant content
  6. Tool results belong to the next request
  7. Do not flatten tool rounds into fake prose
  8. Streaming is a sequence of semantic events
  9. Tool arguments can stream incrementally
  10. Thinking/reasoning should be modeled separately
  11. Round-trip provider-native blocks when required
  12. Stop reasons are control state
  13. max_tokens is a request budget, not a context window
  14. Usage accounting should be stored as provider data
  15. Prompt caching affects request construction
  16. Images and documents are content, not separate chat messages
  17. Server tools are not the same as client tools
  18. Beta features need version-aware handling
  19. Errors should be normalized, not erased
  20. A clean Anthropic adapter
  21. Test with non-text blocks
  22. Where BYOKchat fits
  23. Further reading

Anthropic’s Messages API is a structured conversation API built around messages containing typed content blocks.

That sounds similar to other chat APIs, but several details matter when you are building a reusable client:

  • the system instruction is separate from the messages array;
  • message content can be text or typed blocks;
  • tool use is represented as blocks in the assistant response;
  • tool results are sent back as user content blocks;
  • streaming is event-oriented rather than “append every string you see”;
  • thinking/reasoning features can introduce additional content or continuation requirements;
  • server tools and beta features can add more block types over time.

The right architecture is not to flatten all of that into one string.

The basic request shape

A simplified request looks like:

{
  "model": "claude-sonnet-5",
  "max_tokens": 1024,
  "system": "You are a concise technical assistant.",
  "messages": [
    {
      "role": "user",
      "content": "Explain event loops."
    }
  ]
}

The response contains a message object whose content is a list of blocks.

A simple text-only response may look conceptually like:

{
  "role": "assistant",
  "content": [
    {
      "type": "text",
      "text": "An event loop..."
    }
  ]
}

The array matters because a response can contain more than one kind of output.

Treat content as typed data

A weak client assumes:

assistant response = one text string

A stronger client assumes:

assistant response = ordered content blocks

Possible block categories can include things such as:

  • text;
  • tool use;
  • thinking/reasoning-related blocks;
  • citations or source-bearing content;
  • server-tool use/results;
  • other provider-defined blocks.

Your app does not need to expose every block directly to the user, but the adapter should understand their semantics.

System instructions are not ordinary messages

Anthropic’s Messages API uses a top-level system field rather than putting a system role into the normal message sequence.

That matters for a provider-neutral model.

Your application can represent instructions semantically:

interface ConversationContext {
  systemInstructions?: string;
  messages: PortableMessage[];
}

Then the Anthropic adapter maps:

systemInstructions → request.system
portable user/assistant turns → request.messages

Do not leak one provider’s role model into the app’s entire database schema.

Multi-turn conversations are client-managed by default

The usual pattern is to send the relevant conversation history again on each request:

{
  "messages": [
    { "role": "user", "content": "What is a mutex?" },
    { "role": "assistant", "content": "A mutex is..." },
    { "role": "user", "content": "How is it different from a semaphore?" }
  ]
}

Your client therefore still needs:

  • durable conversation storage;
  • context budgeting;
  • summarization/truncation policy;
  • edit/regenerate semantics;
  • crash recovery.

The API request is not your product database.

Tool use is represented in the assistant content

You provide tool definitions in the request. When Claude decides to use one, the assistant output contains a tool-use block rather than the final answer alone.

Conceptually:

{
  "type": "tool_use",
  "id": "toolu_123",
  "name": "get_weather",
  "input": {
    "city": "Tokyo"
  }
}

The application then:

  1. validates the requested tool and arguments;
  2. applies permission policy;
  3. executes the tool if allowed;
  4. sends the result back in a subsequent request;
  5. lets the model continue.

The model never gets direct authority to execute application code merely because it produced a tool block.

Tool results belong to the next request

A tool result is represented as a content block associated with the tool-use ID.

Conceptually:

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_123",
      "content": "18°C and clear"
    }
  ]
}

The important invariant is the correlation ID.

Your persistence layer should preserve enough provider metadata to reconnect the tool result with the exact call.

Do not flatten tool rounds into fake prose

This is a poor durable representation:

Assistant: TOOL get_weather({city: Tokyo})
User: TOOL RESULT 18°C

It loses typed semantics and makes future migration difficult.

Prefer a neutral conversation item model with explicit tool call/result records, then map to Anthropic blocks at the adapter boundary.

See How AI Tool Calling Works.

Streaming is a sequence of semantic events

Anthropic streaming uses Server-Sent Events and emits events that represent message and content-block lifecycle.

A typical stream can include concepts such as:

message_start
content_block_start
content_block_delta
content_block_stop
message_delta
message_stop

The exact delta payload depends on the block type.

This is why a renderer should not simply concatenate every string field found in the stream.

Normalize provider events into application events such as:

assistantTextDelta
reasoningDelta
toolInputDelta
toolStarted
usageUpdated
responseCompleted

Tool arguments can stream incrementally

During tool use, JSON input may arrive in pieces.

A partial fragment can look like:

{"city":"San

Do not execute the tool yet.

Wait until the block/response boundary indicates the input is complete, then parse and validate it.

See How Streaming Tool Calls Work.

Thinking/reasoning should be modeled separately

Anthropic supports thinking/reasoning features on compatible models and modes. The exact controls and block types can evolve, and not every model exposes identical behavior.

Your client should therefore represent reasoning capability separately from visible answer text.

Useful application states include:

reasoning unsupported
reasoning enabled but hidden
reasoning visible as provider summary/content
provider continuation state required

Do not assume every reasoning block should be stored or rendered as normal assistant prose.

Round-trip provider-native blocks when required

Some provider features require previous native blocks to be passed back exactly or semantically preserved across turns.

This is especially relevant for:

  • tool use;
  • server tools;
  • thinking/reasoning state;
  • beta features with special result blocks.

The adapter can preserve opaque/native metadata alongside the portable conversation.

A good rule is:

portable semantic record for product durability
+ provider-native continuation metadata when required

Stop reasons are control state

A response can finish for different reasons.

Your app should distinguish concepts such as:

normal end of turn
maximum token limit
tool use requested
provider pause/resume condition
refusal/safety outcome
failure/cancellation

Do not map every non-error HTTP response to “assistant finished successfully.”

The stop reason may tell the client that another tool or continuation round is required.

max_tokens is a request budget, not a context window

The Messages request includes an output-token budget.

That does not mean the total model context is the same value.

The client must account for:

system instructions
+ conversation history
+ tool definitions
+ attachments
+ provider-specific blocks
+ requested output budget
≤ model constraints

See Context Window vs Output Limit vs Reasoning Tokens.

Usage accounting should be stored as provider data

Usage can include input and output token counts and, depending on features, cache-related or other accounting fields.

Persist usage per request instead of trying to reconstruct it later from text length.

A provider-neutral analytics record might store:

{
  inputTokens,
  outputTokens,
  cachedInputTokens,
  providerRawUsage
}

Use optional fields rather than forcing every provider into one identical accounting shape.

Prompt caching affects request construction

Anthropic supports prompt caching features that can reward stable repeated prefixes.

Architecturally, this reinforces a useful ordering principle:

stable instructions/tool definitions first
changing conversation tail later

But do not rewrite prompts solely for cache behavior if it changes semantics.

Cache policy belongs in the provider adapter/context builder, not the UI.

Images and documents are content, not separate chat messages

Multimodal inputs fit naturally into the content-block model.

Your portable attachment representation should preserve:

  • media type;
  • source type;
  • local file identity;
  • provider upload/reference metadata where applicable;
  • lifecycle/deletion state.

Then the Anthropic adapter converts the attachment into the API’s accepted source form.

Server tools are not the same as client tools

Anthropic can expose provider-hosted tools/features in addition to tools executed by your application.

These differ in an important way:

client tool → your app receives request and performs operation
server tool → provider executes operation inside provider workflow

Privacy, observability, billing, and persistence boundaries differ.

Do not show them as identical just because both appear as tool-related content blocks.

Beta features need version-aware handling

Provider beta features can require special request headers and introduce new content-block variants.

A robust client should keep beta enablement explicit:

interface AnthropicFeatureFlags {
  betas: string[];
}

Do not globally send every known beta header.

Tie beta flags to the feature that requires them and test their removal path.

Errors should be normalized, not erased

Keep provider request IDs and raw error metadata for diagnostics, but convert errors into application categories:

authentication
permission
rate_limit
invalid_request
model_unavailable
context_too_large
provider_unavailable
network
unknown

The UI can then present consistent recovery actions without losing provider detail.

A clean Anthropic adapter

Diagram illustrating the surrounding section

The adapter owns:

  • top-level system mapping;
  • content-block encoding;
  • tool block correlation;
  • streaming event decoding;
  • reasoning/native continuation details;
  • provider error mapping.

Test with non-text blocks

A text-only test suite misses most of the difficult parts.

Include fixtures for:

multiple text blocks
tool use
streamed partial tool JSON
tool result round trip
reasoning block
unknown additive block type
max-token stop
rate limit
stream disconnect
server-tool pause/resume
usage metadata changes

Unknown block types should not crash the whole conversation parser.

Where BYOKchat fits

A multi-provider client can support Anthropic deeply without making the rest of the app Anthropic-shaped. The portable conversation owns user-visible history, while the Anthropic adapter owns Messages API content blocks, tool correlation, streaming semantics, and provider-native continuation details.

That preserves both feature depth and provider portability.

Further reading

Keep reading