Streaming
Tokens as they are produced, over plain server-sent events.
Enabling streaming
Set "stream": true in the request. The response is text/event-stream and is proxied chunk-for-chunk from the model server; nothing is buffered to disk on the way.
Wire format
Standard OpenAI SSE: one data: line per event, blank line between events, data: [DONE] at the end.
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"thairouter/glm-5.3-flash","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"thairouter/glm-5.3-flash","choices":[{"index":0,"delta":{"reasoning":"The user wants"},"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"thairouter/glm-5.3-flash","choices":[{"index":0,"delta":{"content":"กาล"},"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"thairouter/glm-5.3-flash","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"thairouter/glm-5.3-flash","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":96,"total_tokens":110}}
data: [DONE]delta.contentcarries answer text;delta.reasoning(older builds:delta.reasoning_content) carries thinking on reasoning models (Reasoning). Withreasoning.exclude: truethose deltas are removed before the frame reaches you.- The chunk with
finish_reasonends the choice; one more chunk with usage follows.
Usage chunk
ThaiRouter always sets stream_options.include_usage upstream, so the last data chunk before [DONE] has an empty choices array and a usage object. You don't need to request it. That chunk is what your request is billed from, so handle the empty-choices case in your loop.
Response headers
HTTP/2 200
content-type: text/event-stream; charset=utf-8
cache-control: no-cache
x-thairouter-usage-id: 7d0a2b6c-…
x-thairouter-reasoning-effort: max
access-control-expose-headers: x-thairouter-usage-id, x-thairouter-reasoning-effortThe usage id is in a header because the stream body is passed through untouched. Match it against your logs to see the settled cost. x-thairouter-reasoning-effort is present only on reasoning models and reports the level actually used.
Disconnecting early
max_tokens), because the real token count never reaches us. Keep max_tokens honest when you expect to cancel streams.Examples
curl -N https://api.thairouter.ai/v1/chat/completions \
-H "Authorization: Bearer $THAIROUTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "thairouter/glm-5.3-flash",
"messages": [{"role": "user", "content": "เล่านิทานสั้นๆ"}],
"stream": true
}'