What you will be able to do
- Explain why Claude streaming uses server-sent events over HTTP, and what the sources do and don't say about websockets
- Choose between stream=True and the SDK stream helper for a given memory or convenience need
- Walk through the order of stream events and assemble content blocks by index
- Rebuild a tool's input from partial_json deltas and handle error events that arrive mid-stream
1.Streaming is SSE on a REST call, not a websocket
When you stream from the Claude API, you are still making an ordinary HTTP request. You set "stream": true on a Messages request, and the response comes back as a sequence of server-sent events (SSE) over the same connection. The Python and TypeScript SDK docs both describe their streaming support as SSE.
This matters for integration design. SSE sends data in one direction only, from server to client, over a single request. The client doesn't send anything more on that connection. The next turn is a new request carrying the full message history.
2.Two ways to stream with an SDK
The Python and TypeScript SDKs each give you two options. client.messages.create(..., stream=True) returns the raw events as an iterable. It uses less memory because it never builds a final message object for you. client.messages.stream(...) is a helper with extra conveniences: it accumulates the text and can return the finished message at the end.
| Approach | What you get | Trade-off |
|---|---|---|
| client.messages.create(..., stream=True) | An iterable of the events in the stream | Uses less memory; you build the final message yourself |
| client.messages.stream(...) | Helpers such as text_stream and get_final_message() | Accumulates the complete Message object for you |
The helper also matters when you don't need the text as it arrives. With a large max_tokens, a non-streaming request can run into HTTP timeouts, so the SDKs require streaming. The helper lets you stream internally and still get back the same complete Message object that .create() would return.
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()In TypeScript the equivalent call is .finalMessage(). To cancel a TypeScript stream, break out of the loop or call stream.controller.abort().
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.
3.The event flow of one stream
Every stream follows the same sequence. It opens with message_start, which carries a Message object with empty content. Then each content block arrives as a content_block_start, one or more content_block_delta events, and a content_block_stop. After that come one or more message_delta events with top-level changes to the Message, and finally message_stop. ping events can appear anywhere in the stream.
| Event | Meaning |
|---|---|
| message_start | A Message object with empty content |
| content_block_start / content_block_delta / content_block_stop | One content block, built up by deltas; index matches its position in the final content array |
| message_delta | Top-level changes to the final Message object |
| message_stop | End of the stream |
| ping | May appear any number of times |
New event types may be added over time, and your code should handle unknown event types gracefully. A strict parser that rejects anything unfamiliar will start failing when a new event type ships.
Sources1
4.Reassembling tool_use input from partial JSON
Text blocks stream as text_delta fragments that you join together. Tool calls work differently. The deltas for a tool_use block are input_json_delta events, and each one carries a partial_json string. Those fragments are not valid JSON by themselves, and only the finished tool_use.input is an object.
event: content_block_delta
data: {"type": "content_block_delta","index": 1,"delta": {"type": "input_json_delta","partial_json": "{\"location\": \"San Fra"}}}The correct approach is to collect the string fragments for that block's index and parse them once content_block_stop arrives. The alternatives are a partial-JSON parser such as Pydantic, or the SDK helpers that give you parsed values as they come in. Current models emit one complete key and value from the input at a time, so a tool block can go quiet for a while before a burst of deltas.
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.
Sources1
5.Errors that arrive after a 200
The page on HTTP errors explained status-code handling, but a stream breaks that model. The API has already returned 200 before the first event is sent, so a failure after that point arrives as an error event inside the stream. For example, under heavy load you can get an overloaded_error event, which in a non-streaming call would have been an HTTP 529.
event: error
data: {"type": "error", "error": {"type": "overloaded_error", "message": "Overloaded"}}Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Each input_json_delta contains a usable input object, so you can pass each one to the tool as it arrives.Why is that wrong?
The deltas are fragments of a JSON string. Concatenate them for the block and parse once content_block_stop arrives. Only the final tool_use.input is an object.
2.Once a streaming request returns HTTP 200, the rest of the response is guaranteed to succeed.Why is that wrong?
Errors can arrive after the 200 as error events inside the SSE stream, and they don't go through normal HTTP error handling.
Covered in Errors that arrive after a 200
3.If you need the complete Message object, you can't stream and have to make a plain non-streaming call, even with a very large max_tokens.Why is that wrong?
The SDK stream helper streams internally and returns the same complete Message. For large max_tokens values, the SDKs require streaming to avoid HTTP timeouts.
Covered in Two ways to stream with an SDK
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“When creating a Message, you can set "stream": true to incrementally stream the response using server-sent events (SSE).”
↩︎ Streaming is SSE on a REST call, not a websocket“This is especially useful for requests with large max_tokens values, where the SDKs require streaming to avoid HTTP timeouts.”
↩︎ Two ways to stream with an SDK“Each content block has an index that corresponds to its index in the final Message content array.”
↩︎ The event flow of one stream“new event types may be added, and your code should handle unknown event types gracefully.”
↩︎ The event flow of one stream“You can accumulate the string deltas and parse the JSON once you receive a content_block_stop event”
↩︎ Reassembling tool_use input from partial JSON“during periods of high usage, you may receive an overloaded_error, which would normally correspond to an HTTP 529 in a non-streaming context”
↩︎ Errors that arrive after a 200“the deltas are partial JSON strings, whereas the final tool_use.input is always an object.”
↩︎ Exam trap 1“This is especially useful for requests with large max_tokens values, where the SDKs require streaming to avoid HTTP timeouts.”
↩︎ Exam trap 3 - 2.https://platform.claude.com/docs/en/api/overviewOfficial docs
“The Claude API is a RESTful API at https://api.anthropic.com that provides programmatic access to Claude models and Claude Managed Agents.”
↩︎ Streaming is SSE on a REST call, not a websocket - 3.
“The SDK provides support for streaming responses using Server-Sent Events (SSE).”
↩︎ Streaming is SSE on a REST call, not a websocket“Alternatively, you can use client.messages.create(..., stream=True) which only returns an iterable of the events in the stream and uses less memory”
↩︎ Two ways to stream with an SDK - 4.https://platform.claude.com/docs/en/api/errorsOfficial docs
“When receiving a streaming response over server-sent events (SSE), an error can occur after the API returns a 200 response.”
↩︎ Errors that arrive after a 200“When receiving a streaming response over server-sent events (SSE), an error can occur after the API returns a 200 response.”
↩︎ Exam trap 2