What you will be able to do
- Explain how a streamed Messages response travels as server-sent events on the HTTP response, rather than over a separate bidirectional socket
- Put the SSE events of a stream in order and assemble text, tool-input, thinking and usage deltas correctly
- Handle errors that arrive mid-stream, and use SDK streaming helpers to avoid HTTP timeouts on long outputs
1.One HTTP request, streamed back: SSE rather than a websocket
A streamed Claude response is an ordinary Messages API call with "stream": true in the body. You send the same POST to /v1/messages with the same headers. The difference is in how the response comes back: a series of server-sent events (SSE) delivered as soon as they are produced, instead of one JSON object at the end. Each event has a name, such as event: message_stop, and a JSON data payload that repeats that type.
Teams used to chat backends often expect a websocket here: a persistent, two-way channel the client keeps open and pushes new user messages into. The Messages API is not built that way. It is stateless, so every turn is a new request that carries the whole conversation history. Streaming only changes how that one request's answer arrives: the HTTP connection stays open while events flow from server to client. A chat feature therefore sends a new request per user turn and reads each answer as an event stream.
Checkpoint 1 of 8· Check yourself
A mobile chat app wants a user's follow-up message to reach Claude over the connection it already used to stream the previous answer. How does the Messages API handle conversation turns?
The API is stateless and the client resends the history on every call. Streaming delivers the answer to that single request as server-sent events.
“The Messages API is stateless, which means that you always send the full conversational history to the API.”Source: platform.claude.com
Sources1
2.The order of events in a stream
Every stream follows the same overall structure, which lets a parser track its position. message_start comes first and carries a Message object whose content is still empty. Each content block follows with a content_block_start, one or more content_block_delta events and a content_block_stop. A block's index is its position in the final content array, so you can merge deltas by index even when there are several blocks. One or more message_delta events then report top-level changes such as stop_reason, and message_stop ends the stream. ping events can appear at any point, and the parser should simply ignore them.
event: content_block_start
data: {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}
event: ping
data: {"type": "ping"}
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": "!"}}
event: content_block_stop
data: {"type": "content_block_stop", "index": 0}
event: message_delta
data: {"type": "message_delta", "delta": {"stop_reason": "end_turn", "stop_sequence":null}, "usage": {"output_tokens": 15}}
event: message_stop
data: {"type": "message_stop"}Checkpoint 2 of 8· Put it in order
Put the events of a single-block stream in the order the API sends them
- 1.content_block_stop
- 2.message_start
- 3.message_delta
- 4.message_stop
- 5.content_block_start
- 6.content_block_delta
The message opens with empty content, each block opens, receives deltas and closes, then message_delta reports top-level changes and message_stop ends the stream.
“A series of content blocks, each of which has a content_block_start, one or more content_block_delta events, and a content_block_stop event.”Source: platform.claude.com
Sources1
3.Reading text, tool-input and thinking deltas
The delta.type field tells you how to apply each content_block_delta to its block. A text_delta carries a piece of text to append. A tool call is different. Its input_json_delta events carry partial_json, which are string fragments of the tool's arguments such as {"location": "San Fra. These fragments are not valid JSON on their own. Concatenate them per block index and parse the result when that block's content_block_stop arrives. The SDK helpers, or a partial-JSON parser, can do this for you. Expect gaps between tool-input events: current models produce one complete key and value at a time, and then send it as several chunks.
| delta.type | Payload field | Updates |
|---|---|---|
| text_delta | text | the text of a text block |
| input_json_delta | partial_json | the input of a tool_use block, as partial JSON strings |
| thinking_delta | thinking | the thinking field of a thinking block |
| signature_delta | signature | the integrity signature, sent just before content_block_stop |
Thinking blocks stream their reasoning as thinking_delta events. Each one ends with a single signature_delta, which is used to verify that the block is intact. If thinking display is set to omitted, the block still opens and closes, but its thinking_delta holds an empty string and is followed only by the signature. A UI that waits for reasoning text in that mode will show nothing.
Checkpoint 3 of 8· Match them up
Match each delta type to the block content it builds
Tap a term, then the definition that fits it.
Each delta type updates one field of the content block at its index. The signature arrives last, just before the thinking block closes.
“For thinking content, a special signature_delta event is sent just before the content_block_stop event.”Source: platform.claude.com
Checkpoint 4 of 8· Check yourself
A hand-written stream parser calls json.loads on every partial_json value it receives and crashes on the first one. What should it do instead?
Each fragment is part of a JSON string. The complete input becomes a parseable object only after all fragments for the block have been joined.
“the deltas are partial JSON strings, whereas the final tool_use.input is always an object.”Source: platform.claude.com
Checkpoint 5 of 8· Exam question
An engineering team maintains a custom SSE event handler for the Messages API. After an Anthropic API update, their handler throws an unhandled exception whenever it encounters an event type it does not recognize, causing the entire stream connection to be dropped. According to Anthropic's guidance on the event stream, what should the handler do instead?
Correct answer: A — Treat unrecognized event types as expected and skip them without failing, since new event types may be introduced over time under the API's versioning policy.
- A. Correct — Anthropic's versioning policy states new event types may be added, and client code should handle unknown event types gracefully rather than failing.
- B. Falling back and dropping the connection on unfamiliar events causes unnecessary failures for forward-compatible additions that carry no breaking change.
- C. Downgrading anthropic-version does not prevent new event types from appearing and is not the documented mitigation; the correct behavior is graceful handling, not version rollback.
- D. Discarding an otherwise valid response because of harmless new event types would break the application for no reason tied to actual errors.
Sources1
4.Usage counts, mid-stream errors and unknown events
Token usage comes in two places in a stream. message_start reports the input tokens. Each message_delta reports output tokens as a running total, not a per-event increment. Take the value from the last event. A billing job that adds up every message_delta will report more than the dashboard shows, and the gap grows with the number of events.
A stream can fail after the server has already returned 200 OK. Because the status line has been sent by then, the failure arrives as an event: error in the stream, using the same type and error object as a normal error body. An overloaded_error here is the streaming equivalent of an HTTP 529. Treat it the way [API error types and recovery](lesson:4.1) describes, and throw away the partial content you received. Also, the API may add new event types under its versioning policy, so a parser should skip unknown events rather than fail on them.
event: error
data: {"type": "error", "error": {"type": "overloaded_error", "message": "Overloaded"}}Checkpoint 6 of 8· Check yourself
A service only checks the HTTP status code and treats 200 as success. Sometimes it stores truncated streamed answers during busy periods. What is it missing?
Once streaming has started, the status is already 200. Failures such as overloaded_error then arrive as SSE error events, which the client has to watch for.
“an error can occur after the API returns a 200 response.”Source: platform.claude.com
Sources1
5.SDK streaming helpers and long outputs
The SDKs offer two streaming styles, and they suit different needs. client.messages.create(..., stream=True) gives you the raw events as an iterable. It uses less memory because it does not build up a final message. client.messages.stream(...) adds helpers on top: text_stream for text only, event handlers in TypeScript, and accumulation into a complete Message through get_final_message() in Python or finalMessage() in TypeScript. Use the raw iterator if you are forwarding events somewhere else. Use the helper if you want the assembled result. In TypeScript, you cancel a stream by breaking out of the loop or calling stream.controller.abort().
| Call | Returns | Use when |
|---|---|---|
| client.messages.create(..., stream=True) | An iterable of stream events, with no final message built | You process or relay raw events and want lower memory use |
| client.messages.stream(...) with text_stream | Accumulated text as it arrives | You render text to a user incrementally |
| client.messages.stream(...) with get_final_message() | The complete Message object | You only need the finished result, especially with large max_tokens |
Long outputs are where streaming stops being optional. A non-streaming request sends nothing back until the whole response has been generated. With a large max_tokens, that wait can outlast HTTP timeouts, and for these requests the SDK requires streaming. A streamed request keeps the connection busy with events. If your code just wants the finished report, it still gets an object identical to what create() returns by calling get_final_message(). Long requests can also fail with a 504 timeout_error, and streaming is the suggested fix for that too.
Checkpoint 7 of 8· Fill the gap
A report generator with max_tokens=128000 wants the complete Message object without handling individual events. Which helper completes this code?
client = anthropic.Anthropic()
with client.messages.stream(
max_tokens=128000,
messages=[{"role": "user", "content": "Write a detailed analysis..."}],
model="claude-opus-5-5",
) as stream:
message = stream. ? ()get_final_message() collects all streamed events into the full Message while the stream keeps the HTTP connection alive, which avoids the timeout a non-streaming call would risk.
Source: platform.claude.comCheckpoint 8 of 8· Exam question
A team monitoring their Claude API streaming integration notices periodic ping events interspersed with content_block_delta events in their SSE logs, with no impact on the assembled message content. What is the correct handling for these events?
Correct answer: A — Treat ping events as keep-alive signals with no data payload relevant to the message content, and simply ignore them when reconstructing the response.
- A. Correct — ping events are simply keep-alive markers within the SSE stream and do not carry content or usage data that needs to be merged into the message.
- B. Ping events do not indicate an impending disconnect; they are a normal part of the stream and require no reconnection action.
- C. Ping events carry no usage information; token counts are only reported in message_delta's usage field.
- D. A ping event is unrelated to content block progress and is not evidence of a stalled block requiring a resend.
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.To get a streamed response's billed output tokens, add up output_tokens from every message_delta event.Why is that wrong?
message_delta usage is cumulative. The last reported value is the total, so adding the events together overstates usage.
Covered in Usage counts, mid-stream errors and unknown events
2.Each input_json_delta contains a usable JSON object with the tool's arguments.Why is that wrong?
The deltas are fragments of a string. Join them for the block and parse once content_block_stop arrives, or let the SDK helpers do it.
Covered in Reading text, tool-input and thinking deltas
3.client.messages.create(..., stream=True) returns the same assembled Message as the stream helper, just faster.Why is that wrong?
It returns only the raw event iterable and does not build a final message. Use client.messages.stream(...) with get_final_message() when you want the assembled result.
Covered in SDK streaming helpers and long outputs
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“The .stream() call keeps the HTTP connection alive with server-sent events”
↩︎ One HTTP request, streamed back: SSE rather than a websocket“Each server-sent event includes a named event type and associated JSON data.”
↩︎ One HTTP request, streamed back: SSE rather than a websocket“Each content block has an index that corresponds to its index in the final Message content array.”
↩︎ The order of events in a stream“Event streams may also include any number of ping events.”
↩︎ The order of events in a stream“Current models only support emitting one complete key and value property from input at a time.”
↩︎ Reading text, tool-input and thinking deltas“These deltas correspond to the thinking field of the thinking content blocks.”
↩︎ Reading text, tool-input and thinking deltas“you may receive an overloaded_error, which would normally correspond to an HTTP 529 in a non-streaming context”
↩︎ Usage counts, mid-stream errors and unknown events“new event types may be added, and your code should handle unknown event types gracefully.”
↩︎ Usage counts, mid-stream errors and unknown events“This is especially useful for requests with large max_tokens values, where the SDK requires streaming to avoid HTTP timeouts.”
↩︎ SDK streaming helpers and long outputs“You can accumulate the string deltas and parse the JSON once you receive a content_block_stop event”
↩︎ Exam trap 2“A series of content blocks, each of which has a content_block_start, one or more content_block_delta events, and a content_block_stop event.”
↩︎ Checkpoint“For thinking content, a special signature_delta event is sent just before the content_block_stop event.”
↩︎ Checkpoint“the deltas are partial JSON strings, whereas the final tool_use.input is always an object.”
↩︎ Checkpoint - 2.https://platform.claude.com/docs/en/api/errorsOfficial docs
“504 - timeout_error: The request timed out while processing. Consider using the streaming Messages API for long-running requests.”
↩︎ SDK streaming helpers and long outputs“an error can occur after the API returns a 200 response.”
↩︎ Checkpoint - 3.
“If you need to cancel a stream, you can break from the loop or call stream.controller.abort().”
↩︎ SDK streaming helpers and long outputs
Also cited
“The token counts shown in the usage field of the message_delta event are cumulative.”
↩︎ Exam trap 1“The token counts shown in the usage field of the message_delta event are cumulative.”
↩︎ Prediction“uses less memory (it doesn't build up a final message object for you)”
↩︎ Exam trap 3“The Messages API is stateless, which means that you always send the full conversational history to the API.”
↩︎ Checkpoint