Claude API Message Threads: Send Only the New Turn (Beta Guide)

TL;DR
Claude API message threads (beta, Nov 2) keep a conversation on the server for 5 minutes, so each turn sends only new messages. Setup, SDK helper, errors, limits.
Last updated: November 2, 2026
Message threads, a Claude API beta launched November 2, 2026, let you send only the new turn instead of the whole conversation. You create a thread with "thread": {"type": "create"} on the first request, then continue it with "thread": {"type": "continue", "previous_message_id": "<id>"} and just the new messages. The payoff is smaller requests and a lower, steadier time to first token in long agent loops. The catch is that a thread is best-effort: it expires 5 minutes after its last request, and your code must be able to replay the full conversation at any time.
It needs the message-threads-2026-08-12 beta header, and it works on the Claude API, Claude Platform on AWS, Claude Models on Google Cloud (Anthropic managed) and Microsoft Foundry. Everything below comes from the message threads docs and the Claude Platform release notes, read November 2, 2026.
Official Sources#
| Resource | What it confirms |
|---|---|
| Message threads docs | The thread field, the field rules, caching, errors, limits and retention |
| Claude Platform release notes, Nov 2, 2026 | Launch date, beta header, the 5-minute expiry and 1-hour deletion |
@anthropic-ai/sdk 1.4.0 on npm | The TypeScript thread helper we type-checked |
anthropic 2.4.0 on PyPI | The Python thread helper we type-checked |
Why it matters#
Without a thread, every turn of an agent loop uploads the whole conversation again, and the API validates and tokenizes all of it again. On turn 40 of a coding agent, that is a lot of bytes and a lot of repeated work before the first token. With a thread, the docs say "most of that work covers only the new turn." It is the server-side half of the problem that prompt caching solves on the model side, and the two stack.
It also changes how you think about the agent loop. The loop still owns the conversation: the docs say "your application stays the source of truth," so keep your own copy of every message. The thread is an optimization you can lose at any moment, not storage.
%% title: Claude API message thread lifecycle with the replay path
flowchart TD
A["Turn 1: full conversation, system, tools + thread create"] --> B["Response id stored, 5-minute expiry starts"]
B --> C["Next turn: new messages only + thread continue, previous_message_id"]
C -->|"200"| B
C -->|"404 thread_not_found"| D["Replay: full conversation from your own copy in a new create request"]
D --> B
C -->|"stop_reason refusal"| E["Do not continue: start a new thread without the refused turn"]Use the SDK helper#
The easiest path is the thread helper, which shipped today in @anthropic-ai/sdk 1.4.0 and anthropic 2.4.0. It adds the beta header and creates the thread on the first turn. After that it continues the thread, leaving out system and tools. It also replays once by itself when a continue request fails with thread_not_found, thread_already_continued or thread_continue_in_flight.
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
// Create the helper. This call sends no request.
const 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 creates the thread and sends the full conversation.
await thread.send({ role: 'user', content: 'Hi! I have a question about an order.' });
// Turn 2 continues it and sends only the new message.
const second = await thread.send({
role: 'user',
content: 'How long does standard shipping usually take?',
});
for (const block of second.content) {
if (block.type === 'text') console.log(block.text);
}
// The helper keeps the whole conversation in memory, and the id it continues from.
console.log(thread.messages.length, thread.previousMessageId);
The Python version is the same shape: client.beta.messages.thread(...), then thread.send(...), with thread.messages and thread.previous_message_id.
We type-checked both against the SDKs published today. The TypeScript file passes tsc --noEmit --strict (TypeScript 5, nodenext) against @anthropic-ai/sdk 1.4.0, and a misspelled previousMessageID fails with TS2551. The Python version passes Pyright in strict mode against anthropic 2.4.0. No API key was available, so we made no calls and show no output.
The helper keeps the conversation in memory only. If a conversation has to outlive your process, keep your own copy. The SDK's beta tool runner also uses a thread by default; pass thread: false (Python: thread=False) when you create it to turn that off.
Send the thread field yourself#
Without the helper, add thread and the beta header to each request:
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const BETA = 'message-threads-2026-08-12';
// Turn 1: full conversation, system and tools, plus thread: create.
const first = await 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: [BETA],
});
// Turn 2: only the new message and the previous response id. No system, no tools.
const second = await client.beta.messages.create({
model: 'claude-opus-5-5',
max_tokens: 1024,
cache_control: { type: 'ephemeral' },
messages: [{ role: 'user', content: 'How long does standard shipping usually take?' }],
thread: { type: 'continue', previous_message_id: first.id },
betas: [BETA],
});
console.log(second.id);
This also passes tsc --strict against 1.4.0, and a bogus type: 'continues' fails with TS2769. With cURL, it is the same JSON body plus -H "anthropic-beta: message-threads-2026-08-12".
The field rules#
This is where integrations will break, so it is worth reading twice. The docs sort every request field into three groups:
| Group | Fields | Rule |
|---|---|---|
| Create-only | system, tools | Send on the create request only. Sending either on a continue request, even as null, returns a 400. |
| Fixed | model, tool_choice, thinking, mcp_servers (apart from each server's authorization token), context_management, output_config, the skills in container, the top-level cache_control, and the set of anthropic-beta values | Same values on every request. A different value returns 400 thread_fingerprint_mismatch, naming the fields. |
| Free | max_tokens, stop_sequences, stream, metadata, service_tier, and other unnamed fields | Can change turn to turn. |
Three more rules that follow from it:
- Append-only, continue once. Each response
idcan be continued once. Always continue from the latest one. To edit or branch an earlier message, start a new thread. A continue request that starts with the previous response returns an error. - To change a fixed field, start over. Send the full conversation in a new create request with the new values. On Fable 5.1, Mythos 5.1, Opus 5.5, Sonnet 5.5 and Haiku 5.5, changing
systemortoolsinvalidates the earlierthinkingblocks, so leave them out of the replayed conversation. pause_turn: resume with a continue request from that response'sidand"messages": [].
Prompt caching on a thread#
A thread does not cache your prompt for you. Caching works as it does without a thread, and the docs' recipe is a top-level cache_control on every request (it is a fixed field). Two limits to know:
- A request with
threadcarries at most 4cache_controlbreakpoints. The top-level one counts, and breakpoints on the create request'ssystemandtoolscount again on every continue. One too many returns 400thread_unsupported_request. - You can't move or remove a breakpoint after you send it, because a continue request can't resend an earlier message.
If you already tuned caching, our prompt caching production guide still applies. Just budget the 4 breakpoints for the thread.
Errors and the replay path#
These errors come back before the model runs. They use no tokens and do not touch the thread or its expiry:
| Status | error_code | What to do |
|---|---|---|
| 404 | thread_not_found | Send the full conversation in a new create request |
| 400 | thread_fingerprint_mismatch | Keep the fixed fields the same, or start a new thread |
| 400 | thread_already_continued | Continue from the latest response id, or replay |
| 400 | thread_continue_in_flight | Let the other request finish, or replay |
| 400 | thread_unsupported_request | Remove the feature, or send without thread |
thread_not_found is the one you will see, and the docs list many causes:
- the thread expired
- the previous response was served but not stored, for example one ending in a tool call cut off by
max_tokens - a fallback model wrote the previous response
- the previous response is still generating
- the request comes from a different workspace, user or service account
- the new messages add something the stored conversation can't take, such as a first image
A model that does not exist also returns 404, so check error.details.error_code before you replay. If replaying still 404s, send the conversation without thread. The helper does that when you create it with unsupportedRequestBehavior: 'continue_unthreaded'. Retry-with-backoff patterns from our Claude API error handling guide still apply to the rest.
Two more cases:
- Streaming: the response
idarrives inmessage_start, but continue only aftermessage_stop. If a continue stream drops, send the same continue request again, and replay if that fails withthread_already_continuedorthread_continue_in_flight. - Refusals: after
stop_reason: "refusal", do not continue the thread. Start a new one that leaves out or rephrases the refused turn. The helper does this for you.
What threads don't support#
- Message Batches (a batch item with
threadends ininvalid_request_error) - Token counting (returns 400; count the full conversation instead)
- Compaction and compaction on demand. To compact, start a new thread from a summary you write yourself.
- Context editing, except
clear_thinking_20251015withkeep: "all" - Organizations with HIPAA readiness or Inference hooks turned on
- Listing, reading or deleting threads. There is no API for it.
Server-side fallback works, with conditions. Send fallbacks on every request of a thread or on none. A turn written by a fallback model is not stored, so you must replay after it.
Cost, limits and data retention#
- Pricing and rate limits: unchanged. The docs: "Threads do not change pricing or rate limits."
- Retention: not ZDR or HIPAA eligible. A thread is held in memory, encrypted under a server-managed key, never written to durable storage, and deleted no more than 1 hour after its last use. Files are held by reference, so keep them available while you use the thread.
- Models: the docs list support from Claude Sonnet 4.5 and Opus 4.5 through Fable 5.1 and Mythos 5.1.
Should you turn it on?#
For long agent loops and chat sessions with turns less than 5 minutes apart, yes: the change is one field plus a replay path you should already have. Skip it for single-shot requests, batch jobs, anything that relies on compaction, and any workload with ZDR or HIPAA requirements. If you are still on an older SDK, upgrade first. The 2026-10-21 SDK migration guide covers the breaking changes between you and 1.4.0.
What people are actually saying#
- The feature, the docs and both SDK releases landed on November 2, 2026. We found no Hacker News or Reddit thread about it yet.
- The likely reaction is that this is the Messages API catching up to stateful APIs elsewhere, but with a deliberate twist: Anthropic keeps the client as the source of truth and makes the server copy disposable. That avoids lock-in to server-side conversation storage, at the cost of a replay path every integration has to get right.
FAQ#
What are message threads in the Claude API?#
A beta (message-threads-2026-08-12) that keeps a conversation on the server briefly, so each later turn sends only new messages plus the previous response id.
How long does a message thread last?#
5 minutes after its last request, restarted by each stored response, and deleted no more than 1 hour after last use. A continue can 404 at any time, so build replay.
Do message threads cost extra or cache my prompt?#
No extra cost or rate-limit change, and no automatic caching. Send a top-level cache_control on every request to cache each turn.
Are message threads ZDR eligible?#
No. They are not ZDR or HIPAA eligible, and HIPAA-ready or Inference-hooks organizations cannot use them.
Which SDK versions have the thread helper?#
TypeScript @anthropic-ai/sdk 1.4.0 and Python anthropic 2.4.0, both published November 2, 2026. The docs show helpers for C#, Go, Java, PHP and Ruby too.
Continue Reading#
- Prompt Caching in the Claude API: A Production Guide - the other half of cheap, fast long conversations
- The Agent Loop Explained: How 5 SDKs Run Tools Until Done - where threads plug into your loop
- Claude API 2026-10-21: Breaking Changes and SDK Migration - get onto the SDK line that has the helper
Sources#
Get the next deep dive like this in your inbox
One email a week on claude-api and the rest of the AI dev stack. Free.
Read next on Claude Code
Prompt Caching in the Claude API: A Production Guide
Cut Claude API spend by up to 90% with prompt caching. Real numbers, TypeScript SDK code, and the gotchas Anthropic's docs gloss over.
11 min readThe Agent Loop Explained: How 5 SDKs Run Tools Until Done
An agent loop calls the model, runs tools, feeds results back and repeats until a stop rule fires. Defaults vary: 20 steps, 25 super-steps, 5, 10 or no cap.
13 min readClaude API 2026-10-21: Breaking Changes and SDK Migration
Claude API version 2026-10-21 removes temperature, top_p and top_k, and new major SDKs (TypeScript 1.0, Python 2.0) send it by default. What breaks and how to migrate.
9 min readTechnical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.





