Chunk shape
stream = client.chat.completions.create(
model="auto",
messages=[{"role": "user", "content": "Count to 5"}],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
Each event is a chat.completion.chunk object
with an incremental delta, the stream ends
with a literal data: [DONE] line — identical
to the OpenAI API's own shape, regardless of which provider actually served the request underneath.
Usage on a stream
Token accounting works identically whether or not you ask for it: we always request
stream_options.include_usage upstream so
billing and the x-llmrouter-cost-usd header
are correct either way — if you didn't ask for it yourself, we strip it back out of the final chunk before it
reaches you, so response shape stays exactly what you requested.
Time-to-first-token
TTFT is only measurable on a streaming request — it's captured per request and feeds the latency numbers on /usage and each model's page, so you can see real p50/p95 time-to-first-token per model, not a marketing number.
Mid-stream errors
Failover across candidates (see Model fallbacks)
only happens before the first token — once bytes are already streaming to you, a live
upstream failure can't be transparently swapped for another model or provider. Instead it surfaces as a
normal SSE error event carrying the
request's trace_id, so you can look up
exactly what happened at /logs. Tokens
already delivered before the drop are still billed (and still yours) — see
Zero-completion insurance.
Stream cancellation
Closing the connection from your side — aborting a fetch
with an AbortController, breaking out of a
for chunk in stream loop, or closing the
underlying response — tears down the upstream provider connection too, so the request doesn't keep running
server-side after you've stopped listening. Tokens already delivered before you disconnected are still billed
and still recorded in the trace at /logs,
the same as a mid-stream failure above — see
Zero-completion insurance.
This only applies to streaming requests — a non-streaming call has already been dispatched as a single blocking
upstream request, so closing your connection early doesn't stop it or change what you're billed.
import requests
from threading import Event, Thread
def stream_with_cancellation(prompt: str, cancel_event: Event):
with requests.Session() as session:
response = session.post(
"https://llmrouter.sh/v1/chat/completions",
headers={"Authorization": f"Bearer {api_key}"},
json={"model": "auto", "messages": [{"role": "user", "content": prompt}], "stream": True},
stream=True,
)
try:
for line in response.iter_lines():
if cancel_event.is_set():
response.close()
return
if line:
print(line.decode(), end="", flush=True)
finally:
response.close()
# start the stream on a background thread, then cancel it whenever you like
cancel_event = Event()
stream_thread = Thread(target=lambda: stream_with_cancellation("Write a story", cancel_event))
stream_thread.start()
# ...later, to cancel:
cancel_event.set()
Headers on a stream
The same x-llmrouter-* headers described in
Models & routing are sent with the
initial response, before any bytes stream — with one exception: response-cache hit/miss status
(x-openrouter-cache-status) is only ever
set on non-streaming responses, since whether the first chunk came from cache isn't known until after headers
are already committed. See Response caching.