On this page
TLDR
Streaming returns tokens as they're generated for low-latency UX. A full deep-dive guide is coming soon.
What you are deciding
Streaming delivers a response as a sequence of events instead of one body. You accumulate the pieces, treat the stream as finished only at message completion, and handle an error event that arrives inside the stream.
Which event tells me a piece is complete, and what must my code do when the stream ends or fails?
What it is
Streaming is the capability to receive Claude's response token-by-token as Server-Sent Events (SSE) rather than waiting for the entire response. Add stream=True to messages.create(), and instead of a single response object, you get an HTTP event stream where each event represents a chunk of text or metadata. The user sees text appear word by word, creating a responsive chat experience instead of a blank screen for 10-30 seconds.
What makes streaming realtime is that the connection stays open while the model writes. Each token is emitted as a ContentBlockDelta event within milliseconds, allowing the client to render incrementally. The alternative (non-streaming) waits for the full response and ships it all at once, creating artificial latency. Streaming is especially valuable for long-form responses.
The event stream contains seven main types: MessageStart, ContentBlockStart, ContentBlockDelta (the actual text chunks), ContentBlockStop, MessageDelta, MessageStop, and optional error events. Production handling requires three guarantees: (1) graceful reconnection on network drop, (2) correct handling of tool_use blocks (their input streams as partial JSON strings that must be accumulated before parsing), (3) cost tracking (you pay per token regardless).
The main risk is premature disconnection. If the client closes before completion, the request is still billed but you lose partial output. Mitigations: track stream state, buffer all received tokens, exponential backoff on reconnection, log connection drops. Secondary risk: treating streaming as cost optimization, it costs the same as non-streaming, use it for UX.
How each certification frames it
CCD-F: How CCD-F tests streaming
CCDVF-D2-O3Claude API Mechanics: Claude API behavior and mechanics, including messages, tools, streaming, vision, thinking, caching, invoking Claude through third-party vendors, Messages API data access patterns, batch API use, and tradeoffs between realtime and batch API selection
Streaming is one of the Claude API behaviors listed in the Developer guide's API mechanics objective, next to messages, tools, caching and batch use (PDF page 6, Domain 2). Items test how to handle events, not only what streaming is for.
Expect to reason about when a tool call's input is usable and what counts as a finished response.
How it works
Request structure is identical to non-streaming, except stream=True. The SDK returns an iterator (Python) or async generator (TS) that yields events. for event in stream: process(event). Fundamentally synchronous from the caller's perspective: you block reading events until more arrive.
Event payload structure is JSON. ContentBlockDelta events contain a delta field with text. ContentBlockStart signals block type (text or tool_use). MessageStart carries the message shell with initial usage. MessageDelta carries the stop_reason and the usage counts, which are cumulative. MessageStop is the final end marker. Extract from ContentBlockDelta.delta.text, accumulate. Read the stop_reason and usage from `MessageDelta`, and treat MessageStop as the signal that the stream is complete.
For tool_use blocks, the stream works differently. The ContentBlockStart event announces the tool_use block, and its input then arrives as ContentBlockDelta events of type input_json_delta, each carrying a partial JSON string (the final input is an object). Accumulate the string incrementally, validate only after ContentBlockStop, then execute. Most common mistake: trying to execute halfway through the JSON stream.
Network handling is critical. Streaming uses a single HTTP response carrying Server-Sent Events; the connection stays open while the model writes. Wrap the loop in try/except: catch RequestException, log, decide retry or escalate. The SDK provides with client.messages.stream(...) as stream: context manager that auto-closes and handles cleanup.

Worked example: a streamed answer that ends in a tool call
Authored teaching example, not a bank question.
A chat screen streams Claude's reply. The reply starts with a sentence of text and then asks to call a lookup tool.
| Event | What your code does |
|---|---|
| content_block_delta (text_delta) | Append text to the display |
| content_block_delta (input_json_delta) | Append to a per-block string; do not parse yet |
| content_block_stop | Parse the tool input for that block |
| message_delta | Read stop_reason; usage counts are cumulative |
| message_stop | Mark the response complete |
| error | Fail the response and report it |
The screen shows text immediately, the tool call runs only after its JSON is complete, and a mid-stream error surfaces to the user and the logs. Check afterwards with a test that feeds a truncated stream and an error event into the handler.
How this decision goes wrong
Parsing incomplete JSON
Looks right: Each delta contains JSON, so parse each one as it arrives.
Why it fails: Anthropic documents tool input deltas as partial JSON strings, while the final input is an object. A fragment cannot be parsed alone.
Instead: Accumulate the string and parse at content_block_stop, or use the SDK helpers.
Treating the first token as completion
Looks right: Text is on screen, so the response is done and the UI can unlock.
Why it fails: Text can be followed by a tool call, more blocks and a final stop_reason. Acting early skips the tool step or cuts the reply.
Instead: Mark complete on message_stop and branch on stop_reason.
Losing an in-stream error
Looks right: The request was accepted, so a failure would have raised an exception.
Why it fails: The API may send an error as an event within the stream. If the loop only reads text events, the failure is silently dropped.
Instead: Handle the error event type explicitly, and apply the documented recovery approach for interrupted streams where it applies.
Where you'll see it
Real-time chat UI in Streamlit or Flask
Web app uses stream=True, emits each text chunk via SSE to the frontend. JavaScript EventSource API consumes SSE automatically. User perceives instant feedback; same cost as non-streaming.
Live code generation in IDE extensions
VS Code extension calls Claude. With streaming, user sees the function appear line-by-line. Real incremental generation, not a fake typewriter effect. Extension inserts text into editor buffer as events arrive.
Show 2 more examples
Streaming agentic loops with tool results
Refund agent loop streams text ("Let me check the order..."), then a tool_use block (its input streams as partial JSON and is parsed once the block stops). Harness executes, appends result. Next turn streams reasoning again. Streaming improves perceived responsiveness even in multi-turn flows.
Long-form document generation
Research assistant generates 5000-word report. Non-streaming = blank screen 20+ seconds. With streaming, user sees intro, sections, conclusions live. User can interrupt mid-generation without paying for full output.
Code examples
import anthropic
client = anthropic.Anthropic()
def stream_response(user_msg: str, tools: list = None):
accumulated_text = ""
with client.messages.stream(
model="claude-opus-4-5",
max_tokens=2048,
system="You are a helpful assistant.",
tools=tools or [],
messages=[{"role": "user", "content": user_msg}],
) as stream:
for event in stream:
if event.type == "content_block_delta":
if event.delta.type == "text_delta":
text = event.delta.text
accumulated_text += text
print(text, end="", flush=True) # Real-time output
elif event.type == "message_delta":
# stop_reason and cumulative usage arrive on message_delta
print(f"\n[Stop reason: {event.delta.stop_reason}]")
final = stream.get_final_message() # SDK helper: the accumulated Message
for block in final.content:
if block.type == "tool_use":
print(f"[Tool: {block.name}]")
return accumulated_textLooks right, isn't
Each row pairs a plausible-looking pattern with the failure it actually creates. These are the shapes exam distractors are built from.
Streaming is cheaper than non-streaming.
Same cost.
You pay per token regardless. Streaming is purely UX, not cost optimization.
If stream connection drops, retry the entire request.
Drop doesn't lose what was received.
Client retains buffered text. Retrying wastes tokens on duplicate work. Log error, decide based on context.
Tool_use input is complete in each delta; parse JSON as it arrives.
Tool_use input arrives as chunked input_json_delta events, each a partial JSON string that must be fully accumulated and parsed at content_block_stop.
Parsing a fragment fails.
Display each event to the user immediately.
Some events (MessageStart) are metadata, not displayable.
Filter: display only ContentBlockDelta.text. Meta events go to logging.
Cancel a streaming request mid-stream by closing connection.
Closing stops receiving, but request is still processed server-side and billed.
Cancellation is not a cost-saving mechanism.
Side-by-side
| Aspect | Streaming | Non-streaming | Polling endpoint | WebSocket |
|---|---|---|---|---|
| Time to first token | 100-200ms | Entire response time | Batch delay | Similar to streaming |
| Cost | Same per token | Same per token | Same | Same |
| Connection | HTTP SSE | Request/response | Repeated polling | Persistent TCP |
| UX | Real-time, progressive | Batch, instant or long wait | Polling jitter | Real-time, lowest latency |
| Complexity | Event loop, buffer | Simple | Poll interval tuning | Server upgrade |
| Best for | Chat UIs, long-form | Quick queries, APIs | Legacy systems | High-frequency real-time |
Decision tree
Response >1000 tokens (likely >10 seconds)?
User-facing chat or interactive interface?
Agentic loop with tool_use blocks?
Need to reconnect on network failures?
Bandwidth a constraint?
The exam trap
Treating the stream as finished once text appears, or parsing each tool-input fragment as JSON. A tool call's input is parseable only after its content_block_stop event, the response is complete only at message_stop, and an error can still arrive as an event inside a stream that started normally.

Question patterns
CCD-F practice questions
3 approved CCD-F questions for this topic. Tap an answer to check it, then read the rationale for every option.
A team marks their entire growing message list (every user and assistant turn so far) with cache_control: {"type": "ephemeral"} in a 15-turn agentic loop, expecting cost savings on every turn. Why does this fail to deliver the expected savings?
CCA-F practice questions
3 approved CCA-F questions for this topic. Tap an answer to check it, then read the rationale for every option.
A customer-facing chatbot is wired to the Batch API to save money. What goes wrong in production?
Frequently asked
Showing 12 of 12 questions
input_json_delta events. Buffer them, and parse when ContentBlockStop fires.stream=True, you get events. Buffer all events to assemble complete response, but can't "un-stream"./stream endpoint, iterate Claude stream, response.write(event) each chunk. Client: const es = new EventSource('/stream').input_json_delta events into a buffer keyed by index; parse on the matching content_block_stop. Treating each delta as a complete tool call leads to malformed JSON crashes.message_stop, you'll burn tokens on the loop.Sources and next steps
- Claude Certified Developer - Foundations Exam Guide v1.0 (July 2026), PDF page 6, section: Domain 2: Applications and Integration (Claude API Mechanics, Claude Application Design). Checked 2026-10-16.
- Streaming messages (Claude API docs), section: Event types; Input JSON delta; Error events; Error recovery. Checked 2026-10-16.
