Claude Streaming API: How Token Streaming Works
The Claude streaming API lets you receive a model's response as a sequence of small chunks (tokens) over an open HTTP connection, instead of waiting for the entire completion to finish before you see anything. Instead of a single blocking request that returns after 5–20 seconds, you get a live feed of text_delta events you can render to the screen as they arrive — the same experience you see in ChatGPT or Claude.ai's chat interface.
This matters for two practical reasons: perceived latency and long-running generations. A user watching text appear word by word feels the app is fast even if total generation time is unchanged. And for long outputs — code files, reports, multi-step tool chains — streaming avoids client-side or proxy timeouts that a single 60+ second blocking call can hit.
How Claude's streaming works under the hood
Claude's Messages API supports streaming via Server-Sent Events (SSE). You set "stream": true in your request, and instead of a single JSON body, the server responds with a stream of data: lines, each carrying a JSON event. The event types you'll actually parse are:
message_start— the response begins, includes initial metadatacontent_block_start— a content block (text or tool use) beginscontent_block_delta— the actual incremental content (text_deltafor text,input_json_deltafor streamed tool arguments)content_block_stop— a block finishesmessage_delta— top-level metadata updates, includingstop_reasonmessage_stop— the response is complete
A minimal raw SSE stream looks like this:
event: message_start
data: {"type":"message_start","message":{"id":"msg_01...","role":"assistant"}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":", world"}}
event: message_stop
data: {"type":"message_stop"}
Your client reads each data: line, parses the JSON, and appends delta.text to whatever buffer or DOM node is rendering the response.
Implementing it yourself with curl
You can test any streaming-compatible endpoint directly with curl using the -N flag (disable buffering) and --no-buffer:
curl -N https://api.example.com/v1/messages \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 512,
"stream": true,
"messages": [{"role": "user", "content": "Write a haiku about latency"}]
}'
You'll see the SSE events print to your terminal as they arrive rather than all at once at the end.
Implementing it in JavaScript
In a browser or Node environment, you typically consume the stream with fetch and read the response body as a stream, or use a small SSE parsing helper. Here's the general pattern using the Fetch API's ReadableStream:
const response = await fetch("https://api.example.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 512,
stream: true,
messages: [{ role: "user", content: "Explain event loops briefly" }],
}),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop(); // keep incomplete line for next chunk
for (const line of lines) {
if (!line.startsWith("data:")) continue;
const json = JSON.parse(line.slice(5));
if (json.type === "content_block_delta" && json.delta?.text) {
process.stdout.write(json.delta.text); // or append to DOM
}
}
}
This is the core logic behind every streaming chat UI: read chunks, split on newlines, parse each data: line, and append text deltas as they arrive.
Streaming with tool use
When Claude calls a tool mid-response, the streaming events look slightly different — you get content_block_start with content_block.type: "tool_use", followed by input_json_delta events that stream the tool's arguments as partial JSON fragments you need to accumulate and parse once the block closes. This is more fiddly to implement correctly than plain text streaming, since you can't safely parse the JSON until content_block_stop fires.
Streaming without building the SSE parser yourself
If you already have Claude access through a personal or team plan and just want a working streaming HTTPS endpoint without hand-rolling SSE parsing, retry logic, and timeout handling, SubToAPI turns that access into a standard API you call with an sub_live_... key. Streaming works the same way — set "stream": true and read the SSE response — but you get usage metadata, per-key logs, and team seats in one dashboard instead of managing infrastructure yourself. See the streaming docs for the exact event shapes, or the quickstart to get a key in a couple of minutes. Full request/response formats for both streaming and non-streaming calls are in the Messages API reference, and tool use with streaming is documented separately since it has its own event handling quirks.
Common pitfalls
- Buffering proxies: Nginx, some CDNs, and certain corporate proxies buffer responses by default, which defeats streaming. Disable buffering (
X-Accel-Buffering: noon Nginx) for the streaming route. - Partial JSON on tool calls: don't try to
JSON.parseinput_json_deltafragments individually — accumulate the full string and parse once atcontent_block_stop. - Reconnection: SSE doesn't automatically resume a dropped connection with context — if the connection drops mid-stream, you need to restart the request, not "resume" it.
- Rate limits still apply: streaming reduces perceived latency, not token usage or rate-limit consumption — you're still billed and limited per token generated.
FAQ
Does streaming reduce total generation time? No. The model generates tokens at the same rate either way — streaming just shows you tokens as they're produced instead of making you wait for the full response, which improves perceived speed, not raw throughput.
Can I stream and use tools in the same request? Yes. Claude streams tool_use content blocks the same way it streams text, but the tool's input arguments arrive as fragmented JSON deltas that you must accumulate before parsing — see /docs/tools for the exact event sequence.
What's the difference between SSE and WebSockets for this? SSE is one-directional (server to client) over a standard HTTP connection and works with normal fetch/curl tooling, which is why it's the format Claude's streaming API uses. WebSockets would add bidirectional complexity that isn't needed for a request-then-stream-response pattern.