The session object
string
Unique id (e.g.
ses_01H...).string
The agent this session runs.
integer
The pinned agent version.
string | null
The deployment that created this session, if any.
string | null
The computer this session provisioned for its filesystem and shell tools, or
null when its toolset needs none. It is a real computer — it lists under GET /v1/computers as session {id} and takes exec, snapshot and fs/* — but it belongs to the session: DELETE on it is refused while the session can still run, and it is destroyed when the session ends.string | null
Why the last turn yielded control — see below. Read it whenever the session is not
running; do not treat idle as success.string
Effective completion window for this session.
integer
Per-session cap, in micro-USD. Always set: it falls back to the agent’s
budget.max_task_micro_usd.integer
Cumulative spend on this session so far, in micro-USD.
object
Cumulative token counts across five billed tiers.
object[]
What the session is parked on. One array, two kinds of row, distinguished by
kind — which defaults to "tool", so a row without it is a held tool call.A kind: "tool" row is resolved with tool confirmations and accompanies stop_reason: "awaiting_approval". A kind: "question" row is resolved with answers and accompanies awaiting_answer. A session parked on both reports awaiting_approval and carries both.object | null
JSON Schema the final result must satisfy, inherited from the agent unless overridden at create.
object | null
The typed result, populated when the session goes idle satisfying
output_schema. See Structured outputs.object
Arbitrary key/value pairs.
string | null
When the session first started running.
string | null
When the session reached a terminal state (
completed, failed, cancelled).string
Creation timestamp.
Lifecycle
A session moves through thesestatus values:
Whenever a turn yields control,
stop_reason is set to one of:
idle is resumable and completed is terminal — both are non-running. Always branch on stop_reason rather than treating idle as success. Anything worth keeping is written to Files; scratch work stays in the sandbox.Create a session
POST /v1/sessions → 201 Created. Creates and starts the session. Inherits the agent’s configuration; you may override the window, set a per-session budget, and provide an initial message.
string
required
The agent to run.
integer
Pin a specific version. Defaults to the agent’s
current_version.string
required
Initial input. Starts the first turn immediately.
string
Override the completion window (
immediate | priority | loose).integer
Per-session cap in micro-USD. Work pauses at the cap.
string
Attribute this session to a deployment, so its spend shows under that schedule in the ledger. Defaults to
null.string
The persona this session acts as — an
idn_ id or the persona’s name. The agent must hold a grant to it. Defaults to null.object
JSON Schema the final result must satisfy. Overrides the agent default. See Structured outputs.
boolean
When
true, a session that cannot produce a conforming object goes idle with stop_reason: "error". Overrides the agent default; requires an output_schema on the session or the agent.object
Arbitrary metadata.
Retrieve a session
GET /v1/sessions/{id} → 200 OK with the current status, stop_reason, and usage.
Response
List sessions
GET /v1/sessions → 200 OK, cursor-paginated. Filter with ?agent_id=, ?deployment_id=, and ?status=. See Pagination.
?handoff_from=ses_… lists the top-level sessions that root handed off — the room’s member work — newest first. A thread the root delegated to is not a handoff; those are GET /v1/sessions/{id}/threads. ?handoff_key=room:ses_… lists the top-level sessions handed off under that exact key — the room’s seated members, whoever seated them — newest first.
Hand off to other agents
POST /v1/sessions/{id}/handoffs → 200 OK. Scope sessions:write. The session {id} — top-level and not yet ended — hands message to one or more agents of your organization, each started as a top-level session of its own marked metadata.handoff: { from_session, from_agent, key, depth }. Who may be reached is the sender’s agent’s handoffs policy, exactly as it governs the model’s own send_to_agent; the receiver’s budget is capped at its agent’s per-task cap.
string[]
required
One to six targets, each an
agt_ id or an agent name. Duplicates collapse to one.string
required
The task. The receiver sees it prefixed with the sending agent and session, so it can tell a handoff from a person.
string
De-duplicates per target: a target still working under this key answers
already_running with that session instead of starting another; send follow-ups to that session.integer
Per-receiver cap in micro-USD; clamped to the target agent’s
max_task_micro_usd.Response
403 forbidden), has ended (409 session_terminal), or is not yours (404 not_found). A chain of handoffs is at most four deep.
Send input
POST /v1/sessions/{id}/messages → 202 Accepted. Sends input to a session and produces events. The reply is the acceptance, not a session snapshot: the turn has not run yet.
string
required
The message text to send.
boolean
When
true, steer a running session in one call: the current turn stops at its next commit boundary and the new message is applied. This is the common case. Defaults to false.boolean
When
true, a send to a running session is accepted and held: the current turn finishes on its own and the message becomes the input of the turn that follows. On a session that is not running it is an ordinary send. Cannot be combined with interrupt. Defaults to false.string
Accepted and ignored. De-duplicate a retried send with the
Idempotency-Key header — see Idempotency; this body field does nothing.interrupt or queue, sending to a running session returns 409 with code session_running — interrupt first, set interrupt: true to steer it, or queue: true to wait for it.
Response
accepted_seq is the session’s event high-water mark at the moment the send was accepted: every event this turn produces has seq > accepted_seq. That is what to wait on — poll GET /v1/sessions/{id}/events?after_seq=143, or wait for the session’s last_seq to pass it. Do not wait on status alone: the send returns before the first event is committed, so a status read taken immediately after can still describe the previous turn.
Interrupt a session
POST /v1/sessions/{id}/interrupt → 200 OK. Stops the current turn at its next commit boundary, preserving history and the sandbox, and returns the session idle with stop_reason: "interrupted". An optional message is applied as the next input. Unlike cancel, the session stays resumable.
string
Optional input to apply after interrupting.
Response
Respond to a tool confirmation
When a tool withpermission: "ask" runs, the session goes idle with stop_reason: "awaiting_approval" and the blocking calls appear in pending_actions. Resolve each with:
POST /v1/sessions/{id}/tool_confirmations → 202 Accepted, carrying the full session object with the resolved call removed from pending_actions. Like a send, the reply is the acceptance: the tool has not run yet.
string
required
allow to run the tool, or deny to reject it.string
On
deny, fed back to the model as the tool result.string
default:"call"
How far the
allow reaches. call — the default, and what an omitted scope means — approves this one call. session grants the call’s tool for the rest of this session: every later call to that tool name runs without a confirmation. allow only; session with deny is rejected with validation_failed (400) naming scope.deny for a particular call still stands even
while its tool is granted.
When the parked call is the platform’s tool invoker (tools_execute, which runs any catalogue tool
named in its tool argument), the grant is for that target tool: later tools_execute calls
with the same tool run without a confirmation, and a different tool through the same invoker is
confirmed as usual. The target is read from the parked call — the body carries nothing extra — and a
grant for the invoker with no target grants nothing.
Response
Answer a question
An agent that needs something from a person parks the turn: the session goes idle withstop_reason: "awaiting_answer" and the question appears in pending_actions as a row with kind: "question", carrying a prompt and one to three fields. Answer it with:
POST /v1/sessions/{id}/answers → 202 Accepted, carrying the full session object with the answered question removed from pending_actions. Scope sessions:write.
string
required
The id from
pending_actions[].tool_call_id on the row you are answering.object
required
One entry per field, keyed by
fields[].key. A string, or an array of strings for a choice field with multiple: true. Every field must be answered, except one the question marked optional: true — say “no answer” by leaving that key out of the object. A key that is present is validated either way, so "" and [] are validation_failed, not a way to skip.tool_confirmations, not a variant of it. A held tool call is answered with a verb and no payload; a question is answered with a payload and no verb. Posting an answer for a kind: "tool" row is rejected with validation_failed (400) naming tool_call_id, and points you at the other route.
A choice field accepts anything the person types unless it sets other: false, in which case only its listed options are accepted. The values are read back by the model as words, so send the option text and not an index.
Response
Raise the budget
PATCH /v1/sessions/{id}/budget → 200 OK. Scope sessions:write.
A session that hit its cap stops with stop_reason: "budget_paused". Raise the cap and it becomes resumable.
integer
required
The new per-session cap, in micro-USD.
budget_micro_usd.
Errors — validation_failed (400) for a cap that is not a raise; not_found (404); insufficient_credits (402) when the organization cannot cover the new cap.
Cancel a session
POST /v1/sessions/{id}/cancel → 200 OK. Terminally stops the session and moves it to cancelled. Any in-flight turn is stopped. Use interrupt instead if you want to keep the session resumable.
Response
Download a workspace
GET /v1/sessions/{id}/workspaces/{phase} → 200 OK with the archived working directory of a session’s sandbox, as it was at one boundary of its work: end (the session finished, or its idle sandbox was reclaimed), or card_start / card_end (it moved a board card to doing / done). start has none — a fresh sandbox’s disk is its base image. The body is a tar.gz; its sha-256 is in the x-workspace-sha256 header, and an unchanged workspace archives to the same hash. ?kind=diff returns the git diff HEAD of the repository in the workspace instead, when there was one. ?card_id=crd_… picks that card’s boundary; without it the newest one of the phase answers. Credential files (.env* and the platform’s own) and re-derivable caches such as node_modules are never archived.
Needs audit:read, like stored proxy calls: it is captured content. A boundary with nothing stored — or a session in another organization — is 404 not_found.
Stream events
List and stream a session’s events, resumable by
seq.