Core
Streams
Server-sent events on the three formats, and the cost in a chat stream. What we charge when a stream stops or when you close it.
Send stream: true to get the answer in parts, as server-sent events. This works on the three
endpoints that cost money.
| Endpoint | The events |
|---|---|
/v1/chat/completions | OpenAI chat.completion.chunk frames, then data: [DONE] |
/v1/messages | The Anthropic events (message_start to message_stop), as the model sends them |
/v1/responses | The OpenAI Responses events (response.created to response.completed), as the model sends them |
We do not change the events of a Messages stream or a Responses stream. The rest of this page is about the chat completions stream.
A chat completions stream
stream = client.chat.completions.create(
model="claude-haiku-4-5-20251001",
messages=[{"role": "user", "content": "Count to five."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)const stream = await client.chat.completions.create({
model: "claude-haiku-4-5-20251001",
messages: [{ role: "user", content: "Count to five." }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}The stream starts with a frame that has the role. Then the content frames come. The last choice frame
has the finish_reason:
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"One"},"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
Token counts and cost in a chat stream
To get the token counts, send stream_options:
stream = client.chat.completions.create(
model="claude-haiku-4-5-20251001",
messages=[{"role": "user", "content": "Count to five."}],
stream=True,
stream_options={"include_usage": True},
)Then the stream has one more frame after the last choice frame. Its choices list is empty:
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":9,"total_tokens":23},"kontinent":{"request_id":"req_8f2a1c","cost_micros":12,"cost_usd":0.000012,"list_price_usd":0.000059,"saved_usd":0.000047}}
The kontinent block on this frame shows the cost of the request. It is the same amount that we take
from your balance. If your code reads chunk.choices[0] on each frame, add a check for an empty list.
Tool calls in a chat stream
Tool calls come in the stream. delta.tool_calls has an index. The first frame of each call has the
id, the type and the function name. The next frames add parts of function.arguments. The SDKs join
these parts for you. See Tool calls.
The model’s thinking in a chat stream
A reasoning model thinks before it answers, and you pay for those tokens. By default the stream does not send the
thinking. To get it, send include_reasoning: true. Each part of the thinking then comes in delta.reasoning_content,
apart from delta.content. An OpenAI SDK ignores this field, so the answer that your code reads does not change.
When the model’s supplier gives the number, the usage frame shows the thinking’s part of the output tokens in
completion_tokens_details.reasoning_tokens.
Keep-alive comments
A model can be quiet for a long time while it thinks. If a chat stream sends no data for 15 seconds, we send this comment line:
: keep-alive
A line that starts with a colon is a comment in server-sent events. The OpenAI SDKs ignore it. If you
read the stream with your own code, ignore each line that does not start with data: . Some proxies
close a quiet connection, and the comment prevents this. Messages and Responses streams have their own ping
events, so we do not add the comment to them.
When a stream stops
After a stream starts, we cannot send an HTTP status. If the model fails after that point, the last
frame of a chat stream has finish_reason: "error", and there is no usage frame. Look at
finish_reason. If you look only at the content, you cannot see the difference between a full answer
and a part of an answer.
We charge for the output that the model wrote before the stop. See Reliability.
When you close the connection
After you close the connection, the request has 15 seconds to complete. If it completes in that time,
we charge for the token count of the model. If it does not complete, we stop it. Then we charge for
the output that the model wrote, and we give back the rest of the amount that we held. That count is
never more than your max_tokens value.
A stream costs the same as the same request without a stream.
Streams and idempotency
A request with stream: true and an Idempotency-Key header gets a 400 error. To repeat a stream
from a cache, we would have to keep each response, so we refuse the header. See
Idempotency.