Streaming
Set "stream": true to receive a response as it is generated. routeur.ai emits
an OpenAI-compatible server-sent event stream, so any OpenAI client library —
the openai SDKs, the Vercel AI SDK, LangChain — consumes it with no
special-casing. Point the client at https://api.routeur.ai/v1 and stream as
usual.
The wire format
The response is Content-Type: text/event-stream: a sequence of
chat.completion.chunk objects, one per data: line, terminated by a literal
data: [DONE].
data: {"id":"chatcmpl_...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"}}]}
data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Hello"}}]}
data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":9,"completion_tokens":2,"total_tokens":11}}
data: [DONE]
- Token text arrives in
choices[].delta.content. - The terminal chunk (before
[DONE]) carriesfinish_reasonandusage. usageis included by default. Send"stream_options": {"include_usage": false}to suppress it on the wire (routeur still meters internally for billing and traces).- With
Routeur-Trace: true, therouteurmeta block (provider, model, route reason, latency, cost) is delivered as one extra chunk with emptychoicesimmediately before[DONE].
n > 1 is rejected with stream: true (invalid_request) — stream a single
choice.
Tool calls
Tool calls stream as incremental delta.tool_calls fragments in the same
OpenAI shape, regardless of which upstream answered (the Anthropic and Gemini
adapters translate their native tool events). Reassemble by index,
concatenating function.arguments:
data: {"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_1","type":"function","function":{"name":"get_weather","arguments":""}}]}}]}
data: {"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\":"}}]}}]}
data: {"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"paris\"}"}}]}}]}
data: {"choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}
Tool-call arguments are model output, so output moderation scans them just like content.
Errors and cancellation
Once the first chunk is sent the HTTP status is committed to 200. A failure
after that point is a terminal event: error frame, after which the stream
closes (no [DONE]):
event: error
data: {"error":{"code":"upstream_error","message":"...","type":"routeur_error"},"request_id":"01K..."}
A failure before the first chunk (auth, routing, an output-moderation block on an unmoderated-stream org) is the ordinary JSON error with its normal status — no stream is opened. See Errors for codes and retry guidance.
Cancelling a stream. To stop a response early — a chat UI's "stop" button —
just abort the HTTP request (close the connection, or cancel the fetch /
AbortController). routeur.ai detects the disconnect and cancels the upstream
call, so you are not billed for tokens generated after you stopped. No
special API call is needed.
const controller = new AbortController();
const res = await fetch("https://api.routeur.ai/v1/chat/completions", {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ model: "auto", stream: true, messages }),
signal: controller.signal,
});
// …later, when the user hits "stop":
controller.abort();
Truncated streams. A deploy or autoscale event can cut a long-lived stream
(no terminal error, no [DONE]). This is expected operationally — treat it
like a retryable error and re-issue the request idempotently.
Output moderation while streaming
How output moderation applies to a stream is configurable per organization (and
per rule). In the default buffered mode the gateway scans the whole response
before sending, so a streamed request from a moderated org simply starts after
moderation completes. In opt-in chunked mode tokens stream as they are
scanned and a block aborts the stream with the blocked_by_moderation terminal
event. See Output moderation → Streaming behaviour.