Skip to main content
Stream an answer when a person is waiting for it. Add :stream to the message endpoint:
The request body is the same as Send a message. The response is text/event-stream. For scripts and batch jobs, the non-streaming endpoint returns the same answer in one response.

Events

Each event has an id (its sequence number), an event type and a single-line JSON data payload:
Field-level detail is on Stream a message.

Rules your client can rely on

  1. Exactly one terminal event ends every stream: answer.completed, answer.interrupted or answer.error.
  2. Append answer.delta text in order. Each delta is an increment, not the answer so far.
  3. On answer.replaced, discard what you rendered and show its text instead. It is sent when the final checks withdraw or rewrite streamed text, for example when an answer becomes an abstention. Later deltas append to it.
  4. Replace, don’t append, citations. answer.sources is sent each time the answer first cites another source, before the delta that contains its [n] marker, so markers can be linked as soon as they appear. When the answer has citations, one more answer.sources with the complete list arrives just before answer.completed. The last one is authoritative; an empty list means the assistant abstained.
  5. The event: line is the type. It is not repeated inside data.
  6. id increases by one. A gap means an event was lost.
  7. Ignore event types you don’t recognise. New non-terminal events may be added.

A 200 is not success

Authentication, validation and the one-question-at-a-time check all happen before the stream opens, so those failures arrive as ordinary HTTP errors. Once the stream has opened with 200, a failure arrives as answer.error. Always read the terminal event before treating an answer as complete. An abstention streams like any answer: its sentence arrives as answer.delta text, or as answer.replaced when it withdraws text that was already streamed, and answer.completed carries abstained: true. Render it as a normal reply. answer.completed also carries abstention_reason — why the assistant declined, or null. Use it to tell “nothing in your sources covers this” apart from “something does, but not confidently enough”: those call for different actions. Treat it as an open string — match the values you know, fall back to a generic notice for one you do not. The values, and the shorter set the widget receives, are on Stream a message. answer.interrupted means generation stopped part-way after producing some text, and that partial text is saved. Its reason says why: an error code such as provider_failure when generation failed after text had streamed, or incomplete_stream when generation ended without finishing. Show the partial answer with a notice that it is incomplete. A failure before any text arrives is answer.error instead.

Disconnects and retries

If the client disconnects, the answer is saved with the text produced so far, marked interrupted, and the conversation is freed. Streams cannot be resumed. If you retry with the same idempotency_key after a turn has finished, the stored turn is replayed as a stream and the model is not asked again. Read an answer back at any time with List messages.

Proxies

Streaming requires every proxy between your client and MITHUNAI to pass events through unbuffered. Responses carry X-Accel-Buffering: no; make sure your proxies honour it and do not buffer text/event-stream. A buffering proxy makes the answer appear all at once at the end.

Example

Last modified on September 26, 2026