The short version: Message Threads let the Messages API keep a conversation on the server for a short time. After the first turn you send only the new messages and the id of the previous response, so requests get smaller and Claude starts answering sooner in long conversations. It saves latency, not tokens.
- According to the Anthropic release notes, message threads are in beta on the Messages API from November 2. Requests need the beta header
message-threads-2026-08-12and athreadparameter in the body. A thread expires 5 minutes after its last request and is deleted no more than 1 hour after its last use. - According to Anthropic's announcement, message threads are already on by default for most Claude Code users, and requests with 500k to 1M tokens see median time to first token drop by about 35%. Claude still reads the full conversation on every turn, so token usage is unchanged, and so are billing and rate limits.
- We tested with an aicodewith key on November 3: through aicodewith, the
threadfield is currently ignored. Every request returns 200, but the continue turn has no earlier context and the model cannot recall what was said before. So do not use threads through aicodewith: send the full history as usual, with prompt caching. Claude Code does not send thread requests to aicodewith and keeps working normally.
Create an API Key and connect Claude in Claude Code, Codex or Cursor →
What Message Threads are
The Messages API does not remember conversations. Without a thread, an agent re-sends every earlier message and tool result on every turn, and the API validates and tokenizes all of it again. Once a conversation reaches hundreds of thousands of tokens, that alone delays the first token.
According to the official docs, a message thread works like this: the first request is sent as usual, with the full conversation, system and tools, plus "thread": {"type": "create"}. The API stores the conversation, including Claude's reply, under the response id. On each later turn you send only the messages since the previous response, plus "thread": {"type": "continue", "previous_message_id": "..."}, and the API rebuilds the full conversation before calling the model. The response is the usual message.
| Without a thread | With a thread | |
|---|---|---|
| Request body per turn | Full history + system + tools | Full on turn 1; then only new messages + previous response id |
| Time to first token | Grows with the conversation | Lower and steadier, more so as the conversation grows |
| Token usage | Full conversation read each turn | The same, the full conversation is still read each turn |
| Billing and rate limits | Standard | Unchanged |
| Who keeps the conversation | Your app | The server for 5 minutes; your app still keeps a full copy |
Also, according to @dotey's summary, Anthropic's numbers show the median request size in long conversations falling from about 800 KB to 1.8 KB. The @ClaudeDevs announcement had about 209,000 views, 3,681 likes and 1,041 bookmarks by early November 3 (Beijing time) and made X trends.
How to use it: the thread parameter and the beta header
Every request of a thread needs anthropic-beta: message-threads-2026-08-12. These examples target the official Anthropic API (see the next sections for aicodewith).
curl
# Turn 1: full conversation + system (+ tools), create a thread
curl -sS https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: message-threads-2026-08-12" \
-H "content-type: application/json" \
-d '{
"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"}
}'
# Turn 2: only the new message + the id of the turn 1 response
# (no system / tools here: the thread keeps them)
curl -sS https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: message-threads-2026-08-12" \
-H "content-type: application/json" \
-d '{
"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": "'"$MSG_ID"'"}
}'
Save the id of the first response as MSG_ID and continue from it on turn 2. Always continue from the latest response id; each id can be continued only once.
Python
The official SDK has a thread helper, client.beta.messages.thread(...). It adds the beta header, leaves system and tools out of continue requests, and replays the full conversation once on a 404. You can also pass thread to create yourself:
import anthropic
client = anthropic.Anthropic() # official API, reads ANTHROPIC_API_KEY
# Option A: the SDK thread helper (adds the beta header, replays on 404 for you)
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.",
)
thread.send({"role": "user", "content": "Hi! I have a question about an order."})
second = thread.send({"role": "user", "content": "How long does standard shipping usually take?"})
print(second.content[-1].text)
# Option B: send the thread field yourself
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"},
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"],
)
TypeScript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic(); // official API, reads ANTHROPIC_API_KEY
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.",
});
await thread.send({ role: "user", content: "Hi! I have a question about an order." });
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);
}
What to send on each request
The official docs split the fields into three groups:
| Group | Fields | Rule |
|---|---|---|
| Create only | system, tools | Sending either on a continue request, even as null, returns a 400 |
| Fixed | model, tool_choice, thinking, mcp_servers, context_management, output_config, top-level cache_control, the set of anthropic-beta values and more | Send on every request with the same value as on create, or get a 400 thread_fingerprint_mismatch |
| Can change per turn | max_tokens, stop_sequences, stream, metadata and others | Change them as you like |
To change the system prompt, the tools or the model, send the full conversation in a new create request and start a new thread.
Expiry and replay
A thread is best-effort. It expires 5 minutes after its last request; after that a continue request returns a 404 with error.details.error_code set to thread_not_found. Send the full conversation from your own copy in a new create request, then continue from the new id. So always keep a full local copy of the conversation and build the replay path from the start. The official SDK helper replays once for you.
Does it save money? No, it saves latency
According to the official docs, threads do not change billing or rate limits. Requests get smaller, but Claude still reads the full conversation every turn, so input tokens are the same as before.
To avoid reprocessing the same tokens, you need prompt caching. A thread does not cache for you: the official examples send a top-level cache_control: {"type": "ephemeral"} on every request, so each turn reads the earlier conversation from the cache and writes the new turn. The two work together: threads cut the upload, caching cuts the repeated processing.
Good fits, according to Anthropic's announcement: long-running coding agents, computer use agents with a screenshot every turn, and document- and image-heavy conversations. For short chats the gain is small.
What changed in Claude Code
According to Anthropic's announcement, message threads are already on by default for most Claude Code users, which is where the 35% figure comes from. Users do not need to change any setting.
We logged, through a local proxy, the requests Claude Code 2.1.316 sends when it points at aicodewith with a custom ANTHROPIC_BASE_URL. A task that read two files made 3 requests, and none had a thread field or message-threads-2026-08-12 in the beta header. The message count grew 2, 4, 6, each request carried the full history at about 46 KB, and the task finished normally. In other words, with a third-party base URL Claude Code keeps working the old way and is not broken by threads.
Setup:
export ANTHROPIC_BASE_URL="https://api.aicodewith.ai"
export ANTHROPIC_AUTH_TOKEN="YOUR_API_KEY"
claude --model claude-opus-5-5
For persistent settings and Windows, see How to connect Claude Code to a third-party API. For a bit more speed in long sessions you can lower effort, see How to choose Claude Opus 5.5 effort.
Message Threads is a Claude Messages API feature; Codex talks to OpenAI's API and is not involved. The Codex SDK also has "threads", but those resume a client-side session, which is a different thing from this server-side storage. See Getting started with the Codex SDK: TypeScript calls, streaming events and thread resume.
Does it work through aicodewith? Test results
Early on November 3, 2026 (Beijing time), we called aicodewith's Anthropic-compatible endpoint https://api.aicodewith.ai/v1/messages with an aicodewith key, the claude-opus-5-5 model and the message-threads-2026-08-12 header, following the official examples case by case:
| Test | Expected on the official API | Actual through aicodewith |
|---|---|---|
| create: full conversation + thread: create | 200, thread created | 200, normal reply |
| continue: new message + previous id | 200, model sees earlier turns | 200, but input_tokens was 27 and the model saw no earlier turns |
| continue the same id a second time | 400 thread_already_continued | 200 |
| previous_message_id that does not exist | 404 thread_not_found | 200 |
| continue with system | 400 | 200 |
| Python SDK 2.4.0 thread helper | Turn 2 remembers turn 1 | Turn 2 said it had no order number |
The real reply on the continue turn (excerpt):
Turn 1 (create): "Hi! I have a question about an order."
-> 200, "Happy to help. What's your order number, and what's the question about it?"
Turn 2 (continue, previous_message_id = turn 1 id):
"How long does standard shipping usually take? Also, what did I say my question was about?"
-> 200, input_tokens: 27
-> "...As for your question, you didn't say what it was about beyond asking about standard shipping..."
Conclusion: aicodewith does not support message threads yet and treats the thread field as if it were not there; each request is handled as a standalone request. The catch is that nothing fails: the SDK helper never gets a 404, never replays, and the conversation silently loses its context. So through aicodewith, do not add thread to requests and do not use client.beta.messages.thread(...).
Workaround: full history + prompt caching
Send the full history every turn as usual and add a top-level cache_control to turn on automatic caching. We ran three turns with the same aicodewith key (a system prompt of about 7,500 tokens):
| Turn | Input (uncached) | Cache write | Cache read | Time | Reply |
|---|---|---|---|---|---|
| 1 | 24 | 7,568 | 0 | 4.0 s | Normal |
| 2 | 8 | 1,541 | 6,163 | 4.4 s | Normal |
| 3 | 8 | 1,560 | 6,240 | 3.5 s | "Your order number is A-1042." |
Turns 2 and 3 hit the cache, and turn 3 correctly recalled the order number from turn 1. In Python:
import anthropic
client = anthropic.Anthropic(
base_url="https://api.aicodewith.ai",
api_key="YOUR_API_KEY",
)
SYSTEM = "You are a support agent for Acme. Keep replies short.\n\n" + open("policy.md").read()
history = [{"role": "user", "content": "Hi! My order number is A-1042 and it hasn't shipped yet."}]
def turn(next_user_message=None):
r = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
cache_control={"type": "ephemeral"}, # automatic caching: moves forward each turn
system=SYSTEM,
messages=history, # full history, no thread field
)
history.append({"role": "assistant", "content": [b.model_dump(exclude_none=True) for b in r.content]})
if next_user_message:
history.append({"role": "user", "content": next_user_message})
u = r.usage
print(u.input_tokens, u.cache_creation_input_tokens, u.cache_read_input_tokens)
return r
turn("When will it ship?")
turn("What was my order number again?")
turn()
TypeScript is the same idea, with the full history in messages:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
baseURL: "https://api.aicodewith.ai",
apiKey: "YOUR_API_KEY",
});
const history: Anthropic.MessageParam[] = [
{ role: "user", content: "Hi! My order number is A-1042 and it hasn't shipped yet." },
];
const r = await client.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: history, // full history every turn, no thread field
});
history.push({ role: "assistant", content: r.content });
console.log(r.usage);
How to check whether an endpoint really supports threads
Send a continue request with a previous_message_id that cannot exist. The official API returns 404 thread_not_found; a 200 with a normal reply means the endpoint ignores thread:
# continue from an id that cannot exist
curl -sS https://api.aicodewith.ai/v1/messages \
-H "x-api-key: YOUR_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: message-threads-2026-08-12" \
-H "content-type: application/json" \
-d '{"model":"claude-opus-5-5","max_tokens":200,"cache_control":{"type":"ephemeral"},
"messages":[{"role":"user","content":"Hi"}],
"thread":{"type":"continue","previous_message_id":"msg_doesnotexist123"}}'
# official API: 404, error.details.error_code = "thread_not_found"
# a gateway that ignores thread: 200 and a normal reply
When aicodewith adds thread support, this command will show it without touching your application code.
Common errors
According to the official docs, these errors come back before the model runs. They use no tokens, and the thread and its expiry time do not change.
404 thread_not_found
The thread expired (no request for 5 minutes), the previous turn was not stored, a fallback model wrote the previous response, the previous response is still generating, or the request uses a different API key, organization or workspace. Fix: send the full conversation in a new create request. A request for a model that does not exist also returns 404, so check error.details.error_code before you replay:
{
"type": "error",
"error": {
"type": "not_found_error",
"message": "...",
"details": { "error_code": "thread_not_found" }
},
"request_id": "req_..."
}
400 thread_fingerprint_mismatch
A fixed field differs from the create request, for example a different model or thinking, a changed top-level cache_control, or one beta header more or less. The error message names the fields. Start a new thread to change them.
400 thread_already_continued / thread_continue_in_flight
Each response id can be continued once, or an earlier continue request is still running. Always continue from the latest response id. With streaming, wait for the message_stop event before continuing; continuing earlier can return a 404.
400 thread_unsupported_request
The request uses something threads do not support: more than 4 cache_control breakpoints (the top-level one counts), on-demand compaction, most context editing, or an organization with HIPAA readiness or Inference hooks. Message Batches and token counting do not accept thread either. Remove the feature, or send the full conversation without thread.
A continue request includes system or tools
Returns a 400 without an error_code. Send only the new messages on continue; the thread keeps system and tools.
Through aicodewith the continue turn lost context
That is because aicodewith currently ignores thread, and no error is raised. Remove thread and go back to full history + cache_control, as shown above.
Data retention and limits
- According to the official docs, a thread is held in memory, encrypted, never written to durable storage, and deleted no more than 1 hour after its last use. Threads are not eligible for zero data retention (ZDR) or HIPAA.
- There is no API to list, read or delete a thread.
- Available on the Claude API, Claude Platform on AWS, Claude Models on Google Cloud (Anthropic managed) and Claude in Microsoft Foundry, in beta on every model.
- Separately, according to reports, Claude's text watermark covers more models from November 3. It is unrelated to threads and needs no request changes, see Claude watermark from November 3.
Summary
Message Threads fix the latency of re-sending the whole history on every turn: create a thread on the first turn, then send only new messages and the previous response id. The first token comes sooner, token usage and billing stay the same, and you still keep a full local copy so you can replay after the 5-minute expiry. Long-running agents on the official API should turn it on. Through aicodewith, thread is currently ignored, so send the full history with cache_control; Claude Code on aicodewith needs no changes.
Create an API Key and start in Claude Code, Codex or Cursor →



