Skip to main content
POST
This runs the same turn as Send a message, with the same request body and the same rules: one question at a time, only the owner can ask, archived conversations refuse questions, and idempotency_key makes retries safe. The difference is delivery. The answer arrives as a text/event-stream response. See Streaming for a walkthrough. Refusals arrive before the stream opens. Authentication, validation, permission checks and the one-turn-at-a-time claim all run first. If any of them fails, you get an ordinary HTTP error with a JSON body (400, 401, 403, 404, 409 or 429) and no stream. This request also resolves the conversation’s assistant. If that assistant can no longer answer (it is disabled or archived, or has no knowledge attached), this request returns 400 validation_error.
A 200 does not mean the answer succeeded. Once the stream opens, a failure is reported as a terminal answer.error event on the 200 response. Always read the terminal event.
If you retry with an idempotency_key whose turn has already finished, the stored turn is replayed as a stream: message.created, answer.started, the full answer text in one answer.delta, its answer.sources if it has citations, and a terminal event matching how the turn ended. The model is not asked again. A replayed turn that had failed ends with answer.error: send a new key to ask again. If you disconnect, the answer is saved with whatever text was produced and marked interrupted, and the conversation is freed for the next question. Streams cannot be resumed and Last-Event-ID is not supported; read the answer back with List messages.
string
required
The conversation ID.
string
required
The question. 1 to 16,000 characters after trimming. Newlines and tabs are allowed; other control characters and invisible formatting characters are rejected.
string
Your own key for this question, unique within the conversation. Up to 128 characters from A-Z, a-z, 0-9, _, ., : and -.
object
Your own labels for the question. At most 20 entries; keys up to 64 characters matching ^[a-z][a-z0-9_.-]*$; values up to 512 characters on a single line.

Response

Returns 200 OK with Content-Type: text/event-stream; charset=utf-8. Each event is framed as:
  • id is the event’s sequence number. It starts at 1 and increases by exactly 1, so a gap means you lost an event.
  • event is the event type. It is the only place the type appears: data does not repeat it.
  • data is a single-line JSON object. Non-ASCII characters are escaped as \uXXXX.
  • Ignore event types you do not recognize. New ones may be added.
The first event is always message.created. Exactly one terminal event ends the stream: answer.completed, answer.interrupted or answer.error.

message.created

The question is saved and an answer message exists with status pending.
string
required
The conversation ID.
string
required
The ID of your question message.
string
required
The ID of the answer message being generated.

answer.started

Generation has begun. The gap between message.created and this event is retrieval.
string
required
The ID of the answer message.

answer.delta

A piece of answer text. Each delta is an increment, not the text so far: append deltas in order to build the answer.
string
required
The next piece of answer text.

answer.sources

The citations the answer uses so far. A new answer.sources is sent whenever the answer first cites another source, and it always arrives before the answer.delta containing that source’s [n] marker, so every marker you render can be linked immediately. Each event carries the full list, so replace your citation list rather than appending to it. When the answer has citations, a final answer.sources with the complete list is sent just before answer.completed. If the assistant abstains after sources were sent, a final answer.sources with an empty citations list clears them.
object[]
required

answer.replaced

The answer’s final checks withdrew or rewrote the text already streamed, for example turning an answer that did not cite its sources into an abstention, or removing a link that could not be verified. Discard every answer.delta you have received and show this text instead. Later deltas, if any, append to it. The stored message holds only the final text.
string
required
The whole answer so far, replacing everything streamed before it.

answer.completed (terminal)

The answer finished and is saved with status complete.
string
required
The ID of the answer message.
string
required
answered, or abstained when the assistant declined because your knowledge did not support an answer.
boolean
required
true for an abstention. An abstention is a successful outcome, not an error; do not present its sources as supporting an answer. See How answers work.
string | null
required
Why generation stopped, as reported. length means the answer was cut off at the output limit and is incomplete.
string | null
required
Why the assistant declined, or null when it declined without naming a reason. Always null when abstained is false.An open string: match the values you know and fall back to a generic notice for one you do not. New values are not a breaking change.The widget surface receives a deliberately coarser set — see Stream a thread message.This is a streaming field only: a message read back from List messages carries abstained but no reason, and a replayed idempotent turn sends null.

answer.interrupted (terminal)

Generation produced some text and then stopped early. The partial answer is saved with status interrupted. You have text to show, but it is not a complete answer.
string
required
The ID of the answer message.
string
required
Why it stopped: an error code such as provider_failure when generation failed part-way, incomplete_stream when generation ended without finishing, or interrupted on a replayed turn.

answer.error (terminal)

Generation failed before producing any text. The answer is saved with status failed, and the conversation is free for the next question.
string
required
A stable error code, such as provider_failure, retrieval_failure, dependency_failure or internal_error. See Errors.
string
required
A client-safe message. It never contains internal detail.
Last modified on September 26, 2026