/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 anid: 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:
: is a keep-alive, sent every 15 seconds. Ignore it.
Reconnecting without losing anything
Send the lastid: 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 a429 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.
Shell
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.
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 theafter_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
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 anawaiting_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:
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.