Skip to content

Stream events

GET /v1/sessions/{session_id}/events/stream

Streams a session’s events as server-sent events while they happen.

Without Last-Event-ID, the stream sends only events that happen after it opens, so open it before you send a message.

Each key can hold up to 24 open streams across all its sessions, unless your deployment sets a different limit. The limit applies on each server instance, so a key whose streams land on more than one instance can hold more in total. A stream counts until your client disconnects.

The key must have the tasks:read capability. For what each capability allows, see Keys.

Headers

NameTypeRequiredDefaultDescription
Last-Event-IDstringNoThe ID of the last event you received from this session. The stream replays every event after it, then continues live. sevt_0_0 replays from the start. A send receipt’s ID (sevt_receipt_<uuid>, returned when you send an event) returns 400.

Path parameters

NameTypeRequiredDefaultDescription
session_idstringYesThe ID of the session to stream.

Query parameters

NameTypeRequiredDefaultDescription
event_deltasbooleanNoNot supported. Omit it. Any value returns 400.

Response

Returns 200 with the content type text/event-stream. Each event is one frame, described in Frames. The : open line is a comment, sent once when the stream opens, and SSE clients ignore it.

: open
id: sevt_40_0
event: agent.message
data: {"type":"agent.message","id":"sevt_40_0","processed_at":"2026-10-21T01:42:24.003Z","content":[{"type":"text","text":"The output is `first`."}]}
id: sevt_40_1
event: session.usage
data: {"type":"session.usage","id":"sevt_40_1","processed_at":"2026-10-21T01:42:24.003Z","usage":{"input_tokens":6,"output_tokens":82,"cache_read_input_tokens":44428,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"active_seconds":7.288}}
id: sevt_41_0
event: session.usage
data: {"type":"session.usage","id":"sevt_41_0","processed_at":"2026-10-21T01:42:24.043Z","usage":{"input_tokens":6,"output_tokens":82,"cache_read_input_tokens":44428,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"active_seconds":7.331}}
id: sevt_41_1
event: session.status_idle
data: {"type":"session.status_idle","id":"sevt_41_1","processed_at":"2026-10-21T01:42:24.043Z","stop_reason":{"type":"end_turn"},"stop_details":null}
event: ping
data: {"type":"ping"}

Errors

This endpoint can return these errors as well as the common errors.

Statuserror.typeCause and fix
400invalid_request_errorLast-Event-ID is not an event ID of this session. Send an ID from this session’s events, or omit the header.
400invalid_request_errorThe request includes the event_deltas query parameter. Remove it.
404not_found_errorNo session with this ID exists, or this key can’t read it. Check the session ID.
429rate_limit_errorThis key already holds the most open streams allowed on the server instance that answered, 24 by default. Close a stream you no longer read, then retry.

Frames

Each event is one frame with id, event and data, and the frame’s id is the event ID. Events arrive whole, without deltas, and agent.message carries the full reply. Every 20 seconds the server sends a ping frame with no id.

Event order in a turn

Stop reading when a session.status_idle arrives whose stop_reason.type is anything other than requires_action. A session.status_idle with requires_action means the agent is waiting for you. A session.error with retry_status.type: "retrying" doesn’t end the turn, so keep reading. A session.error with retry_status.type: "exhausted" is followed by a session.status_idle with retries_exhausted.

A turn in which the agent asks to run a tool looks like this:

  1. The stream shows user.message, then session.status_running.
  2. The stream shows agent.tool_use for the call, then session.status_idle with stop_reason.type: "requires_action" and the call’s ID in event_ids.
  3. To answer, send a user.tool_confirmation event with Send events for each call. The stream shows it, then session.status_running once every waiting call is answered. Until you answer every ID in event_ids, the session keeps waiting and the stream sends no further status event.
  4. The stream shows agent.tool_result, then agent.thinking if the agent reasoned, then agent.message. A session.usage event follows each model call.
  5. The stream shows session.usage, then session.status_idle with stop_reason.type: "end_turn". The turn is over.

How a stream ends

The server doesn’t close the stream when a turn ends. Close it yourself when you stop reading, because an open stream counts against the key’s limit.

A stream can end with an event: error frame, without an id, whose data is an error body. The server sends nothing after it, so close the connection yourself.

error.typeWhyWhat to do
api_errorThe connection has carried 8 MiB.Reconnect with Last-Event-ID.
invalid_request_errorThe session was archived.Stop. The history stays readable.
authentication_errorThe key was revoked or expired, or its owner left the organization.Stop. To keep reading, open a new stream with a valid key.
permission_error, not_found_errorThe key lost access to the session.Stop. To keep reading, open a new stream with a key that can read the session.

A stream can also close with no frame, for example when the server restarts. The SDKs do not reconnect for you.

Reconnecting

To resume a stream without missing events, follow these steps:

  1. Keep the id of the last event you received.
  2. Reopen the stream with a Last-Event-ID header set to that ID. The server replays every event after it, then continues live.

If you have not received any event yet, send sevt_0_0 to replay the whole history. To read the history first, list it with List events and stream from the last listed ID. You can also open the stream first, then list the history and skip any event IDs the stream has already delivered.

Example

cURL
curl -sSN "$CONTEXT_BASE_URL/v1/sessions/dd13d728-93e3-440a-874c-4efeb58c97e0/events/stream" -H "x-api-key: $CONTEXT_API_KEY"
# Resume after the last event you received
curl -sSN "$CONTEXT_BASE_URL/v1/sessions/dd13d728-93e3-440a-874c-4efeb58c97e0/events/stream" -H "x-api-key: $CONTEXT_API_KEY" \
-H "Last-Event-ID: sevt_29_0"
Python
sid = "dd13d728-93e3-440a-874c-4efeb58c97e0"
with client.beta.sessions.events.stream(sid) as stream:
for event in stream:
print(event.id, event.type)
if event.type == "session.status_idle" and event.stop_reason.type != "requires_action":
break
# Resume after the last event you received
stream = client.beta.sessions.events.stream(sid, extra_headers={"Last-Event-ID": "sevt_29_0"})
TypeScript
const sid = "dd13d728-93e3-440a-874c-4efeb58c97e0"
const stream = await client.beta.sessions.events.stream(sid)
for await (const event of stream) {
if ("id" in event) console.log(event.id, event.type)
if (event.type === "session.status_idle" && event.stop_reason.type !== "requires_action") break
}
// Resume after the last event you received
const resumed = await client.beta.sessions.events.stream(sid, {}, { headers: { "Last-Event-ID": "sevt_29_0" } })