Skip to main content
Everything POST /v1/sessions accepts, and what each field is for. The quickstart creates a session with a model and nothing else, which is the right way to see the thing work. This page is for the second session — the one that needs your repository, your files, your secrets, or a limit on what the agent may do without asking. Nothing here is required. A session with only a model is a complete session.

The fields

POST /v1/sessions takes more than a model. Every field below is optional.
agent.instructions, agent.approval_mode, agent.permission_rules and metadata can also be changed later with PATCH /v1/sessions/{id}, which takes any combination of those and title.

Eight complete requests

Every field above in isolation, rather than assembled — eight real, complete POST /v1/sessions bodies, each run against production before being written down here, for eight distinct reasons to create one. A computer with nothing running on it yet. No input — the agent waits for a first message rather than starting a turn immediately. Useful when your service decides what to say after the sandbox exists, not before.
A computer that starts working immediately. The quickstart’s shape — input at creation runs the first turn without a second call.
Labeled so a restarted process can find it again. title for a person reading the Console, metadata for your own code — see pagination.md for ?metadata=job:8842.
A worker joining a crew. parent_session_id names the coordinator; the tree it joins is read back with ?root= on the coordinator’s id — see Crews below.
With a function tool declared up front, rather than added with PUT /v1/sessions/{id}/tools afterward — both reach the same place, and declaring it at creation means the agent has it from the first turn.
See required-actions.md for answering the call this produces. Seeded with a file, and an opening message that reads it back. Files land when the workspace comes up — see Files at creation — which needs a turn to run at all. input here is doing that job as much as it is asking a question.
With standing instructions, followed on every turn rather than restated in every message — see Instructions below.
Read-only, so nothing it does can change anything. For a session you want to ask questions of but never let write — see Approval mode and permission rules.

Crews

Several sessions working on one task are a tree, not a new kind of object. Pass parent_session_id when you create one, and the coordinator becomes the root:
?root= returns the whole tree, not only the direct children — a worker can start workers of its own. A root that does not resolve is refused rather than answered with an empty list, because an empty crew and a mistyped id look identical otherwise. POST /v1/sessions/{id}/fork records the same relation, so a forked session is already in its parent’s tree. The difference is only what the new session starts with: a fork copies the workspace and transcript, and parent_session_id on create starts clean. The pointer is history, not a live link. Delete the parent and the child keeps answering where it came from — which is the question you ask when two sessions in one crew behave differently.

What a crew cost

Several numbers rather than one, because one would have to merge three different kinds of certainty: Drop ?root= for one session instead of the tree. On this path root must name the session in the path — it chooses whether to aggregate, not which crew.

Reading a session back

Everything you can set, you can read. GET /v1/sessions/{id} reports the model, the credential, the approval mode, the permission rules, the instructions — and the tools, with any secrets removed:
On the single read only. A page of sessions does not carry it: reading one session’s tools is a second lookup, and twenty of them to answer a question about one is not a trade worth making. That matters after a restart. Before answering a required action you can check the session is configured with the function you are about to answer for, rather than assuming it.

Repository

Two things about this are worth knowing before you rely on it. A 201 does not mean the code is there. The session is created immediately; the clone happens when its workspace comes up, which is later. If it fails, the session still starts with an empty workspace and the reason is on the session:
Read environment.repo.clone_error before concluding the agent ignored your instructions. The branch is reported, not chosen. The clone checks out the repository’s default branch and tells us which one that was; environment.repo.branch is that answer. Sending environment.branch is refused rather than ignored, because storing a value that changes nothing is worse than saying no. Your organization needs a GitHub connection — Console, Settings → App integrations. Without one, a request naming a repository is refused up front rather than producing a session that can never clone.

Files at creation

Not every job is “work on my repo”. When what you have is a CSV, a spec or a PDF, send the bytes:
data is base64 of the file’s bytes. path may be absolute under /workspace or relative to it — notes.md above lands at /workspace/notes.md. A path that resolves outside the workspace is refused, and the refusal tells you where it resolved to. Ceilings are in limits.md: 5 MiB a file, 10 MiB a request, 50 files. Each applies to the decoded bytes, not the base64. A file that could not be written says so. The session still opens — one unwritable path must not cost you the other forty-nine — and an error item names the file and the reason. See troubleshooting.md. A 201 does not mean the files are there — same as the clone. They are written when the workspace comes up, after the clone, so you can drop a config file into a repository you also asked for. They are written only if not already present. A sandbox that was paused and woken keeps whatever the agent did to those files; one that had to be rebuilt gets them again, because they are part of how the session was defined. There is no type: "file_id" and no type: "url". Gobare has no file store to reference, and does not fetch addresses on your behalf. For a large input, use environment.repo.

Files afterwards

The same shape, against a session that is already running:
The answer is what was written, not an acknowledgement — your next move is usually to tell the agent to read it, and you need to know that is safe. Two differences from environment.files: It overwrites. A path that is already there is replaced, because that is what you asked for. Seeded files are the opposite: they never overwrite.

Forking

A new session that starts where the original is, not where it began: the workspace and the transcript come with it. That is the point — trying two approaches from a state worth keeping, without paying for the setup twice or losing the first attempt. The original is untouched. It spends a session slot, so it is refused by the same ceiling with the same project_limit_exceeded as creating one. workspace_copied is worth reading. A fork whose workspace could not be copied is still a useful session, and believing the files are there when they are not means finding out from the agent — the worst place to find out. A session mid-turn, or with queued messages, is refused with conflict: fork it when the work in flight has finished.

Reading it back

A read is of the last snapshot, not the live sandbox. captured_at says which moment, and state: "missing" means none has been taken yet — which is not the same as an empty workspace. POST /files/refresh takes a fresh one; it is a separate call, and needs sessions:write, because it wakes a paused sandbox and that costs money and seconds. Environment files and key material are withheld, and say so — permission_denied rather than a 404 that would read as “not there”. Directories and withheld paths are marked readable: false in the listing. This is what answers “the agent said it wrote that, did it?” — a question the artifact list cannot, because artifacts only ever cover /workspace/outputs. It is not remembered. A live write is working state, not part of how the session was defined, so a sandbox rebuilt from nothing will not have it. A paused sandbox keeps it — and a paused session is woken to serve this call rather than refusing it.

Environment profiles

Environment profiles are named groups of variables, managed in the Console. Bind them by id:
Values are never returned by this API, and there is no endpoint to set them. variable_count is there so you can recognise the profile you meant. Omitting profiles inherits the organization’s default group; sending an empty list binds nothing. A profile belonging to another organization answers not_found rather than a permission error, so an id cannot be probed for existence.

Instructions

These sit on top of the product’s own rules rather than replacing them, and they apply to every turn. Where your instructions and our safety rules disagree, ours win — so instructions is how you shape an agent’s behaviour, and approval_mode and permission_rules below are how you constrain what it may actually do. Two different jobs; instructions are not a permission system. Two things worth knowing before you rely on them:
  • They take effect on the session’s next workspace, not the next turn. The system prompt is fixed when the agent’s session is built, and a running session does not rebuild it mid-life. A PATCH during a running turn is not ignored — it is stored and reads back correctly — but the running agent keeps the instructions it started with until the workspace is rebuilt (paused-and-woken does not count; a fresh boot does). The same is true of PUT /tools — a read-back that matches what you sent is not the same claim as the agent having it.
  • A session older than this feature refuses them. Setting instructions on a workspace whose runtime predates them answers bridge_incompatible rather than accepting the field and running without it. Deleting the session and creating a new one gets you a current workspace.
Send null or "" to clear them.

Approval mode and permission rules

read_only and MCP

A tool this session cannot vouch for is a tool it will not run. MCP servers declare what a tool does with readOnlyHint in tools/list; read_only allows the ones that carry it and refuses the ones that do not, because “no annotation” is not the same as “harmless” — and an organization’s private plugin has been reviewed by nobody. This bites the obvious shape: a read-only investigation with a read-only runbook server. If the server does not annotate, either have it do so, or say so yourself:
A rule beats the mode, in both directions, which is what makes this expressible. A refusal is visible. It arrives as approval.resolved on the event stream with approved: false, the tool’s name, a code of denied_read_only or denied_by_rule, and the same sentence the agent was given. Without that, an agent saying “I could not reach the runbook” is indistinguishable from a model that did not try — which is what it looked like before this event carried a name. permission_rules narrows further, and applies in every mode:
Both are readable back on the session, so what a session enforces is never something you have to remember having sent.

Opening message

Pass input and the session starts working immediately:
It is all or nothing. A 201 means the session exists and the message was accepted. If the message cannot be sent, the session is destroyed and you get the send’s error — so a non-2xx means nothing exists and there is nothing to clean up. Unless we say otherwise, in the error itself. Destroying the session is the one step that can also fail. When it does, the message tells you so and names the session, because the alternative is a slot held by something you were never given the id of:
input takes the same shapes as input.message content: a plain string, or the content-part array. The steps below then apply unchanged; skip step 4.

Next

  • input — sending the session work once it exists
  • preview — publish what it builds
  • tools — give it your own functions