Skip to content
Fuse
Esc
navigateopen⌘Jpreview

Act on an environment

Supported actions:

  • rotate-token — generate fresh per-VM credentials, upload them to the guest filesystem, and persist the orchestrator’s encrypted copy. Returns 204. Note: the running fused process reads its token once at startup and keeps serving the old token until it is restarted; rotation currently takes full effect only after the guest agent restarts.
  • drain — first phase of two-phase teardown. Runs the environment’s configured drain command inside the guest (default: the fused drain command) so in-guest workloads quiesce gracefully; an empty drain command only transitions the state. The VM transitions to draining but is not destroyed; a subsequent DELETE tears it down. Only running VMs can be drained — any other state returns 409. If the drain command fails the VM remains in draining so the caller can still DELETE; the failure is returned as 5xx.
  • fork — create a new environment seeded from a checkpoint of this one. With reuse_snapshot_id empty the server snapshots the source first; otherwise it reuses that ready snapshot (it must belong to this VM). The fork is pinned to the source’s host, receives its own guest credentials (fused is restarted), and is returned as a new Environment with 201. GPU environments (spec.gpus > 0, including MIG) and QEMU-hosted environments cannot be forked: a vfio device cannot be checkpointed, so the request returns 409 conflict. That is a permanent property of the environment and must not be retried.
  • exec — run a single command inside the guest and return its exit code, stdout, and stderr. Takes an ExecEnvironmentRequest body and returns an ExecEnvironmentResponse. See the exit-code contract below.

Exec: a non-zero exit code is not an HTTP error. A guest command that exits non-zero comes back as 200 with that code in exit_code. The command ran, and it failed; that is the answer the caller asked for, not a transport failure. Clients and SDKs must surface it as a value and must not raise on it.

HTTP errors are reserved for the cases where the command could not be run at all:

  • 400 invalid_argument — neither cmd nor shell was set, or both were.
  • 403 unauthorized — exec requires the master token. API keys are refused: exec is root in the guest, and keys carry no scopes today.
  • 404 not_found — unknown VM.
  • 409 conflict — the VM is not running (e.g. draining).
  • 501 unimplemented — the provider has no guest to exec into (e.g. the in-memory stub).

Fork error mapping. Fork returns 409 conflict for three distinct preconditions:

  • the reused snapshot (reuse_snapshot_id) is not in state ready;
  • the source holds a GPU passthrough device (permanent — do not retry);
  • with reuse_snapshot_id empty, the implicit seed snapshot is refused because the snapshot quota (count or bytes) is exhausted. This 409 has nothing to do with a reused snapshot’s state, so do not infer the cause from the status code alone.

404 covers an unknown VM or snapshot, including a reused snapshot that is no longer present on the host.

Known limitation: the remaining fork and rotate-token precondition failures — source VM not running, and a provider with no fork support — still return 500 internal rather than 409/501. Treat the message, not the status code, as the contract until these move to 4xx.

POST/v1/environments/{vmId}
Authorization
AuthorizationBearer token · headerrequired
`Authorization: Bearer <token>` — either the static master token (`ORCH_AUTH_TOKEN`) or a revocable API key issued via /v1/api-keys (API keys require Postgres; without a database only the master token is accepted). Master-only surfaces (exec, attach, API key management) refuse API keys with 403. **Authentication can be disabled entirely.** When neither `ORCH_AUTH_TOKEN` nor an API key store (`DATABASE_URL`) is configured — the default local-development posture — the bearer middleware becomes a pass-through: every operation this document marks as secured is reachable with no credentials, requests carry no principal, and master-only surfaces therefore admit everyone. Never run that configuration on a reachable network; set `ORCH_REQUIRE_AUTH=true` to make the orchestrator refuse to start without a master token.
or
fuse_sessionAPI key · cookierequired
HttpOnly session cookie set by POST /login for browser callers. Consulted only when no Authorization header is present.
Path parameters
vmIdstringrequired
VM identifier assigned by the orchestrator.
Query parameters
actionstringrequired
The action to perform.
Allowed:rotate-tokendrainforkexec
Request body
application/json
Required for `exec`, which rejects an empty body with 400. Optional for `fork` (ForkEnvironmentRequest). Ignored by `rotate-token` and `drain`.
One of:
ExecEnvironmentRequest
cmdstring[]
The argv to run in the guest, e.g. `["ls", "-l", "/var/log"]`. Preferred over `shell`: argv has no quoting rules and cannot be turned into an injection by interpolating a value.
shellstring
A command line to run under `sh -lc`. The explicit opt-in for what argv cannot express: pipelines, redirects, and globs.
timeout_msinteger
Bounds how long the command may run inside the guest. Zero (or omitted) takes the server default of 60000 ms. Values above the server ceiling of 600000 ms are clamped to it, not rejected.
ForkEnvironmentRequest
reuse_snapshot_idstring
Existing ready snapshot of this VM to seed the fork from. Empty means snapshot the source first.
commentstring
Free-form note attached to the seed snapshot.
Responses
200Drain succeeded (body is the updated Environment with `state == "draining"`), or exec completed (body is an ExecEnvironmentResponse). An exec whose guest command exited non-zero lands here, not in an error response.
One of:
Environment
idstringrequired
Fleet-assigned VM identifier (prefix + task_id).
statestringrequired
Allowed:provisioningrunningdrainingdestroying
task_idstringrequired
Empty when the VM is being torn down.
host_idstring
Scheduler-assigned host ID when placement uses the host registry.
urlstringrequired
host:port address where the guest agent (fused) is reachable, via per-VM DNAT. Empty before the VM is running.
specResourceSpecrequired
Hardware requirements for a VM. No lower bound is declared on cpus/ram_mb because the server does not enforce one: createEnvironment validates only task_id and the GPU fields, so a spec with `cpus: 0` / `ram_mb: 0` is accepted and scheduled. Callers should still send positive values; a strict request validator must not reject zero, or it will refuse payloads the API accepts.
Show properties
cpusinteger<int32>
min 0
ram_mbinteger<int32>
min 0
storage_gbinteger<int32>
min 0
gpusinteger<int32>
Count of GPU devices requested. Zero means no GPU. With gpu_profile unset, this is whole devices; with gpu_profile set, this is the count of MIG instances of that profile (decision D5).
min 0
gpu_kindstring
Optional GPU model to match (e.g. "a100"). Empty matches any kind on a host that has GPUs.
gpu_profilestring
Optional MIG profile in mig-parted vocabulary (e.g. "1g.10gb"). When set, gpus counts MIG instances of this profile rather than whole devices, and the qemu host agent attaches them via vfio-pci sysfsdev (decision D5).
imagestring
Optional named base rootfs for the provider to boot from (resolved by the host agent against its named-rootfs directory). Empty means the provider's default base image.
regionstring
host_idstring
Pins the environment to an exact host id (the Fusefile's placement.host). A pin is a hard gate, not an override: the host must still be active, run the right backend, and fit the request. An unknown host id is rejected with 404 before any VM is created.
labelsobject
Placement label selectors (the Fusefile's placement.labels). Every pair must match the target host's operator-declared labels. A selector that matches no host is rejected with 503, never queued.
max_runtime_secondsinteger<int64>
Leak-detection ceiling for this task. Zero means "use the fleet default". The orchestrator tears down tasks that exceed this age with no state transitions.
idle_timeout_secondsinteger<int64>
Destroys the environment once it has gone this long with no exec and no attach session. Zero means "use the fleet default", which is no idle expiry unless the operator configured one. Measured from the last exec or attach, not from create, and must be at least 60 (idle detection runs on the reconcile loop). In-guest CPU use and traffic on exposed ports are not observed.
created_atstring<date-time>required
updated_atstring<date-time>required
errorstring
Last failure message, empty on the happy path.
endpointsEndpoint[]
Published network endpoints for ports requested via the create request's `expose` list. Omitted when none.
Show properties
Array of Endpoint
asstring
Caller-chosen label from the expose request.
urlstringrequired
Reachable address, e.g. "http://203.0.113.5:41231".
portintegerrequired
The guest-side port this endpoint publishes.
healthobject
Last verdict of the environment-level healthcheck. Omitted when the environment declared no healthcheck, and omitted until the first verdict has been read back from the guest. The orchestrator refreshes it on the reconcile tick (30s by default), so it lags the guest by up to one tick.
Show properties
statestringrequired
The probe's last verdict. Deliberately separate from `Environment.state`: an unhealthy environment is still a running one, and nothing tears it down for a failing probe.
Allowed:startingpassingfailing
sincestring<date-time>
When the probe entered this state.
failuresinteger
Consecutive failed attempts. Zero while passing.
messagestring
The last attempt's failure detail. Empty while passing.
ExecEnvironmentResponse
exit_codeintegerrequired
The guest command's own exit status. Non-zero is a normal result, delivered with HTTP 200.
stdoutstringrequired
stderrstringrequired
201Fork succeeded; body is the new Environment.
idstringrequired
Fleet-assigned VM identifier (prefix + task_id).
statestringrequired
Allowed:provisioningrunningdrainingdestroying
task_idstringrequired
Empty when the VM is being torn down.
host_idstring
Scheduler-assigned host ID when placement uses the host registry.
urlstringrequired
host:port address where the guest agent (fused) is reachable, via per-VM DNAT. Empty before the VM is running.
specResourceSpecrequired
Hardware requirements for a VM. No lower bound is declared on cpus/ram_mb because the server does not enforce one: createEnvironment validates only task_id and the GPU fields, so a spec with `cpus: 0` / `ram_mb: 0` is accepted and scheduled. Callers should still send positive values; a strict request validator must not reject zero, or it will refuse payloads the API accepts.
Show properties
cpusinteger<int32>
min 0
ram_mbinteger<int32>
min 0
storage_gbinteger<int32>
min 0
gpusinteger<int32>
Count of GPU devices requested. Zero means no GPU. With gpu_profile unset, this is whole devices; with gpu_profile set, this is the count of MIG instances of that profile (decision D5).
min 0
gpu_kindstring
Optional GPU model to match (e.g. "a100"). Empty matches any kind on a host that has GPUs.
gpu_profilestring
Optional MIG profile in mig-parted vocabulary (e.g. "1g.10gb"). When set, gpus counts MIG instances of this profile rather than whole devices, and the qemu host agent attaches them via vfio-pci sysfsdev (decision D5).
imagestring
Optional named base rootfs for the provider to boot from (resolved by the host agent against its named-rootfs directory). Empty means the provider's default base image.
regionstring
host_idstring
Pins the environment to an exact host id (the Fusefile's placement.host). A pin is a hard gate, not an override: the host must still be active, run the right backend, and fit the request. An unknown host id is rejected with 404 before any VM is created.
labelsobject
Placement label selectors (the Fusefile's placement.labels). Every pair must match the target host's operator-declared labels. A selector that matches no host is rejected with 503, never queued.
max_runtime_secondsinteger<int64>
Leak-detection ceiling for this task. Zero means "use the fleet default". The orchestrator tears down tasks that exceed this age with no state transitions.
idle_timeout_secondsinteger<int64>
Destroys the environment once it has gone this long with no exec and no attach session. Zero means "use the fleet default", which is no idle expiry unless the operator configured one. Measured from the last exec or attach, not from create, and must be at least 60 (idle detection runs on the reconcile loop). In-guest CPU use and traffic on exposed ports are not observed.
created_atstring<date-time>required
updated_atstring<date-time>required
errorstring
Last failure message, empty on the happy path.
endpointsEndpoint[]
Published network endpoints for ports requested via the create request's `expose` list. Omitted when none.
Show properties
Array of Endpoint
asstring
Caller-chosen label from the expose request.
urlstringrequired
Reachable address, e.g. "http://203.0.113.5:41231".
portintegerrequired
The guest-side port this endpoint publishes.
healthobject
Last verdict of the environment-level healthcheck. Omitted when the environment declared no healthcheck, and omitted until the first verdict has been read back from the guest. The orchestrator refreshes it on the reconcile tick (30s by default), so it lags the guest by up to one tick.
Show properties
statestringrequired
The probe's last verdict. Deliberately separate from `Environment.state`: an unhealthy environment is still a running one, and nothing tears it down for a failing probe.
Allowed:startingpassingfailing
sincestring<date-time>
When the probe entered this state.
failuresinteger
Consecutive failed attempts. Zero while passing.
messagestring
The last attempt's failure detail. Empty while passing.
204Action succeeded with no body. Always for rotate-token; for drain and fork only in the rare race where the action succeeded but the environment record could not be re-read to build a response body.
400Malformed request.
errorobjectrequired
Show properties
codestringrequired
Stable machine-readable code. `route_not_found` (404) means the URL matches no route this server exposes — usually a wrong host, port, or path prefix — as opposed to `not_found`, where the route exists but the resource does not. `forbidden` is emitted only by CIDR-allowlist rejections; master-only refusals (exec, attach, API key management) return 403 with code `unauthorized`.
Allowed:not_foundroute_not_foundconflictinvalid_argumentunauthorizedforbiddenunavailableinternalunimplemented
messagestringrequired
Human-readable description.
detailsobject
Optional stable, non-sensitive metadata (e.g. ids, counts). Omitted when empty.
403The caller authenticated, but not as the master operator. Master-only surfaces (exec, attach, API key management) refuse API keys; those 403 bodies carry code `unauthorized`. CIDR allowlist rejections also return 403 but with code `forbidden`.
errorobjectrequired
Show properties
codestringrequired
Stable machine-readable code. `route_not_found` (404) means the URL matches no route this server exposes — usually a wrong host, port, or path prefix — as opposed to `not_found`, where the route exists but the resource does not. `forbidden` is emitted only by CIDR-allowlist rejections; master-only refusals (exec, attach, API key management) return 403 with code `unauthorized`.
Allowed:not_foundroute_not_foundconflictinvalid_argumentunauthorizedforbiddenunavailableinternalunimplemented
messagestringrequired
Human-readable description.
detailsobject
Optional stable, non-sensitive metadata (e.g. ids, counts). Omitted when empty.
404Resource does not exist. Note the distinct code `route_not_found`, returned when the URL matches no registered route at all (wrong host, port, or path prefix) rather than a known route with a missing resource.
errorobjectrequired
Show properties
codestringrequired
Stable machine-readable code. `route_not_found` (404) means the URL matches no route this server exposes — usually a wrong host, port, or path prefix — as opposed to `not_found`, where the route exists but the resource does not. `forbidden` is emitted only by CIDR-allowlist rejections; master-only refusals (exec, attach, API key management) return 403 with code `unauthorized`.
Allowed:not_foundroute_not_foundconflictinvalid_argumentunauthorizedforbiddenunavailableinternalunimplemented
messagestringrequired
Human-readable description.
detailsobject
Optional stable, non-sensitive metadata (e.g. ids, counts). Omitted when empty.
409Conflicts with current state (e.g. duplicate task assignment).
errorobjectrequired
Show properties
codestringrequired
Stable machine-readable code. `route_not_found` (404) means the URL matches no route this server exposes — usually a wrong host, port, or path prefix — as opposed to `not_found`, where the route exists but the resource does not. `forbidden` is emitted only by CIDR-allowlist rejections; master-only refusals (exec, attach, API key management) return 403 with code `unauthorized`.
Allowed:not_foundroute_not_foundconflictinvalid_argumentunauthorizedforbiddenunavailableinternalunimplemented
messagestringrequired
Human-readable description.
detailsobject
Optional stable, non-sensitive metadata (e.g. ids, counts). Omitted when empty.
500Unexpected server error.
errorobjectrequired
Show properties
codestringrequired
Stable machine-readable code. `route_not_found` (404) means the URL matches no route this server exposes — usually a wrong host, port, or path prefix — as opposed to `not_found`, where the route exists but the resource does not. `forbidden` is emitted only by CIDR-allowlist rejections; master-only refusals (exec, attach, API key management) return 403 with code `unauthorized`.
Allowed:not_foundroute_not_foundconflictinvalid_argumentunauthorizedforbiddenunavailableinternalunimplemented
messagestringrequired
Human-readable description.
detailsobject
Optional stable, non-sensitive metadata (e.g. ids, counts). Omitted when empty.
501The operation is not supported by the backing provider (e.g. an exec or attach against an environment with no real guest).
errorobjectrequired
Show properties
codestringrequired
Stable machine-readable code. `route_not_found` (404) means the URL matches no route this server exposes — usually a wrong host, port, or path prefix — as opposed to `not_found`, where the route exists but the resource does not. `forbidden` is emitted only by CIDR-allowlist rejections; master-only refusals (exec, attach, API key management) return 403 with code `unauthorized`.
Allowed:not_foundroute_not_foundconflictinvalid_argumentunauthorizedforbiddenunavailableinternalunimplemented
messagestringrequired
Human-readable description.
detailsobject
Optional stable, non-sensitive metadata (e.g. ids, counts). Omitted when empty.
Request
curl -X POST "http://localhost:8080/v1/environments/string?action=rotate-token" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "cmd": [
    "ls",
    "-l"
  ],
  "timeout_ms": 5000
}'
Response
{
  "id": "string",
  "state": "provisioning",
  "task_id": "string",
  "host_id": "string",
  "url": "string",
  "spec": {
    "cpus": 0,
    "ram_mb": 0,
    "storage_gb": 0,
    "gpus": 0,
    "gpu_kind": "string",
    "gpu_profile": "string",
    "image": "string",
    "region": "string",
    "host_id": "string",
    "labels": {},
    "max_runtime_seconds": 0,
    "idle_timeout_seconds": 0
  },
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-01T00:00:00Z",
  "error": "string",
  "endpoints": [
    {
      "as": "string",
      "url": "string",
      "port": 0
    }
  ],
  "health": {
    "state": "starting",
    "since": "2024-01-01T00:00:00Z",
    "failures": 0,
    "message": "string"
  }
}