CertSafari
    Anthropic Claude Certified Developer – Foundations (CCDV-F)· Lessons

    Domain 5 · Lesson 13/25

    Streaming Claude responses with server-sent events

    Technical Fundamentals

    11 min read
    4.2% of exam
    6 sources
    Published 12 Oct 2026
    Docs as of 30 Sep 2026

    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?

    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.

    The tail of a raw text stream: a block's lifecycle, a ping, then the closing message eventstext
    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. 1.content_block_stop
    2. 2.message_start
    3. 3.message_delta
    4. 4.message_stop
    5. 5.content_block_start
    6. 6.content_block_delta

    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 types and what each one updates
    delta.typePayload fieldUpdates
    text_deltatextthe text of a text block
    input_json_deltapartial_jsonthe input of a tool_use block, as partial JSON strings
    thinking_deltathinkingthe thinking field of a thinking block
    signature_deltasignaturethe 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.

    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?

    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?

    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.

    An error delivered inside an already-open streamtext
    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?

    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().

    Choosing a Python SDK streaming style
    CallReturnsUse when
    client.messages.create(..., stream=True)An iterable of stream events, with no final message builtYou process or relay raw events and want lower memory use
    client.messages.stream(...) with text_streamAccumulated text as it arrivesYou render text to a user incrementally
    client.messages.stream(...) with get_final_message()The complete Message objectYou 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. ? ()

    Checkpoint 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?

    Sources123

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 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. 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. 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. 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. 2.
      “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. 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

    Ready to test yourself?

    Practise the 20 questions on this subdomain.

    Spotted a mistake, or was something unclear? Tell us.