Streaming (SSE)
The native API streams over Server-Sent Events with a two-step protocol: you spawn a turn, then subscribe to its durable event log. The split makes the stream resumable — a dropped connection reconnects and replays from where it left off, and the same turn can be watched from multiple tabs.
Looking for a one-line drop-in?
If you only need plain streamed text and already use the OpenAI SDK, the OpenAI-Compatible endpoint streams in a single request and is the simpler path. This page documents the native protocol and its full event set.
The protocol
Spawn the turn
POST /api/v1/sessions/stream does not stream — it returns 202 Accepted with the identifiers of the turn that was spawned in the background.
Subscribe to the event log
GET /api/v1/sessions/{session_id}/messages/{message_id}/stream returns text/event-stream and replays every event for that turn. Each frame carries an id: — keep the last one to resume.
Request fields
The POST /sessions/stream body accepts:
| Field | Type | Description |
|---|---|---|
| messagerequired | string | The user turn. Either message or document_ids is required. |
| session_id | string | Reuse a session_id to continue a prior conversation. Omit to start fresh — the response returns the new id. |
| agent_id | string | Run the turn under a specific agent profile and its granted connectors. See Agents. |
| document_ids | array | Attach previously uploaded documents to the turn by id. |
Event types
Every frame is data: followed by a JSON object with a type discriminator. These are the stable, public event types — handle the ones you need and ignore any type you don't recognize; the set is additive.
| Type | Meaning |
|---|---|
start | Turn began. Carries session_id. |
delta | Incremental assistant text — append content for live rendering. |
tool_started | A tool call began. Carries tool_name, label, iteration. |
tool_completed | Tool finished. Carries success, message, duration_ms. |
artifact_start | A file/artifact began streaming. Carries artifact_id, artifact_type, title. |
artifact_delta | A chunk of artifact content (content_chunk, chunk_index). |
artifact_complete | Artifact finished. Carries version, content_type, optional download_url. |
artifact_update | A previously completed artifact was revised. |
complete | Terminal success. Carries final_response, token counts, and artifacts. |
error | Terminal failure. Carries error code, message, retryable, user_message. |
cancelled | Turn stopped. Carries reason (user_stop, timeout, …) and any partial_response. |
paused | Turn suspended awaiting a wake (form, timer, or rate-limit). The stream closes cleanly — resume later with the same ids. |
form_request | The agent needs input. Carries form_kind (clarify | integration | signature | approval), form_id, and a payload. |
The canonical answer is on the complete event
Render delta text live for UX, but the authoritative answer is complete.final_response — not the concatenation of deltas. The agent may revise mid-stream, so deltas are interstitial narrative, while final_response equals the persisted message.
Transient diagnostic events (thinking, heartbeat, retry_notification, browser_frame) may also appear; they are not part of the stable contract and are safe to skip.
Resuming a dropped stream
The subscribe endpoint is idempotent and multi-tab safe. To resume after a disconnect, send the last frame id back — either as the Last-Event-ID header or a ?since= query parameter — and the server replays only what you missed.
Answering a form_request
When a form_request arrives, the turn pauses (you may also see a paused event). Send the user's answer back as a normal POST /sessions/stream with the same session_id — the platform routes it to the waiting form instead of starting a new turn, and the original turn resumes on its existing stream.
Stopping a turn
POST /api/v1/sessions/{session_id}/stop cancels the active turn. It is idempotent and always returns 204; the stream then emits a cancelled event with reason: "user_stop".
Quick Links