Subscribes to a real-time session event stream via SSE: assistant output messages, tool calls, and status changes. Upon connection, the server immediately sends a : connected comment line.
Prerequisites
A session must already be created. For details, see Create Session. {session_id} is in the format sesn_xxx....
Endpoint
GET /sessions/{session_id}/events/stream
The request must include Accept: text/event-stream. Upon connection, the server immediately sends a : connected comment line, then pushes session events in real time.
Event envelope
The data field is a Message object with the following common fields:
| Field | Type | Description |
|---|---|---|
object | string | Always message |
id | string | Event ID (prefix msg_ or out_). Clients use this ID for business-level deduplication |
created_at | string | ISO 8601 timestamp |
role | string | user | assistant | tool |
type | string | Event type, see table below |
status | string | Always completed |
content | array | ContentBlock array, same structure as the send side |
is_error | bool | Only on tool output events (tool_call_output / mcp_call_output), at the Message top level (not inside content[].data): true means the tool execution failed or was interrupted/rejected; data still contains at least call_id and output. Clients should read the top-level is_error to determine success and read data.output for the output |
metadata | object | Event additional information (such as thread_id, call_id, etc.) |
Server-pushed event types
type | Description |
|---|---|
message | Assistant output message, can be streamed in chunks |
tool_call | Built-in tool call request |
tool_call_output | Built-in tool execution result |
tool_approval_request | Tool approval request. Sent once only when a tool needs approval; data carries batch_id, call_id, name, arguments, and tool_type (builtin or mcp; MCP also includes server_label). Whether approval is needed is determined by this event |
tool_approval_response | Tool approval ruling (bidirectional: client submits upstream, server also replays downstream). Under requires_action, when a pure interrupt is sent, the server replays result=deny for each call in the batch not yet executed, paired with the corresponding *_call_output (is_error=true); this pairing is for display and audit only, best-effort — the pending set is still governed by pending_call_ids |
function_call | Self-hosted function call request; requires client to backfill function_call_output |
function_call_output | Server acknowledgment echo of received function result |
mcp_call | MCP tool call request |
mcp_call_output | MCP tool execution result |
session_status | Session status change (includes stop_reason, see below) |
error | Runtime error event. Details are in the top-level error object (error.code / error.message), not in content[].data. Such errors are not synchronous 4xx responses of POST /sessions/{session_id}/events; observe via this stream or GET /events?types=error |
session_status and stop_reason
data.content[0].data carries the specific status:
-
session_status:running|idle|terminated -
stop_reasononly appears when the status transitions toidle, with three possible values:{"type": "end_turn"}: Current turn completed normally (model ended on its own){"type": "requires_action", "pending_batch_id": "...", "pending_call_ids": ["..."]}: Waiting for tool approval.pending_batch_idand a non-emptypending_call_idsidentify the current pending approval batch and calls; the pending set is governed bypending_call_ids{"type": "retries_exhausted"}: Retries exhausted, current turn failed
Subscription example
Delta streaming
By default, each event is delivered as a single complete Message (status: "completed") only after generation finishes. To stream text-type events (such as message and reasoning) chunk by chunk as they are generated, enable delta streaming for specific event types with the event_deltas[] query parameter at request time. This parameter can be repeated to declare multiple types; event types not declared are still delivered as a single complete Message.
event_id:
Frame type | Description |
|---|---|
event_start | Event start frame. event.id is the id of the event's final complete Message, and event.type is the event type (message / reasoning); this frame itself carries no object / content |
event_delta | Delta frame. event_id matches the event.id of event_start; delta.content is the newly added ContentBlock fragment, and delta.index is the content-block index |
message / reasoning | The complete Message delivered when the event ends (object: "message", status: "completed"); content is the aggregated full content, identical to when delta streaming is off |
event_delta frame fields:
| Field | Description |
|---|---|
type | Always event_delta |
event_id | ID of the owning event, equal to the event.id of event_start and to the id of the final complete Message; used to merge delta frames into the corresponding event |
delta.type | Always content_delta |
delta.index | Index of the content block within the content array |
delta.content | The newly added ContentBlock fragment, e.g. {"type":"text","text":"…"} |
reasoning and message have delta streaming enabled):
To reassemble the text, accumulate each
event_delta's delta.content in order by event_id + delta.index; the complete Message delivered when the event ends carries the aggregated full content and can be used for verification. A reasoning event may have only an event_start and a complete Message with no event_delta (the thinking preview carries no thinking text).