"stream": true in the body, read the events, stop at data: [DONE]. If your
client already streams from an OpenAI-compatible endpoint, it streams from this
one without a change.
Asking for a stream
Content-Type: text/event-stream. The body is relayed as it
arrives: nothing is buffered on our side, so the first token reaches you as soon
as the model produces it.
On the Responses API
POST /v1/responses streams too, and everything on this page about how a stream
is relayed, where the counts ride and how a stream can end early applies to it
unchanged, because it is the same request path.
What differs is the transcript. A Responses stream carries named events with
their own shapes rather than the chat completion chunks shown below, and those
frames are the provider’s, forwarded as they arrive. Read them against the
Responses format rather than against the transcript in the next section. The
one place the two are identical is the ending: the counts arrive on the same
:x-uniblock-usage comment line, and the codes that can cut a stream short are
the same codes.
What the transcript looks like
Annotated. Ids and content are illustrative; the framing is exact.:x-uniblock-usage line is the standard format. That line
is a comment: the server-sent events specification requires a parser to ignore
any line beginning with a colon, so SDKs and browsers skip it and your streaming
loop never sees it. It sits immediately before data: [DONE] and never after,
because the counts are final only at that point and a client that stops reading
at the terminator would otherwise miss it.
A streamed response carries Uniblock’s own token counts as a server-sent events
comment line, written immediately before data: [DONE]:
The usage line
Counts not reported
Fields are omitted rather than set to zero when a provider did not report them,
so “the provider did not say” stays distinguishable from “nothing was used”.
reported: true with zeros means zero. An absent field means unknown.
The counts are the provider’s own. Nothing is re-counted or estimated.
A stream that is cut off carries no usage line, because there are no final
counts to report.
On a response that is not streamed there is no separate counts line. The
provider’s own usage object inside the JSON body is what you read, exactly as
you would from OpenAI. Either way, the figures we settle your balance against
are the ones on your Activity screen.
Absent counts are omitted rather than sent as zero, and "reported": false
carries no counts at all. The full field list is in
Headers.
Getting the counts in the stream itself
The comment line above is ours. If you want token counts as part of the stream your SDK parses, ask for them the standard way:choices array and a usage
object, ahead of data: [DONE]. If you do not ask for it, you do not receive it,
because the commonest streaming loop in every language reads
chunk.choices[0].delta and throws on a chunk that has no choices.
When a stream ends early
A streamed response has already sent its status and headers by the time anything can go wrong, so a failure cannot arrive as a status code. Instead the stream ends with one moredata: line carrying the standard error body, and then closes.
There is no data: [DONE] after it.
balance_exhausted only ever appears mid-stream. A request refused before it
started because the account cannot pay is a 402 with insufficient_credit, so
“never started” and “cut off part way” are always distinguishable. Neither is
charged as a whole request: a refusal costs nothing, and a cut stream costs what
it produced.
Handle a stream that stops without data: [DONE] and without an error line as an
incomplete response. It means the connection ended, which is a truncated answer
rather than a complete one, and we record it as a stream that did not finish
rather than as a fault.
What streaming does not change
- Money works the same way. Credit is held before the request goes out, and a streamed request is metered and appears in your usage like any other.
- Errors use the same body and the same codes as a non-streamed request. Only the channel differs, and only because a status code cannot be recalled.
- The bytes are the provider’s. We add the one comment line and relay everything else exactly as it arrived.