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.
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:
Repository
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: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
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
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: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
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 mid-turn. The
system prompt is fixed when the agent’s session is built. A
PATCHduring a running turn is not ignored — it applies from the next one. - A session older than this feature refuses them. Setting instructions on a
workspace whose runtime predates them answers
bridge_incompatiblerather than accepting the field and running without it. Deleting the session and creating a new one gets you a current workspace.
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:
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:
Opening message
Passinput and the session starts working immediately:
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.