Environments
GET /api/orgs/{org_id}/environments
Section titled “GET /api/orgs/{org_id}/environments”List Environments
List the org’s environments (built-in first — it is created first).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| org_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | EnvironmentResponse[] |
| 422 | Validation Error | HTTPValidationError |
POST /api/orgs/{org_id}/environments
Section titled “POST /api/orgs/{org_id}/environments”Create Environment
Create an environment.
A built-in sandbox’s name is not the caller’s to choose: there is at most
one per org and it is always called “Built-in sandbox”, so name is
ignored entirely for BUILTIN_SANDBOX (older clients may still send one).
A remote environment, by contrast, requires a name — there can be many.
For a remote environment this also mints the daemon token and returns the install one-liner. That response is the only place the plaintext token ever appears — losing it means creating a new environment.
A custom-image environment needs an image_ref, is capped by the org’s
max_custom_environments entitlement, and comes back validating: the
pull and the conformance probe run on the agent-runner and the row settles
on online or invalid when they finish. Poll GET for it, exactly
as the create-modal already polls a remote machine waiting to call home.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| org_id | path | string (uuid) | yes |
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
| image_ref | string | no | |
| kind | EnvironmentKind | yes | |
| name | string | no |
Responses
| Status | Description | Body |
|---|---|---|
| 201 | Successful Response | CreateEnvironmentResponse |
| 422 | Validation Error | HTTPValidationError |
GET /api/orgs/{org_id}/environments/{environment_id}
Section titled “GET /api/orgs/{org_id}/environments/{environment_id}”Get Environment
Fetch one environment. The create-modal polls this until status is
online — i.e. until the machine has called home.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| environment_id | path | string (uuid) | yes | |
| org_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | EnvironmentResponse |
| 422 | Validation Error | HTTPValidationError |
PATCH /api/orgs/{org_id}/environments/{environment_id}
Section titled “PATCH /api/orgs/{org_id}/environments/{environment_id}”Update Environment
Edit an environment’s operator-owned settings.
The one that matters here is exec_timeout_s — how long a single command
may run. It is a ceiling, not a default: an agent’s bash call asks
for the time it needs and is clamped to this, so raising it does not make
every trivial command hold a long lease.
The value is clamped into the platform’s bounds rather than rejected — the
bounds are ours, not the caller’s mistake — and the response reports the
ceiling that will actually be enforced, which for an unset environment is
its per-kind default rather than the null that is stored.
image_ref is the other one, and it is the Update button for a custom
image: it re-points the environment, drops the pinned digest and sends
it back through validation. That is the only way a new push to the same tag
ever takes effect — a validated environment follows a digest, never a tag,
so nothing changes under a running agent until somebody asks for it. Note
that dropping the pin is not merely bookkeeping: it is the signal the
validator reads to decide it should resolve the reference again rather than
re-probe the bytes POST /validate would have re-probed.
name is the operator’s label for a machine or an image they set up. The
built-in sandbox is not one of those: it is the same container pool for
every org, named by us and described everywhere in the product by that
name, so renaming it is refused rather than quietly ignored.
Admin-only: it decides how long an agent may hold a machine the org owns, and which image runs on our infrastructure.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| environment_id | path | string (uuid) | yes | |
| org_id | path | string (uuid) | yes |
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
| exec_timeout_reset | boolean | no | |
| exec_timeout_s | integer | no | |
| image_ref | string | no | |
| name | string | no |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | EnvironmentResponse |
| 422 | Validation Error | HTTPValidationError |
DELETE /api/orgs/{org_id}/environments/{environment_id}
Section titled “DELETE /api/orgs/{org_id}/environments/{environment_id}”Delete Environment
Delete an environment.
Agents and chats pointing at it keep working: the FK is SET NULL, so
they fall back to the resolution order rather than breaking.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| environment_id | path | string (uuid) | yes | |
| org_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 204 | Successful Response | |
| 422 | Validation Error | HTTPValidationError |
GET /api/orgs/{org_id}/environments/{environment_id}/activity
Section titled “GET /api/orgs/{org_id}/environments/{environment_id}/activity”Get Environment Activity
What has actually run here — the audit sandbox_runs has always kept.
Every one of these rows was already being written on every acquire and teardown; nothing in the product read them back. That is why an org whose agents mysteriously stopped getting a workspace had no way to see that the last four runs failed, and why “is anybody using this environment?” was a question only answerable by deleting it.
A separate request from the environment itself, and deliberately not folded into the list: the settings page polls the list, and a reporting join over the run history has no business running every few seconds against every environment an org owns. This is fetched once, when somebody opens the panel.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| environment_id | path | string (uuid) | yes | |
| limit | query | integer | no | |
| offset | query | integer | no | |
| org_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | EnvironmentActivityResponse |
| 422 | Validation Error | HTTPValidationError |
POST /api/orgs/{org_id}/environments/{environment_id}/ping
Section titled “POST /api/orgs/{org_id}/environments/{environment_id}/ping”Ping Environment
Actually talk to the machine, and report what happened.
ping is the one verb every daemon has ever served, and the daemon
deliberately does not count it as activity — it never touches the workspace
and never renews a pinned workspace’s claim — so this is safe to press
against a machine somebody is working on.
It answers a question no stored field can. status and last_seen_at
are both history; connected says a socket exists. Only a round trip
says the machine on the other end is still answering, which is what an
operator who has just changed a firewall rule or woken a laptop is asking.
Failures come back as ok: false with a sentence, never as an error
status: the request succeeded, the machine is what did not.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| environment_id | path | string (uuid) | yes | |
| org_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | EnvironmentPingResponse |
| 422 | Validation Error | HTTPValidationError |
POST /api/orgs/{org_id}/environments/{environment_id}/reveal-token
Section titled “POST /api/orgs/{org_id}/environments/{environment_id}/reveal-token”Reveal Environment Token
Read a remote machine’s current daemon token back, in the clear.
The View Install Command dialog’s other button: unlike rotate-token,
this mints nothing and changes nothing on the row — it only decrypts the
copy already stored and hands it back, so an admin who still has a working
token installed somewhere can see it again without re-keying (and
breaking) every other machine on this environment.
Why this is safe to expose at all: whoever may call it — an org admin,
the same gate as rotate-token — could already mint a token that works
with no confirmation beyond the click, so reading the existing one back
grants no new capability; it is strictly the less disruptive of the two
ways to end up holding a working token. See
EnvironmentService.reveal_token for the full rationale.
POST rather than GET on purpose — nothing should prefetch or cache
a secret. The response also carries Cache-Control: no-store, belt and
braces against any intermediary that caches POST responses.
404 for no such environment, 422 for a kind with nothing to reveal (not a remote machine), 409 for a remote machine whose token predates this feature — the row has never had an encrypted copy stored, so there is nothing here to decrypt and rotating once is the only way to get one.
Admin-only, like every other endpoint that hands back a working credential.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| environment_id | path | string (uuid) | yes | |
| org_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | RevealTokenResponse |
| 422 | Validation Error | HTTPValidationError |
POST /api/orgs/{org_id}/environments/{environment_id}/rotate-token
Section titled “POST /api/orgs/{org_id}/environments/{environment_id}/rotate-token”Rotate Environment Token
Re-key a remote machine and hand back a fresh install command.
The fresh plaintext token is returned directly here, same response shape
as a create — and, like a freshly created one, it can also be read back
again later through POST .../reveal-token without another rotation.
Two things it is for: an install command that was lost, and a machine being
replaced. Before it, both meant deleting the environment and creating
another, which SET NULLs every agent and chat pointing at it and throws
away the run history — a destructive operation standing in for a
credential rotation.
The currently connected daemon keeps serving. Authentication happens at connect, so an established socket is unaffected and only the next connect needs the new token. Say so in the UI: the natural assumption is the opposite, and an operator who believes this cuts a machine off will never press it.
Admin-only — it mints a credential.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| environment_id | path | string (uuid) | yes | |
| org_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | CreateEnvironmentResponse |
| 422 | Validation Error | HTTPValidationError |
GET /api/orgs/{org_id}/environments/{environment_id}/usage
Section titled “GET /api/orgs/{org_id}/environments/{environment_id}/usage”Get Environment Usage
Which agents and chats point at this environment.
The delete dialog’s honest version. It has always promised that agents “fall back to their next configured environment” — true, and useless on its own, because the operator still cannot see which agents are about to start running somewhere else. Naming them is the difference between a warning and a decision.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| environment_id | path | string (uuid) | yes | |
| org_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | EnvironmentUsageResponse |
| 422 | Validation Error | HTTPValidationError |
POST /api/orgs/{org_id}/environments/{environment_id}/validate
Section titled “POST /api/orgs/{org_id}/environments/{environment_id}/validate”Revalidate Environment
Re-run validation against the pinned digest — the Re-validate button.
Distinct from PATCH with an image_ref, and the difference is which
bytes get probed. This re-checks the exact image the org’s agents are
running and never re-resolves the tag, so pressing it cannot move an
environment onto a push nobody has approved; Update is the one that
re-resolves and re-pins, because it is deliberately pointing at something we
have not looked at.
That split is what makes the product’s headline promise true — we pin the digest, so a new push to the same tag changes nothing until somebody asks for it — and neither this endpoint nor the event it pushes carries a mode to say so: a pinned digest on the row is the instruction, and the worker reads it there.
The other reason it exists, and the one it is now actually shaped for: our own sandbox contract moves. An image validated against contract 1 keeps working when contract 2 ships (our release never takes a customer’s agents offline), and re-probing the pinned bytes is exactly how its owner finds out what a rebuild would gain them — a question about this image, which a tag re-resolution would have quietly answered about a different one.
An environment that never pinned anything — it failed before the digest was resolved — has nothing to re-check, so this falls back to pulling the stored reference. That is the only thing it could mean, and it is still useful: it is how a rejected environment gets a fresh verdict.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| environment_id | path | string (uuid) | yes | |
| org_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | EnvironmentResponse |
| 422 | Validation Error | HTTPValidationError |
GET /api/orgs/{org_id}/environments/custom-images/registries
Section titled “GET /api/orgs/{org_id}/environments/custom-images/registries”List Allowed Registries
The registry hosts we will pull a custom image from.
Served rather than hard-coded in the client for the usual reason: the allowlist is a security decision that changes on our side, and a create form listing hosts we no longer accept is a form that produces confident 422s.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| org_id | path | string (uuid) | yes |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | string[] |
| 422 | Validation Error | HTTPValidationError |