Docs / How Agents Talk to Models / Streaming
Streaming
Add
"stream": true to any request and the answer arrives token by token as Server-Sent Events instead of one big JSON blob at the end. Cost is identical — same tokens, same price — streaming changes delivery, not the bill. The gain is perceived latency: your UI can start rendering before the model finishes writing. The catch: anything that must inspect the whole output first — a JSON parser, a verification gate — still has to buffer the stream to completion.The wire call changes by exactly one field. Point the same POST at the same endpoint, keep the same headers, add "stream": true:
bash
# same endpoint, same headers — one extra field
curl https://api.anthropic.com/v1/messages \
-H 'content-type: application/json' \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H 'anthropic-version: 2023-06-01' \
-d '{
"model": "claude-haiku-4-5-20251001",
"max_tokens": 1024,
"stream": true,
"messages": [{"role": "user", "content": "Scan orders.py for validation gaps."}]
}'The connection stays open and events arrive in a fixed sequence, each a pair of lines (event: <type> then data: <json>). Four matter most:
message_start— opens; the input-token count is already known.content_block_delta— a few tokens per event; this is where you render.message_delta— closes with the final output-token count andstop_reason.message_stop— the stream is done.
sse
# ① message_start — input_tokens known immediately; output_tokens not yet final
event: message_start
data: {"type":"message_start","message":{"id":"msg_01XA9g...","role":"assistant",
"model":"claude-haiku-4-5-20251001","stop_reason":null,
"usage":{"input_tokens":842,"output_tokens":1}}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: ping
data: {"type":"ping"}
# ② content_block_delta — one or a few tokens per event; render as they arrive
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"I"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"'ll scan"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" orders.py"}}
... more content_block_delta events ...
event: content_block_stop
data: {"type":"content_block_stop","index":0}
# ③ message_delta — final usage: output_tokens now settled; stop_reason set
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},
"usage":{"output_tokens":312}}
# ④ message_stop — stream closed; safe to release the connection
event: message_stop
data: {"type":"message_stop"}Key idea The
usage object is split across two events in a stream. message_start reports input_tokens immediately — the model read your whole prompt before writing a word, so input is known at the start. message_delta reports output_tokens at the end, once the model finishes. The bill is the same as if you hadn't streamed: those token counts, those prices.Common mistake Trying to parse or verify partial stream output. A JSON parser fails on a half-written object. A semantic gate can't rule on an answer the model hasn't finished. If your code needs the complete text — to validate, to parse structured output, to decide on tool calls — you must accumulate every
content_block_delta and wait for message_stop before acting. Rendering can happen token by token; reasoning about the output cannot.Under the hood Firstpass sits between your agent and the provider and forwards
stream: true transparently — but it must buffer the full stream before the gate can run. Once the gate passes, Firstpass re-emits the buffered content as SSE to your client, so your agent still sees a stream. The practical effect: time-to-first-token is slightly higher through Firstpass (gate latency on top of generation), but the wire contract your agent code observes is identical. See how it works for the full flow.Streaming changes when tokens arrive, not how many — cost is identical, time-to-first-token drops, but anything that reasons about the output still needs the complete stream first.