Claude API Streaming: Server-Sent Events Example
If you're searching for a Claude API streaming server-sent events example, you want to see tokens arrive incrementally instead of waiting for a full response. Claude's streaming mode uses the SSE (Server-Sent Events) protocol: the server keeps the HTTP connection open and pushes small data: chunks as the model generates text, instead of returning one big JSON blob at the end.
This matters for any chat UI, CLI tool, or voice assistant where perceived latency matters more than total completion time. A user watching words appear feels like the app is faster, even if the total generation time is identical. Below is a working example of how the request, response format, and client-side parsing actually look.
How Claude API Streaming Works
When you set "stream": true in your request, the API responds with Content-Type: text/event-stream and sends a sequence of named events rather than a single payload. Each event looks like this:
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}
You'll typically see events in this order:
message_start— metadata about the message (id, model, role)content_block_start— a new content block beginscontent_block_delta— incremental text chunks (the bulk of the stream)content_block_stop— the block is finishedmessage_delta— final usage/stop_reason infomessage_stop— the stream is done
Your client needs to parse each data: line as JSON, check the type field, and append delta.text to whatever buffer you're rendering.
A curl Example
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 512,
"stream": true,
"messages": [
{"role": "user", "content": "Write a haiku about streaming APIs"}
]
}'
You'll see raw SSE lines print to your terminal as they arrive. This is useful for debugging, but production code needs a proper SSE parser.
A JavaScript Streaming Client
Here's a minimal browser/Node example using fetch and a ReadableStream reader:
async function streamMessage(prompt) {
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": process.env.ANTHROPIC_API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
body: JSON.stringify({
model: "claude-3-5-sonnet-20241022",
max_tokens: 512,
stream: true,
messages: [{ role: "user", content: prompt }],
}),
});
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 payload = line.replace("data:", "").trim();
if (!payload) continue;
const event = JSON.parse(payload);
if (event.type === "content_block_delta") {
process.stdout.write(event.delta.text); // or append to UI state
}
}
}
}
The key detail most examples skip: SSE chunks don't always align with line boundaries. You must buffer partial lines and only parse complete ones — otherwise you'll get JSON.parse errors on truncated payloads.
Common Pitfalls
- Forgetting to handle
pingevents. Anthropic sends periodic keep-alive pings during long generations. Your parser should silently skip unknown event types rather than throwing. - Not setting
max_tokenshigh enough. A stream that stops mid-sentence because it hit the token limit looks like a bug but is just truncation. - Buffering the whole response before rendering. Some frameworks (certain proxy setups, some serverless functions) buffer the entire response body before forwarding it to the client, which defeats the purpose of streaming. Test behind your actual deployment target, not just locally.
- Ignoring
stop_reason. Check it in the finalmessage_deltaevent — values likemax_tokens,stop_sequence, orend_turntell you why generation ended.
Streaming Through a Proxy or Gateway
If you're routing Claude requests through your own backend (for key management, logging, or rate limiting), you need to make sure your proxy forwards the stream chunk-by-chunk rather than collecting the full response first. This is a common mistake with Express middleware or Nginx configs that buffer by default — you have to explicitly disable buffering for SSE routes.
SubToAPI handles this for you if you'd rather not build and maintain a streaming proxy. It exposes your Claude access as a hosted HTTPS API with sub_live_... application keys, and streaming works the same SSE way as the raw Anthropic endpoint — the parsing logic above is portable. You get usage metadata per request and team seats without writing your own passthrough layer. See the streaming docs and quickstart for the exact request shape, or check messages for the non-streaming equivalent. Plans start at pricing with a free trial at signup.
questions
Does Claude API streaming use WebSockets or SSE? Server-Sent Events over a standard HTTP connection, not WebSockets. You send one POST request with stream: true and read the response body as a continuous stream of data: lines.
Why does my stream hang or buffer until the end? Usually an intermediary (reverse proxy, serverless runtime, or Node framework) is buffering the response instead of flushing chunks immediately. Disable response buffering for that route and confirm with curl -N locally.
Can I cancel a stream mid-generation? Yes — closing the underlying connection (aborting the fetch request or the HTTP socket) stops the server from sending further tokens, though you may still be billed for tokens already generated up to that point.