◈AI Codex
Claude APIupdate

Message threads: send only the new turn, and get the first token back faster

In brief

On November 2, 2026 Anthropic released message threads in beta on the Messages API. The API holds the conversation for a few minutes, so each later request carries only the new messages and the id of the previous response. Token cost stays the same; time to first token drops, by about 35% at the median for Claude Code requests between 500k and 1M tokens. How it works, working code, the rules that return 400 errors, and the replay path you have to build.

9 min read··Latency

Contents

♡Sign in to save

On November 2, 2026 Anthropic released message threads in beta on the Messages API.

The Messages API is stateless. Every request carries the whole conversation: the system prompt, the tool definitions, and every earlier turn. For a chat that is a few kilobytes. For a coding agent that has read forty files and taken two hundred tool calls, it is hundreds of thousands of tokens, uploaded, validated, and tokenized again on every single turn. That repeated work shows up as time to first token (TTFT), the wait between sending a request and seeing the first word of the answer.

A message thread keeps the conversation on Anthropic's servers for a short time. You send the full conversation once. After that, each request carries only the new messages plus the id of the previous response, and the API rebuilds the rest before calling the model.

Anthropic's number: Claude Code turned threads on by default, and for requests between 500k and 1M tokens the median TTFT dropped by about 35%. The longer the conversation, the more it helps.

What it changes and what it does not

  • Changes: request size, and the time the API spends before the model starts. Screenshots in a computer-use loop and large documents stay with the thread instead of being re-uploaded every turn.
  • Does not change: price, rate limits, or token counts. The model still reads the full conversation on every turn, so you are billed for the full input as before.
  • Does not cache for you. Prompt caching works exactly as it does without a thread. You still place the breakpoints. Threads and caching stack: caching cuts the cost of the repeated prefix, threads cut the upload and processing time around it.

The three moves

A thread has three operations, set with one new request field, thread:

  1. Create. Your first request looks like today's, with the full messages, system, and tools, plus "thread": {"type": "create"}. The API stores the conversation, including Claude's reply, under the response id.
  2. Continue. Later requests send only the messages since the last response, plus "thread": {"type": "continue", "previous_message_id": "<last id>"}. Leave system and tools out; the thread kept them.
  3. Replay. A thread is best-effort. It expires 5 minutes after its last request and is deleted within an hour. When a continue request returns a 404 with thread_not_found, send the full conversation again in a new create request and carry on from that response's id.

Every request in a thread needs the beta header anthropic-beta: message-threads-2026-08-12.

The easy way: the SDK thread helper

The SDKs ship a helper that builds the create and continue requests, adds the beta header, and replays for you when a thread is lost:

import anthropic

client = anthropic.Anthropic()

# Creating the helper sends nothing yet.
thread = client.beta.messages.thread(
    model="claude-opus-5-5",
    max_tokens=1024,
    cache_control={"type": "ephemeral"},  # cache each turn (optional)
    system="You are a support agent for Acme. Keep replies short.",
)

# Turn 1: creates the thread and sends the full conversation
thread.send({"role": "user", "content": "Hi! I have a question about an order."})

# Turn 2: continues the thread and sends only the new message
second = thread.send(
    {"role": "user", "content": "How long does standard shipping usually take?"}
)
print(second.content[0].text)

The TypeScript version is the same shape: client.beta.messages.thread({...}) and await thread.send({...}). If you use the SDK's beta tool runner, it already uses a thread by default.

Doing it by hand

If you call the API directly, the two requests look like this:

first = client.beta.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    cache_control={"type": "ephemeral"},
    system="You are a support agent for Acme. Keep replies short.",
    messages=[{"role": "user", "content": "Hi! I have a question about an order."}],
    thread={"type": "create"},
    betas=["message-threads-2026-08-12"],
)

second = client.beta.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    cache_control={"type": "ephemeral"},
    # no system, no tools: the thread holds them
    messages=[{"role": "user", "content": "How long does standard shipping usually take?"}],
    thread={"type": "continue", "previous_message_id": first.id},
    betas=["message-threads-2026-08-12"],
)

In an agent loop, the "new messages" on a continue request are usually the tool_result blocks that answer the tool_use in the previous response.

The field rules that cause 400 errors

Request fields fall into three groups. Getting these wrong is the most likely first bug.

Group Fields Rule
Create-only system, tools Send on the create request only. Sending either on a continue request, even as null, is a 400.
Fixed model, tool_choice, thinking, mcp_servers, context_management, output_config, container skills, top-level cache_control, the set of beta headers Send on every request with the same value. A different value is a 400 thread_fingerprint_mismatch that names the field.
Free max_tokens, stop_sequences, stream, metadata, service_tier, and anything else Can change every turn.

To change a create-only or fixed field (a new tool, a different model), start a new thread with a create request.

Other rules worth knowing before you ship:

  • Each response id can be continued once. A thread is append-only. To edit or branch an earlier turn, start a new thread.
  • Do not resend messages the thread already has. The API does not de-duplicate. A continue request that begins with the previous assistant response is an error.
  • Streaming: the id arrives in message_start, but continue only after message_stop. If a stream drops, retry the same continue request; if that returns thread_already_continued or thread_continue_in_flight, replay.
  • Cache breakpoints: a thread request can carry at most 4. The top-level cache_control counts as one, and breakpoints on system and tools from the create request count again on every continue.
  • After a refusal (stop_reason: "refusal"), do not continue. Start a new thread without the turn that caused it.
  • Use the same API key and workspace for the whole thread. A request from a different key, user, or workspace gets thread_not_found.

Build the replay path first

No response tells you whether the API stored that turn. You find out on the next continue request. A thread can also be lost because it expired, because a fallback model answered, or because a response ended in a tool call cut off by max_tokens. The API returns the same 404 in every case and does not say why.

So your application stays the source of truth. Keep your own copy of every message, and write the replay path on day one:

import anthropic

def continue_or_replay(client, history, new_messages, last_id, base):
    try:
        return client.beta.messages.create(
            **base,
            messages=new_messages,
            thread={"type": "continue", "previous_message_id": last_id},
            betas=["message-threads-2026-08-12"],
        )
    except anthropic.NotFoundError as e:
        code = (e.body or {}).get("error", {}).get("details", {}).get("error_code")
        if code != "thread_not_found":
            raise  # a 404 for a bad model name looks similar; check the code
        return client.beta.messages.create(
            **base,
            system=SYSTEM, tools=TOOLS,
            messages=history + new_messages,  # full conversation from your copy
            thread={"type": "create"},
            betas=["message-threads-2026-08-12"],
        )

Here base holds the fixed fields (model, max_tokens, cache_control, and so on). If your saved history carries cache_control breakpoints on earlier messages, strip them before replaying so you stay within the limit of 4. The SDK helper does that for you, which is the main reason to use it.

What threads do not support

A request with thread is rejected (400 thread_unsupported_request) or unavailable for:

  • Message Batches and token counting. Count the full conversation instead.
  • Compaction, including compaction on demand. Threads are append-only, so to shrink a long conversation you write the summary yourself and start a new thread from it.
  • Context editing, except clear_thinking_20251015 with keep: "all".
  • Organizations with HIPAA readiness enabled, or with inference hooks turned on.

There is also no API to list, read, or delete a thread.

Data handling

Threads are not eligible for zero data retention. The conversation, system prompt, and tools are held in memory, encrypted under a server-managed key, never written to durable storage, and deleted no more than an hour after last use. Using threads does not affect your other ZDR-eligible features. If your organization has a ZDR agreement, check with whoever owns it before turning threads on.

Threads are available on the Claude API, Claude Platform on AWS, Claude Models on Google Cloud (Anthropic managed), and Claude in Microsoft Foundry. They are not on Amazon Bedrock or Vertex AI.

Should you turn it on?

  • Yes for coding agents, computer-use loops, and document- or image-heavy sessions where the conversation regularly passes 100k tokens and turns come less than five minutes apart.
  • Probably not for short chats, batch jobs, anything that relies on compaction, or a single request per conversation. The upload you save is small, and you take on the replay path.

Measure it on your own traffic. The OpenTelemetry export reports per-request latency, so you can compare TTFT with and without threads on the same workload before you roll it out everywhere.

Official docs: Message threads. Announcement: Make long-running agents faster with message threads.

Related tools

Weekly brief

For people actually using Claude at work.

Each week: one thing Claude can do in your work that most people haven't figured out yet — plus the failure modes to avoid. No tutorials. No hype.

No spam. Unsubscribe anytime.

What to read next

All articles →