How Server-Sent Events Power Claude's Streaming Responses
When you call the Claude Messages API without streaming, you send a request and wait.
Search across all documentation pages
When you call the Claude Messages API without streaming, you send a request and wait.
Nothing comes back until the entire response is finished generating.
For a long answer, that wait can be several seconds of a blank screen.
Streaming changes the delivery mechanism, not the content: instead of one big response at the end, Claude sends the same response as a sequence of small, typed events over a single open connection, as the words are generated.
Understanding the transport underneath streaming - Server-Sent Events - and the shape of the events it carries is the foundation for everything else in this section, from printing tokens as they arrive to building a live chat UI to streaming tool calls.
message_start, content_block_delta, text_delta, message_delta, message_stop.Server-Sent Events (SSE) is a long-standing web standard for one-directional, server-to-client streaming over plain HTTP.
A client opens a normal HTTP request, but instead of the server sending one response and closing the connection, the server keeps the connection open and writes a sequence of small text frames to it over time.
Each frame is prefixed with data: and separated by blank lines - it is deliberately simple, simpler than WebSockets, because it only needs to flow in one direction: server to client.
Claude's Messages API uses this exact mechanism when you request stream=true (or use the SDK's streaming helper).
Instead of one HTTP response body containing the finished message, the connection stays open and the server writes a series of events, each one a small JSON object describing one incremental piece of the response.
A useful analogy: a non-streaming response is like waiting for a whole letter to be printed and then handed to you.
A streaming response is like watching someone type that same letter in front of you, one line at a time, through a window that tells you exactly what kind of thing just got typed - a heading, a sentence, a signature.
The event types are what make that useful: each one names precisely what changed, so client code never has to guess.
import anthropic
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Explain SSE in one paragraph."}],
) as stream:
for event in stream:
print(event.type) # message_start, content_block_delta, ..., message_stopThat loop is the shape every streaming consumer takes: open a stream, receive a sequence of typed events, and react to each type differently.
The event sequence for a typical text response follows a fixed skeleton:
message_start
content_block_start (index 0, type: text)
content_block_delta (text_delta, "Hello")
content_block_delta (text_delta, " there")
content_block_delta (text_delta, "!")
content_block_stop (index 0)
message_delta (stop_reason, usage totals)
message_stop
message_start arrives first and carries the message's metadata shell - its id, model, and initial (near-zero) usage - before any content exists yet.
Between that and message_stop, the response is built out of one or more content blocks, each opened with content_block_start and closed with content_block_stop.
A single response can contain multiple content blocks in sequence - for example a thinking block followed by a text block, or a text block followed by a tool_use block - and each is identified by its index so a client can track several blocks' partial state independently.
The actual token-by-token output arrives as content_block_delta events nested inside a block's start/stop pair.
For a text block, the delta's delta.type is text_delta and its delta.text is the next fragment of text - concatenating every text_delta for a block, in order, reconstructs that block's full text.
content_block_delta is a generic envelope, though - the delta.type inside it varies by what kind of block is streaming: text_delta for prose, input_json_delta for a tool call's arguments, and thinking_delta for extended thinking, each carrying a different partial payload that gets assembled differently.
message_delta arrives once, near the end, and carries message-level fields that were not known until generation finished - stop_reason (why the model stopped) and running token usage totals.
message_stop is the final event and signals that the stream is complete; no more events will follow for this response.
A common reasoning mistake is expecting one content_block_delta to equal one token, or one text_delta to be a clean word boundary.
Neither is guaranteed - a delta can be a partial word, several tokens, or even split mid-character-sequence for multibyte text - so client code should treat deltas as opaque fragments to concatenate, never as units to parse individually.
The event model scales to Claude's more advanced capabilities without changing its shape, which is what makes it composable.
A tool call streams its input as a JSON string built up incrementally across many input_json_delta events - the partial string is not valid JSON until the block closes, so a client must buffer every delta for that block and only attempt to parse once content_block_stop fires for it. See Handling Partial JSON During Streamed Tool Calls for the accumulation pattern.
Extended thinking, when enabled, streams its reasoning as a separate content block using thinking_delta events, ahead of the text block - a UI can render that block distinctly (e.g., a collapsed "reasoning" panel) rather than mixing it into the answer text. See Streaming Extended Thinking Blocks to the Frontend.
Because tool calls stream through the same event types as text, a client can watch one stream for both: render text as it arrives, and the moment a content_block_start reports type: "tool_use", know it is time to accumulate arguments instead of displaying them - this is the basis of combining streaming with the tool-use loop.
| Approach | Strength | Weakness | Best Fit |
|---|---|---|---|
| Non-streaming request | Simplest client code; one parse of one JSON object | User waits for the full response before seeing anything | Background jobs, batch generation, server-to-server calls with no live viewer |
| SSE streaming | Renders output as it's generated; lower perceived latency | Requires event-type handling, partial-state buffering, reconnect logic | Chat UIs, coding assistants, any surface a user is watching in real time |
| Streaming + tool use | Same responsiveness, plus mid-response tool calls | Must distinguish text blocks from tool_use blocks, buffer JSON deltas | Agentic loops where the user should see reasoning/text before a tool result returns |
At the transport level, SSE rides over the same HTTP connection as any other API call, so it inherits ordinary network failure modes - proxies that buffer output, load balancers with idle timeouts, and connections that simply drop mid-response.
Production code has to treat the stream as unreliable: buffer what has arrived, detect a drop, and decide whether to resume or restart the request. See Streaming Best Practices for the concrete patterns.
text_delta can span multiple tokens or be a sub-token fragment - never assume a fixed granularity, always concatenate.content_block_delta always means text." It's a generic envelope - its inner delta.type determines whether it's text_delta, input_json_delta, or thinking_delta.stream=true (or the SDK's streaming helper) changing only how the response is delivered.Server-Sent Events. It's a general web standard (not an Anthropic invention) for one-way, server-to-client streaming over HTTP, also used by many other APIs and web frameworks.
No. The official anthropic Python SDK's client.messages.stream(...) context manager parses the SSE frames for you and yields typed event objects - you work with event.type and event.delta, not raw data: lines.
message_start. It carries the message shell - id, model, role - before any content block has begun.
The message_stop event is always last. Between message_delta (which carries stop_reason and final usage) and message_stop, the response is effectively complete.
Yes. A response can contain multiple blocks in sequence - for example a thinking block, then a text block, then a tool_use block - each with its own content_block_start/content_block_stop pair and index.
No. Treat each delta as an opaque text fragment and concatenate them in order; never assume it starts or ends on a whitespace or word boundary.
content_block_delta carries incremental content inside one block (text, tool JSON, or thinking). message_delta carries message-level information that is only known once generation is finishing, like stop_reason and cumulative token usage.
The block's content_block_start reports type: "tool_use" instead of type: "text", and its deltas arrive as input_json_delta (a growing JSON string) instead of text_delta.
Each input_json_delta is a fragment of a JSON string under construction - it is not valid JSON on its own. You must accumulate every fragment for that block and parse the joined string only after the block closes.
No. The request body and the model's output are the same either way - streaming changes only the delivery mechanism, not the content generated.
You get whatever events arrived before the drop and no message_stop. Production clients need to detect this (e.g., a timeout with no new events) and decide whether to retry the request, since SSE itself does not guarantee delivery of the remaining events.
No, both work in non-streaming requests too. Streaming is purely a delivery-mechanism choice that happens to also expose tool and thinking content incrementally when enabled.
input_json_delta safely.thinking_delta.Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the official
anthropicPython SDK (latest 0.x release). Model names, SDK versions, and pricing move quickly - verify current specifics at platform.claude.com/docs before relying on them.
Reviewed by Chris St. John·Last updated Jul 15, 2026