Skip to main content
Two streams let an agent follow the work instead of polling for it. Both are server-sent events on the same /v1 API as everything else, and take an API key (atn_live_…) or an OAuth connection, with the read scope.

Frames

An event frame has an id: line, an event: line and one data: line of JSON. The awaiting_person and stream_error frames carry no id: line, so they never move your cursor:
A line starting with : is a keep-alive, sent every 15 seconds. Ignore it.

Reconnecting without losing anything

Send the last id: you received as ?after= or as the Last-Event-ID header (an EventSource does this for you). The stream replays everything after it, in order, then keeps tailing. Nothing between the two connections is lost. A stream also ends on its own: The frame is stream_error, not error, because an EventSource fires its own error event on every disconnect.

How many streams can be open

An organisation may hold 100 streams open at once, both kinds together. Opening one more is a 429 rate_limited with Retry-After: 30, before any frame is sent. One GET /v1/events stream carries every event in the organisation, so most agents need only one; if you meet the limit, look for a reconnect that does not close the stream it replaces.

GET /v1/events

Read-only. No credits. Works on a spent balance.
Your organisation’s events, from every workspace in it.
Shell
The log keeps 30 days. An after id older than that, or one that is not your organisation’s, is a 404: reconnect without after and read anything older from the objects themselves. An event id here is the id in the body a webhook receiver gets for the same event, so a client that takes both can dedupe on it.

GET /v1/sessions/{id}/events

Read-only. No credits. Works on a spent balance.
One session’s run. Each frame’s id is the step’s sequence number and event is its kind (text.delta, tool.start, ask.request, session.end and so on). The stream ends at the end of the turn it is in: the first session.end after after that finishes a run (completed, failed or cancelled). It sends nothing past it, even when a later turn is already in the log; read that turn from its own after_seq, or reconnect with the last id you received. A run that stops to wait on you also writes a session.end, with status awaiting_person; the stream stays open through it. A session in another organisation is a 404.

Following one turn of a thread

A thread’s log holds every turn of it. To follow only the turn you just started, open the stream at the after_seq the ask answered with: after_seq is the last sequence number written before your message, so your message is the first frame of GET /v1/sessions/{id}/events?after=<after_seq>. You get every step of this turn, none of an earlier turn’s, and the stream ends at this turn’s end. A retry with the same idempotency_key answers the same after_seq as the first call.
Shell
If you send a message while the thread’s previous run is still going, your message is written first, that run writes its last steps (its answer, or its failure) after it, and then your turn is queued. Your message carries behindRun: true. A stream opened at your after_seq, or reconnected right after your message, leaves that run’s last steps out: you get your message, then your turn from the moment it is queued. A stream opened anywhere else sends the log as it is. If that run fails, your turn still runs: the failure is in the log as the run’s last word, and the session is not ended. If that run is cancelled after your message, the thread stops with it and your message is not answered: the stream at your after_seq ends after your message, with no end frame. Over MCP, ask does this for you. With a progressToken, the call follows the turn from after_seq and reports each step as a progress notification. Its result carries the same point as cursor (on the ask verb, in _meta as attensira/cursor), for a new thread and a continued one alike. MCP get takes no cursor: to follow the turn yourself, stream GET /v1/sessions/{id}/events?after=<cursor>.

When the run is waiting on you

When the run stops for an approval, the stream sends an awaiting_person frame. It has no id, so it never moves your cursor, and it is sent again whenever you reconnect while the run is still waiting:
Approving needs a key with an approve scope for that change; declining needs steer. See Inbox and API keys. The run carries on once the ask is answered, and the stream carries on with it.