This is a different flow from API triggers. A trigger API key runs one agent on demand and is scoped to a single trigger. An account key holds an open-ended conversation with any agent installed in the account’s workspaces, scoped by
sessions:write.Start a chat
sessions:write on a workspace the key’s account owns. The scope is deliberately sessions, not workspaces, so workspace scope alone can’t quietly grant conversations.
Request body
Example
Response
Returns201 with the session. The key is recorded as the session’s initiator, serialized as type: "api_key":
Read the conversation
workspaces:read, plus agents:read when the session ran an agent — see Fetch session events.
Events default to newest-first (sort_by=-id) with a limit of 20, so a naive read returns the tail of the conversation in reverse. For polling, pass from_id: it’s an exclusive cursor (id > from_id), so each poll returns only what happened since the last one. Event ids are UUIDv7 and therefore time-ordered.
The agent’s reply arrives as a runtime_done event whose content.text carries the message. One turn can emit several runtime_done events, because subtasks and tool runs complete with empty content ({}) before the top-level turn does. Read the reply from the runtime_done that carries content.text, not simply the first one to appear. Events persist when a turn completes, not while the model is streaming — a poll during generation returns nothing new until the turn finishes.
If you would rather not poll, the same events are available over a WebSocket — see Stream the conversation.
Example
Response
from_id=9d2f4a1b-... to only see events after this one.
Stream the conversation
Instead of polling, connect to the session events WebSocket and receive each event as it is written.workspaces:read, plus agents:read when the session ran an agent. Privacy is evaluated identically too — a session the key cannot see closes the connection with 404 rather than reporting an authorization error.
Authenticate with the same API key, sent as an Authorization: Basic header on the connection request.
The
Authorization header is the only way to authenticate this connection. A browser’s built-in WebSocket cannot set request headers, so connect from a server-side or native client instead.Query parameters
Example
items array returned by GET /sessions/{session_id}/events.
This endpoint is not listed in the sidebar or in
openapi.yaml: OpenAPI cannot describe WebSockets. It is part of the public API all the same.Send a follow-up
sessions:write — see Post a follow-up event to a session.
The message is authored by the key itself (author.type: "api_key"); a key may never author as anyone else. The agent_id field is accepted but ignored for a key — it only retargets for user viewers — so a key cannot switch which agent answers mid-conversation. Start a new session instead.
Example
GET /sessions/{session_id}/events?from_id=... until a new runtime_done event appears.
Containment
A key converses only in the chat sessions it initiated. Posting into any other session — another key’s chat, a person’s chat, a trigger session — is404, not 403: a key cannot inject messages into a human’s conversation, and cannot even learn that the other session exists.
Reading is broader than writing: with workspaces:read, a key can read every session in its account’s unrestricted workspaces, it just cannot speak in them.