Claude Messages API Threads 2026: The Beta That Cuts Your TTFT

Anthropic shipped a quiet but important change to the Messages API: message threads, now in beta on every Claude model. The pitch is simple — stop re-uploading your entire conversation on every turn. Send the full conversation once, then send only the new messages on each later request. The API keeps the conversation on its servers for a few minutes, rebuilds it before calling the model, and returns the usual response. The result, per Anthropic's own engineering reporting, is a time-to-first-token improvement of roughly 35% on long Claude Code sessions, with your token bill unchanged.

We verified every claim in this article against Anthropic's platform documentation on November 4, 2026.

What message threads actually do

The Messages API is stateless. Today, every turn re-sends the full message history — system prompt, tools, and every prior exchange — and the API validates and tokenizes all of it again. In a long agent loop that is a lot of redundant upload, and it is the dominant driver of rising TTFT as conversations grow.

With a thread, you add one thread parameter to a Messages request. On the first turn you set {"type": "create"} and send everything as usual, plus your system prompt and tools. The API stores the conversation — including Claude's reply — under the response id. On later turns you set {"type": "continue", "previous_message_id": "<previous id>"} and send only the new messages, leaving system and tools out entirely. The thread keeps them. Requests get smaller, and TTFT stays lower and steadier the longer the conversation runs — which matters most in long-running agent loops, exactly the workload where Claude Code users were feeling the drag.

The mental model is worth stating plainly: your application stays the source of truth. A thread is a server-side cache of a conversation the API already saw, held in memory for minutes, not a database. If you treat it as durable state, you will eventually lose data; if you treat it as a per-session optimization over your own copy, it is close to free money.

Pricing and rate limits: unchanged

Anthropic is explicit on this point: threads do not change pricing or rate limits. You still pay the standard per-token rates — Claude Opus 5.5 at $4/M input and $20/M output, Claude Sonnet 5.5 at $2/M and $10/M, Claude Haiku 5.5 at $0.10/M and $0.50/M, with the usual prompt-caching discounts on top. What changes is how much data crosses the wire per request, and how much of the API's per-turn validation work gets skipped. If your workload is dominated by re-uploading large histories, you should see TTFT drop noticeably even though the invoice per token is identical.

The arithmetic is straightforward. A 50-turn agent loop with a 30K-token system prompt, a 5K-token tool schema, and an average of 8K tokens of accumulated history per turn uploads roughly 2.1 million input tokens across the session without a thread. With a thread, turn 1 still uploads everything — about 43K tokens — but each of the 49 continue requests uploads only the new turn's messages, typically a few hundred tokens each. Total upload drops from 2.1M to well under 100K tokens. At Opus 5.5 input rates that is a real latency win on every turn and a meaningful reduction in billed uncached input, which is exactly where the 35% Claude Code TTFT improvement comes from.

One nuance: the thread stores the full conversation server-side, so a continue request still reads the whole history through the model — the tokens are billed as input either way once caching is accounted for. The saving is in upload bandwidth, client-side serialization, and the API's per-turn validation and tokenization work, not in a hidden discount on the model's own compute.

Getting started: the SDK helper

The easiest path is the SDK thread helper. You create it with the same fields as a normal Messages request, then feed it one turn at a time. It adds the required beta header, manages the create-then-continue flow, and keeps the full conversation in memory so it can replay when a thread dies.

import anthropic

client = anthropic.Anthropic()
thread = client.beta.messages.thread(
    model="claude-opus-5-5",
    max_tokens=1024,
    cache_control={"type": "ephemeral"},
    system="You are a support agent for Acme. Keep replies short.",
)

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

# Turn 2: the helper continues the thread and sends only the new message
second = thread.send({"role": "user", "content": "How long does standard shipping take?"})
for block in second.content:
    if block.type == "text":
        print(block.text)

If you prefer to send the thread field yourself — with cURL, the CLI, or an SDK's regular create method — add the beta header message-threads-2026-08-12 on every request of the thread:

# Turn 1: send the full conversation and create a thread
curl https://api.anthropic.com/v1/messages \
  -H "Content-Type: application/json" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: message-threads-2026-08-12" \
  -d '{
    "model": "claude-opus-5-5",
    "max_tokens": 1024,
    "system": "You are a support agent for Acme.",
    "messages": [{"role": "user", "content": "Hi! I have a question about an order."}],
    "thread": {"type": "create"}
  }'
import anthropic

client = anthropic.Anthropic()
# Turn 2: send only the new message and the id of the turn 1 response
second = client.beta.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "How long does shipping take?"}],
    thread={"type": "continue", "previous_message_id": "msg_01XFDUDYJgAACzvnptvVoYEL"},
    betas=["message-threads-2026-08-12"],
)
print(second.content[0].text)

What to send on each request

The API splits request fields into three groups. Create-only fields — system, tools, and any cache breakpoints on them — go on the create request only; sending either one on a continue request, even as null, returns a 400. Fixed fields — model, tool_choice, thinking, the top-level cache_control, and the set of anthropic-beta values — must be identical on every request of the thread; a mismatch returns a 400 with code thread_fingerprint_mismatch and names the offending fields. Free fields — max_tokens, stop_sequences, stream, metadata, service_tier — can change turn to turn. To change a create-only or fixed field, start a new thread.

Two subtleties deserve attention. First, the API does not store MCP server URLs, MCP authorization tokens, or the container id, so you must send mcp_servers and container on every request that uses them — an MCP authorization token can legitimately change between requests. Second, on the Fable 5.1, Mythos 5.1, Opus 5.5, Sonnet 5.5, and Haiku 5.5 families, changing system or tools mid-conversation invalidates any thinking and redacted_thinking blocks already in the conversation, and a create request that carries them can return a 400. The safe pattern is to leave thinking blocks out of the conversation you send; the model then answers without surfacing that earlier reasoning.

Prompt caching and streaming with threads

A thread does not cache your prompt for you — caching works as it does without a thread, and you choose where the breakpoints go. To cache each turn, send a top-level cache_control on every request of the thread; the API applies it to the last cacheable block of each request's messages. Note the limit: a thread request can carry at most 4 cache_control breakpoints, the top-level one counts as one, and breakpoints on system and tools count again on every continue request.

If you place breakpoints on message blocks yourself rather than relying on the top-level field, the rules get sharper. Breakpoints sent on the create request keep working on later turns as long as they sit on tools, on system, or on a message block before the last one. A breakpoint on the last block of a request's messages writes the cache on that request — so to cache each new turn, you need one on every request. You cannot move or remove a breakpoint after sending it, because a continue request cannot re-send an earlier message. And when you replay a conversation in a new create request, strip the breakpoints from earlier messages before you send it, keeping one on the last block, so the request stays within the 4-breakpoint ceiling. Exceeding it returns a 400 with code thread_unsupported_request.

The practical recipe most agent builders want is the simple one: a top-level cache_control: {"type": "ephemeral"} on every request of the thread. That caches each turn without you managing per-block breakpoints, at the cost of one breakpoint slot consumed per request. The only time you need finer control is when your system prompt and tool schema are enormous and you want them cached once and read repeatedly — in that case, put breakpoints on system and tools at create time and let them persist across turns, then use the top-level field for per-turn writes.

Threads work with streaming. The response id arrives in the message_start event, but continue only after you receive message_stop — an earlier continue can return a 404. If a continue stream drops before message_stop, retry the same continue request; if it returns thread_already_continued or thread_continue_in_flight, replay the full conversation in a new create request. Replaying is always safe.

The five gotchas that will bite you

1. Threads are ephemeral. A thread expires 5 minutes after its last request and is deleted no more than 1 hour after last use. If your agent pauses for a coffee break, expect to replay.

2. Threads are best-effort. No response — create or continue — tells you whether the API actually stored that turn. You find out on the next continue. Keep your own copy of every message and build the replay path from day one. The SDK helper keeps its copy in memory and saves it nowhere else.

3. Append-only. A thread is append-only and each response id can be continued exactly once. Always continue from the latest response id. To edit or branch, start a new thread from a fork; to compact, start a new thread from a summary — on-demand compaction is not available on threads.

4. Ownership is strict. A thread belongs to the organization, workspace, and user or service account that created it. A request from a different key — including a workspace key that did not create the thread — returns thread_not_found. On Claude Platform on AWS, sign every request of a thread with the same IAM role or user.

5. Refusals and fallbacks break the chain. After a stop_reason: "refusal", do not continue the thread — start a new one, leaving out or rephrasing the refused turn. With server-side fallback, the API does not store turns that a fallback model wrote, so you cannot continue from a fallback response's id; check usage.iterations for a fallback_message entry and replay the full conversation if you see one.

Threads versus the alternatives

Message threads are not the only way to reduce per-turn upload. Three alternatives are worth pricing against before you commit.

Plain prompt caching is the status quo: mark cache breakpoints on your system prompt and tool definitions, and the API serves repeat prefixes from cache at a fraction of the list input price — $0.20/M on Opus 5.5 against the $4/M uncached rate. Caching is durable across requests, works with every feature, and requires no beta header. Its weakness is exactly what threads fix: cache reads still re-validate and re-tokenize the full prefix server-side, so TTFT degrades as the conversation grows even when the cache hit rate is perfect.

Client-side conversation summarization — compacting older turns into a rolling summary — reduces upload permanently and works with any provider. The cost is quality: you are asking a model to compress context it may need later, and agent loops are unforgiving about dropped tool outputs. Threads give you the latency benefit of a short upload without the information-loss risk, because the API keeps the full conversation.

Session threads in Claude Managed Agents are the closest sibling feature. They serve the same goal — avoid re-uploading history — but they live in the managed agent harness rather than the raw Messages API, persist differently, and are designed for asynchronous long-running tasks. If you have adopted Managed Agents, use its session threads; if you are building your own agent loop against the Messages API, message threads are the native primitive. The documentation is explicit that the two are different features.

The error codes you will actually see

The documented thread errors all return before the model runs, so they consume no tokens and do not change the thread's expiry: thread_not_found (404 — replay the full conversation), thread_fingerprint_mismatch (400 — a fixed field differs), thread_already_continued (400 — each response id continues once), thread_continue_in_flight (400 — another continue from the same id is running), and thread_unsupported_request (400 — the request uses a feature threads do not support, named in error.details.unsupported). A request for a model that does not exist also returns a 404, so check error.details.error_code before you replay.

Worth internalizing: the 404 cases are not all equal. A thread_not_found right after a replay usually means the API is struggling to store that particular conversation — for example, an unusually large one. The documented fallback is to send the request without thread entirely; the SDK helper does this automatically when created with unsupported_request_behavior set to continue_unthreaded, and a tool runner that creates its own thread uses that setting by default. Some 400 thread errors carry no error_code at all — a continue request without previous_message_id, or one that includes system or tools, fails that way. Those are programming errors, not runtime conditions, and they point straight at the fix.

Design your error handling as a decision tree rather than a flat retry: on thread_not_found or thread_already_continued, replay once from your own copy; if the replayed continue returns thread_not_found again, drop the thread and continue unthreaded; on thread_fingerprint_mismatch, fix the offending field or fork a new thread; on thread_continue_in_flight, back off and let the in-flight request finish before retrying. Each branch has a distinct, correct response, and conflating them produces the retry loops that make threads feel unreliable in practice.

Who should turn threads on today

Message threads are the right default for long-running agent loops — coding agents, support agents, multi-step research workflows — anywhere the conversation history is large, turns are frequent, and TTFT compoundly dominates the user experience. Anthropic reports Claude Code sessions getting roughly 35% faster TTFT with the feature enabled, and the integration cost is one parameter plus one beta header.

The availability picture is broad. Threads are in beta on every Claude model and available on the Claude API, Claude Platform on AWS, Claude Models on Google Cloud (Anthropic managed), and Microsoft Foundry. The only hard requirement is the message-threads-2026-08-12 beta header on every request. If you are already running Claude Code, the feature is essentially a configuration change; if you are building custom agent infrastructure, the SDK helper removes most of the integration surface, leaving only the replay path and the refusal handling for you to own.

You should skip threads, for now, if your workload requires zero data retention or HIPAA eligibility (threads are neither), if your turns are minutes apart (the 5-minute expiry will force constant replays), or if your conversation state must outlive the process (the helper's copy is in-memory only). For everything else, threads are a free latency win on the same bill — turn them on, keep your replay path tested, and let the API carry the history for a change.

Sources: Anthropic Claude Platform Docs — Message threads, Using the Messages API, Pricing (all verified November 4, 2026); The New Stack, "Anthropic speeds up Claude Code with message threads" (Nov 3, 2026).