API Reference

The live OpenAPI specification for the FastVM public API.

API Endpoint: https://api.fastvm.org
OpenAPI YAML Specification: https://fastvm.org/openapi.decorated.yaml

Plain-text endpoint reference

Plain HTML mirror of the API reference, generated at build time from api/openapi.yaml. Same content as the interactive view above; included for screen readers, search-engine indexers, and LLM-based crawlers that can’t render the JavaScript surface. See also /llms.txt and /llms-full.txt.

VMs

VM lifecycle

GET /v1/vms

List VMs

Lists all non-deleted VMs for the authenticated org. Supports metadata-equality filtering; callers pass repeated query parameters of the form `metadata.<key>=<value>` (e.g. `metadata.env=prod&metadata.role=api`). The optional `status` query filter narrows by lifecycle status (e.g. `?status=paused`).

Auth required (X-API-Key).

Parameters

  • status (query, VMStatus) Restrict to VMs with this status. Accepts any value of `VMStatus`; unknown values return an empty list.

Responses

  • 200 VM[] List of VMs
  • 401 Error Missing or invalid credentials
  • 500 Error Internal server error

POST /v1/vms

Launch a VM

Creates a new VM, either from a machineType (fresh boot) or a snapshotId (restore from snapshot). - Returns **201** when the VM is already running in the response. - Returns **202** when the VM is queued; clients must poll `GET /v1/vms/{id}` until status transitions to `running`. Terminal failure statuses are `error` and `stopped`. The SDK's `launch()` helper handles the 201/202 branching and polling automatically.

Auth required (X-API-Key).

Request body: CreateVMRequest

Responses

  • 201 VMCreateResponse VM is already running. The response is a VM object, with two optional warning fields surfacing non-fatal failures: - `snapshotRestoreWarnings`: pre-registered services from the snapshot failed to land on the new VM. The VM itself is good; the user can re-register the listed services manually. - `attachmentWarnings`: one or more inline `volumes` / `bucketMounts` entries failed to attach during the synchronous create handshake. The VM is **not rolled back** — it boots without the failed mounts. The `failedVolumeAttachments` / `failedBucketMountAttachments` arrays carry the per-attachment `statusMessage`; callers that require all-or-nothing semantics must inspect the warning field and decide whether to delete the VM and retry, or call `POST /v1/vms/{id}/{volumes,bucket-mounts}` to retry the failed entries individually.
  • 202 VM VM is queued; poll for readiness
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 403 Error Org quota exceeded
  • 404 Error Snapshot or base image not found
  • 500 Error Internal server error
  • 502 Error Upstream service error
  • 503 Error Service temporarily unavailable

GET /v1/vms/{id}

Get a VM

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Responses

  • 200 VM VM
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 500 Error Internal server error

PATCH /v1/vms/{id}

Update a VM

Renames a VM and/or replaces its metadata map. At least one of `name` or `metadata` must be provided. Sending `metadata: {}` clears all metadata; omitting `metadata` leaves it unchanged.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Request body: UpdateVMRequest

Responses

  • 200 VM Updated VM
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 500 Error Internal server error

DELETE /v1/vms/{id}

Delete a VM

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Responses

  • 200 DeleteResponse VM deletion result
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 500 Error Internal server error
  • 502 Error Upstream service error

POST /v1/vms/{id}/pause

Pause a VM

Captures the VM state, frees the worker and all customer-facing quotas, and transitions the VM to `paused`. Idempotent on already-paused VMs (returns 200 with the current state). Synchronous; ~3 s end-to-end.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Responses

  • 200 VM Paused VM (or already paused — idempotent).
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error VM in a state that cannot be paused.
  • 500 Error Internal server error
  • 502 Error Upstream service error

POST /v1/vms/{id}/resume

Resume a paused VM

Restores the VM's prior state, re-acquires quota, and transitions to `running`. Sync-when-fast / async-when-queued: returns 200 if the VM is running inline, or 202 if queued for cluster capacity. Idempotent on already-running.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Responses

  • 200 VM VM running (resumed inline).
  • 202 VM Resume queued for capacity. VM is in `resuming`; poll `GET /v1/vms/{id}` or wait for the `vm.resumed` webhook.
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error VM not in a state that can be resumed.
  • 429 QuotaExceeded Org quota exceeded; body indicates the dimension.
  • 500 Error Internal server error
  • 502 Error Upstream service error

POST /v1/vms/{id}/ttl/refresh

Reset the VM's TTL cycle

Resets the TTL countdown to a fresh `seconds` budget. From `running`, the deadline moves to `now + seconds*1000`. From `paused`, the remaining-budget is reset to `seconds*1000` and takes effect on next resume. 409 if no TTL is configured.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Responses

  • 200 VM Updated VM with reset TTL cycle.
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error VM has no TTL configured, OR is in a transitional state.
  • 500 Error Internal server error

Snapshots

Snapshot lifecycle

GET /v1/snapshots

List snapshots

Lists all snapshots for the authenticated org. Supports metadata-equality filtering; callers pass repeated query parameters of the form `metadata.<key>=<value>` (e.g. `metadata.env=prod&metadata.role=api`).

Auth required (X-API-Key).

Responses

  • 200 Snapshot[] List of snapshots
  • 401 Error Missing or invalid credentials
  • 500 Error Internal server error

POST /v1/snapshots

Create a snapshot from a VM

Captures a VM's state into a customer-visible snapshot. Supported on `running` and `paused` VMs; returns 201 Created with the new snapshot in both cases. On a paused VM, repeated calls within the same pause cycle are idempotent: the second call returns the same snapshot record without modification.

Auth required (X-API-Key).

Request body: CreateSnapshotRequest

Responses

  • 201 Snapshot Snapshot created
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 403 Error Snapshot quota exceeded
  • 404 Error Source VM not found
  • 409 Error Source VM is in a non-snapshottable state (provisioning, pausing, resuming, error, deleting), or the paused VM already has a snapshot with a different name.
  • 500 Error Internal server error
  • 502 Error Upstream service error

GET /v1/snapshots/{id}

Get a snapshot

Returns the full Snapshot record for the given ID, scoped to the authenticated org. Used by the SDK's `build()` flow to fetch the completed snapshot after polling reports `completed`.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) Snapshot ID (UUID).

Responses

  • 200 Snapshot Snapshot record
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 500 Error Internal server error

PATCH /v1/snapshots/{id}

Rename a snapshot

Auth required (X-API-Key).

Parameters

  • id (path, string, required) Snapshot ID (UUID).

Request body: UpdateSnapshotRequest

Responses

  • 200 Snapshot Updated snapshot
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 500 Error Internal server error

DELETE /v1/snapshots/{id}

Delete a snapshot

Auth required (X-API-Key).

Parameters

  • id (path, string, required) Snapshot ID (UUID).

Responses

  • 200 DeleteResponse Snapshot deletion result
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 500 Error Internal server error
  • 502 Error Upstream service error

Firewall

VM firewall policy

PUT /v1/vms/{id}/firewall

Replace firewall policy

Replaces the full firewall policy on a VM.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Request body: FirewallPolicy

Responses

  • 200 VM Updated VM
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 500 Error Internal server error
  • 502 Error Upstream service error

PATCH /v1/vms/{id}/firewall

Patch firewall policy

Updates one or more blocks of the firewall policy. Each top-level block (`ingress`, `egress`, `dns`) is optional; when present, the supplied object **replaces that block wholesale**. Per-rule diffing is not supported — to change a single rule, send the full block with the desired rule list. An empty body (`{}`) is a no-op. Examples: - `{"ingress": {"default": "deny", "rules": []}}` clears all ingress rules and sets the default action. - `{"dns": {"mode": "allow", "domains": ["api.example.com"], "blockBypass": true}}` updates only the DNS block.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Request body: PatchFirewallRequest

Responses

  • 200 VM Updated VM
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 500 Error Internal server error
  • 502 Error Upstream service error

Exec

In-VM command execution

POST /v1/vms/{id}/exec

Execute a command inside a VM

Runs `command` inside the VM. Response shape is determined by the client's `Accept` header: - **`Accept: application/json`** (default, omitted, or `*/*`): buffered `ExecVMResponse` — the server collects all output and returns a single JSON object once the command exits. Per-stream output is capped at 4 MiB; overflow bytes are dropped and signalled via `stdoutTruncated` / `stderrTruncated`. - **`Accept: application/x-ndjson`**: newline-delimited stream of `ExecEvent`s — zero or more `stdout`/`stderr` chunks followed by exactly one terminal `exit` event. Use this for incremental output (long builds, test runners, live logs). No server-side cap. Both modes share the same request body. `timeoutSec` bounds server-side execution; clients should set their own HTTP timeout in addition. 502 responses are transient (the upstream VM host is unreachable or returned an error). The SDK's `run()` helper does NOT auto-retry these by default: exec is **not idempotent**, so if a 502 hides a successful exec a retry may run the command twice. Callers opt in with `max_retries=N` per call.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Request body: ExecVMRequest

Responses

  • 200 ExecVMResponse Command completed. `application/json` (default) returns a single `ExecVMResponse`; `application/x-ndjson` returns an event stream terminated by one `exit` event.
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error VM is not running
  • 500 Error Internal server error
  • 502 Error Upstream VM host unreachable or returned an error. Not retried by default (non-idempotent).

Console

Interactive serial console access

POST /v1/vms/{id}/console-token

Mint a console token

Returns a short-lived token and WebSocket path. Open a WebSocket to `wss://<host><websocketPath>?session=<token>` to attach to the VM's serial console. The WebSocket endpoint itself is intentionally not modeled in this spec because it uses a capability-URL flow (no API key on upgrade) and a custom binary/text protocol. See `src/fastvm/lib/console.py` in the Python SDK for a reference client.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Responses

  • 200 ConsoleTokenResponse Console token
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error VM is not running
  • 500 Error Internal server error

ssh

Per-user authorized SSH key management. Register one pubkey on your user, then `ssh <vmId>@<org-slug>.ssh.<stack-domain>` works for every VM in every org you're a member of. The per-stack SSH gateway uses CA-signed user certificates internally — VMs trust the CA, not your raw pubkey, so the customer-facing pubkey lives only at the gateway.

GET /v1/me/ssh-keys

List the calling user's authorized SSH keys

Returns every SSH public key registered to the calling user. Keys are personal: registering a key once authorizes `ssh <vmId>@ssh.<domain>` for any VM in any org you are a member of. SSH terminates at the per-stack gateway and is forwarded to the VM over the cluster network; the VM does not need to be publicly IPv4-reachable, and you do not need to know the VM's IPv6 address.

Auth required (X-API-Key).

Responses

  • 200 SshKeyListResponse Authorized keys list
  • 401 Error Missing or invalid credentials
  • 500 Error Internal server error

POST /v1/me/ssh-keys

Register an SSH public key for the calling user

Adds one authorized SSH public key to the calling user. The fingerprint is derived server-side and returned. Duplicate fingerprints return 409. Up to 32 keys per user. After this call, `ssh <vmId>@ssh.<domain>` works for any VM you have access to. Each fingerprint is globally unique: registering a public key that another user already has on file returns 409.

Auth required (X-API-Key).

Request body: AddSshKeyRequest

Responses

  • 201 SshKey Key registered
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 409 Error Key with this fingerprint is already registered (by this user or another user)
  • 500 Error Internal server error

DELETE /v1/me/ssh-keys/{fingerprint}

Remove an authorized SSH key

Deletes one of the calling user's keys by fingerprint. Existing SSH sessions are NOT terminated — the key simply won't authorize new connections after removal.

Auth required (X-API-Key).

Parameters

  • fingerprint (path, string, required) OpenSSH SHA256 fingerprint of the key to delete (e.g. `SHA256:abc...`). The base64 hash includes `+` and `/` and the prefix has `:`, so callers MUST URL-encode the value into the path segment. SDKs do this automatically.

Responses

  • 200 DeleteResponse Key removed
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 500 Error Internal server error

Files

File upload/download to/from a running VM

POST /v1/vms/{id}/files/presign

Mint signed URLs for uploading a file to a VM

Returns a pair of short-lived signed URLs targeting a per-VM staging location. Upload to `uploadUrl` with PUT (`Content-Type: application/octet-stream`), then pass `downloadUrl` to `POST /v1/vms/{id}/files/fetch` to have the server pull it into the guest filesystem.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Request body: FilePresignRequest

Responses

  • 200 FilePresignResponse Signed URLs + upload size ceiling
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error VM is not running
  • 500 Error Internal server error
  • 501 Error File transfer not configured on this deployment

POST /v1/vms/{id}/files/fetch

Fetch a file into a VM from a presigned URL

Pulls `url` into the guest at `path`. `url` must be a presigned storage URL previously minted by `POST /v1/vms/{id}/files/presign` (URLs from other sources are rejected). Response mirrors `/v1/vms/{id}/exec`: reports stdout/stderr/exit code of the underlying download+unpack operation. Not idempotent; not retried by default.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Request body: FileFetchRequest

Responses

  • 200 ExecVMResponse Fetch completed (command result)
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error VM is not running
  • 413 Error Object too large for VM disk / insufficient guest disk space
  • 500 Error Internal server error
  • 502 Error Failed to HEAD the presigned URL

Quotas

Org quotas and usage

GET /v1/org/quotas

Get org quotas and usage

Auth required (X-API-Key).

Responses

  • 200 OrgQuotaUsage Quota limits and current usage
  • 401 Error Missing or invalid credentials
  • 500 Error Internal server error

Snapshot Imports

Build snapshots from a Docker / OCI image reference or a client-uploaded Dockerfile + build context

GET /v1/snapshot-imports

List the calling org's snapshot imports

Returns every import for the calling org, ordered by `createdAt` descending. Includes pending, in-flight, and terminal rows.

Auth required (X-API-Key).

Responses

  • 200 SnapshotImportResponse[] Array of snapshot imports
  • 401 Error Missing or invalid credentials
  • 500 Error Internal server error

POST /v1/snapshot-imports

Build a snapshot from a Docker / OCI image or a Dockerfile

Submits an asynchronous import. `source.type` selects the pipeline: - **`image`** — pull `source.image` (any Docker / OCI ref) and export its rootfs onto the snapshot. Private registries supported via optional `source.registryUsername` / `source.registryPassword`; the registry host is derived from the image reference. - **`dockerfile`** — build the user-supplied Dockerfile + its uploaded context. The context tarball must be uploaded first to the `uploadUrl` returned by `POST /v1/snapshot-imports/context-presign`; pass the returned `contextRef` as `source.contextRef`. Private `FROM` pulls supported via `source.registryUsername` / `source.registryPassword` plus `source.registryHost` (required when credentials are set on this path). Response is `202 Accepted` with an import id; poll `GET /v1/snapshot-imports/{id}` until `status` is one of `succeeded`, `failed`, or `cancelled`.

Auth required (X-API-Key).

Request body: CreateSnapshotImportRequest

Responses

  • 202 SnapshotImportResponse Import accepted; poll `GET /v1/snapshot-imports/{id}` for status
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 403 Error Org quota exceeded
  • 500 Error Internal server error
  • 501 Error Snapshot import is not enabled on this cluster (no sandbox base image configured). The image-source path needs a sandbox base; the dockerfile-source path additionally needs `FILE_STAGING_BUCKET` configured for context uploads.
  • 503 Error Sandbox base image is configured but not currently available on this stack. Retry after the platform operator publishes a sandbox-base snapshot.

POST /v1/snapshot-imports/context-presign

Mint a signed URL for uploading a Dockerfile build-context archive

Returns a short-lived signed PUT URL and a one-shot `contextRef`. Zip your Dockerfile + build context, PUT the archive to `uploadUrl` with `Content-Type: application/zip`, then submit a snapshot import with `source.type=dockerfile` and `source.contextRef=<ref>`. The ref is consumed by the create call and cannot be reused.

Auth required (X-API-Key).

Request body: ContextPresignRequest

Responses

  • 200 ContextPresignResponse Signed upload URL + opaque context reference
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 500 Error Internal server error
  • 501 Error Build-context uploads are not configured on this stack (no `FILE_STAGING_BUCKET`) or snapshot import itself is disabled. Image-source imports still work in this case.

GET /v1/snapshot-imports/{id}

Get a snapshot import's state

Returns the current state of an import including its event log. `status` is one of `pending`, `claimed`, `running`, `succeeded`, `failed`, or `cancelled`. On `succeeded`, `snapshotId` references a `ready` snapshot — fetch it via `GET /v1/snapshots/{id}`. On `failed`, `error` carries a user-safe diagnostic.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) Snapshot import ID (UUID).

Responses

  • 200 SnapshotImportResponse Snapshot import state
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 500 Error Internal server error

DELETE /v1/snapshot-imports/{id}

Delete a terminal snapshot import (cascades to its snapshot)

Removes the import record and, if the import produced a snapshot, the snapshot itself. Cascading the snapshot delete is safe: snapshots are content-addressed in the underlying block store, and any VMs already booted from the snapshot keep running (the delete only removes the pointer used by future `client.restore()` calls). Refuses non-terminal imports with `409 Conflict`; cancel the import first via `POST /v1/snapshot-imports/{id}/cancel`.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) Snapshot import ID (UUID).

Responses

  • 200 DeleteResponse Deletion result
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error Import is still in flight; cancel it first
  • 500 Error Internal server error

POST /v1/snapshot-imports/{id}/cancel

Cancel an in-flight snapshot import

Transitions the import to `cancelled` and best-effort signals the worker to stop the pipeline. Idempotent: re-cancelling a terminal import returns `200` with the current state. Any orphan snapshot produced just before the cancel is best-effort cleaned up.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) Snapshot import ID (UUID).

Responses

  • 200 SnapshotImportResponse Cancellation result (idempotent)
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 500 Error Internal server error

VM Services

Per-VM service registrations exposed via the public 4to6 HTTP proxy

GET /v1/vms/{id}/services

List service registrations

Returns the services currently registered on this VM, sorted by name. Each service is exposed at `https://<name>--<vmIdHexNoHyphens>.proxy.<stack-domain>` over HTTPS.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Responses

  • 200 Service[] Services
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 500 Error Internal server error

POST /v1/vms/{id}/services

Register a service on a VM

Registers an HTTP service on the VM under `name`, listening on `port`. The service immediately becomes addressable at `https://<name>--<vmIdHexNoHyphens>.proxy.<stack-domain>` once the firewall is applied (synchronous). Idempotent: a POST with a name that already exists at the same `(port, h2c)` returns 201 with the existing entry. POST with a name that already exists at a different port OR different `h2c` returns 409 — use PUT to update an existing service. Per-VM cap: currently 16 services per VM (configurable via `MAX_SERVICES_PER_VM` on the scheduler).

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Request body: RegisterServiceRequest

Responses

  • 201 Service Service registered (or idempotent same-`(port, h2c)` re-register)
  • 400 any Invalid name, invalid port, or per-VM cap exceeded. The body is a `QuotaExceededError` for the cap case (carries the structured `vm_service_quota_exceeded` reason + numeric count) and an `Error` otherwise.
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error The service name is already registered at a different port or different `h2c` (use PUT to update), or the VM is in `error` state and cannot be modified.
  • 500 Error Internal server error

PUT /v1/vms/{id}/services/{serviceName}

Register or update a service on a VM

Idempotent register-or-update: same name + new port updates the port; same name + same port is a no-op. Returns the resulting entry. Used to change the upstream port for an existing service registration without dropping and re-creating it.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).
  • serviceName (path, string, required) Service registration name. 1–29 chars, lowercase letters and digits with optional single internal hyphens (no leading, trailing, or consecutive hyphens). Embedded in the public URL as the leftmost label.

Request body: UpdateServiceRequest

Responses

  • 200 Service Service updated (or no-op same-port re-issue)
  • 400 any Invalid name or port, or per-VM cap exceeded. The body is a `QuotaExceededError` for the cap case and an `Error` otherwise.
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error VM is in `error` state and cannot be modified
  • 500 Error Internal server error

DELETE /v1/vms/{id}/services/{serviceName}

Deregister a service from a VM

Idempotent: deleting a service that doesn't exist returns 204. Removes the firewall auto-rule synchronously; the proxy stops routing to the service within seconds (cache invalidation broadcast; 30s TTL is the safety net).

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).
  • serviceName (path, string, required) Service registration name. 1–29 chars, lowercase letters and digits with optional single internal hyphens (no leading, trailing, or consecutive hyphens). Embedded in the public URL as the leftmost label.

Responses

  • 204 Service deregistered (or already absent)
  • 400 Error Invalid service name
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error VM is in `error` state and cannot be modified
  • 500 Error Internal server error

Health

Service health

GET /healthz

Health check

Returns 200 when the API is reachable. SDK clients call this on startup to warm HTTP/2 connections before the first real request.

Responses

  • 200 object Service is healthy
  • 500 Error Internal server error

Volumes

Managed shared-volume lifecycle (POSIX-coherent multi-attach via virtio-fs).

GET /v1/volumes

List volumes

Auth required (X-API-Key).

Responses

  • 200 Volume[] List of volumes
  • 401 Error Missing or invalid credentials
  • 500 Error Internal server error

POST /v1/volumes

Create a managed volume

Auth required (X-API-Key).

Request body: CreateVolumeRequest

Responses

  • 201 Volume Volume created
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 403 Error Org quota exceeded
  • 409 Error Name collision
  • 500 Error Internal server error
  • 502 Error Upstream service error
  • 503 Error Service temporarily unavailable

GET /v1/volumes/{id}

Get a volume

Auth required (X-API-Key).

Parameters

  • id (path, string, required) Volume identifier (e.g. `vol_<22-char-lowercase-hex>`).

Responses

  • 200 Volume Volume
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found

PATCH /v1/volumes/{id}

Update a volume's name, sizeGiB (grow / shrink-if-not-overfull), or accessMode

Auth required (X-API-Key).

Parameters

  • id (path, string, required) Volume identifier (e.g. `vol_<22-char-lowercase-hex>`).

Request body: UpdateVolumeRequest

Responses

  • 200 Volume Updated volume
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 403 Error Org quota exceeded (grow above limit)
  • 404 Error Resource not found
  • 409 Error Conflict (volume_overfull_for_shrink, volume_in_use_by_attached_vms)
  • 502 Error Upstream service error

DELETE /v1/volumes/{id}

Delete a volume

Returns 200 when the volume transitions to `deleting`. Substrate cleanup is asynchronous; the volume disappears from `GET` after the substrate-cleanup controller completes (typically seconds to minutes for large volumes).

Auth required (X-API-Key).

Parameters

  • id (path, string, required) Volume identifier (e.g. `vol_<22-char-lowercase-hex>`).

Responses

  • 200 DeleteResponse Volume marked for deletion
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 object Volume currently mounted on running VMs
  • 500 Error Internal server error

GET /v1/volumes/{id}/attachments

List VMs currently attached to this volume

Auth required (X-API-Key).

Parameters

  • id (path, string, required) Volume identifier (e.g. `vol_<22-char-lowercase-hex>`).

Responses

  • 200 VolumeAttachmentItemWithVm[] List of attachments
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found

POST /v1/vms/{id}/volumes

Attach a volume to a VM

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Request body: VolumeAttachmentRequest

Responses

  • 201 VolumeAttachmentItem Volume attached
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error VM in invalid state, VM in transitional state (`error: vm_transitioning`; retry-able with `Retry-After`), volume not ready, mount-path collision, duplicate-volume, or already-attached. The body's `error` token distinguishes.
  • 502 Error Upstream service error
  • 503 Error Service temporarily unavailable

DELETE /v1/vms/{id}/volumes/{volumeId}

Detach a volume from a VM

Returns 200 with `{detached: true}` on the clean path. May include a `warnings` array on the force-teardown path (eject-ack timeout or guest-unresponsive). Returns 502 with `error: guest_umount_busy` when the guest reports EBUSY; the volume STAYS ATTACHED in this case. Resolve by killing in-VM users of the mount and retrying.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).
  • volumeId (path, string, required)

Responses

  • 200 DetachVolumeResponse Detached (with optional warnings)
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error VM in transitional state (`error: vm_transitioning`; honor `Retry-After`) or terminal state (`error: vm_invalid_state`).
  • 502 Error Upstream service error
  • 503 Error Service temporarily unavailable

Bucket Mounts

BYO GCS/S3 bucket mounts as a VM sub-resource.

GET /v1/vms/{id}/bucket-mounts

List bucket-mounts on a VM

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Responses

  • 200 BucketMount[] List
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found

POST /v1/vms/{id}/bucket-mounts

Attach a customer GCS / S3 bucket to a VM

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).

Request body: CreateBucketMountRequest

Responses

  • 201 BucketMount BucketMount created and mounted
  • 400 Error Validation failure (bad URI, malformed credentials, bucket-not-found)
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error Path collision, VM invalid state, VM in transitional state (`error: vm_transitioning`; retry-able with `Retry-After`), or per-VM cap exceeded. The body's `error` token distinguishes.
  • 502 Error Upstream service error
  • 503 Error Service temporarily unavailable

GET /v1/vms/{id}/bucket-mounts/{bucketMountId}

Get a bucket-mount

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).
  • bucketMountId (path, string, required) BucketMount identifier (e.g. `bm_<22-char-lowercase-hex>`), unique per VM.

Responses

  • 200 BucketMount BucketMount
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found

PATCH /v1/vms/{id}/bucket-mounts/{bucketMountId}

Rotate bucket-mount credentials in-place

Replaces the stored credentials and re-authenticates the FUSE mount on the worker. Brief I/O blip (~50-200 ms typical) during the swap. Returns 502 on the rollback path; flips `mountStatus` to `failed` on full failure.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).
  • bucketMountId (path, string, required) BucketMount identifier (e.g. `bm_<22-char-lowercase-hex>`), unique per VM.

Request body: UpdateBucketMountRequest

Responses

  • 200 BucketMount Rotation succeeded
  • 400 Error Credentials invalid
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error Concurrent rotation in flight (`error: concurrent_rotation`). `Retry-After` indicates safe wait.
  • 502 Error Rotation rolled back or failed
  • 503 Error Worker/Redis split-brain (`error: worker_state_inconsistent`): the worker has no record of this BM with no rotation in flight (e.g., post worker-restart). The controller reaper reconciles at lease expiry; clients should NOT retry.

DELETE /v1/vms/{id}/bucket-mounts/{bucketMountId}

Detach and delete a bucket-mount

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).
  • bucketMountId (path, string, required) BucketMount identifier (e.g. `bm_<22-char-lowercase-hex>`), unique per VM.

Responses

  • 204 Detached and deleted
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error Conflict. Either a concurrent rotation owns the worker-side slot (`error: concurrent_rotation`) or the VM is in a transitional state (`error: vm_transitioning`). Both are retry-able; honor `Retry-After`.
  • 500 Error Internal server error
  • 502 Error Guest reported EBUSY on umount. The BucketMount STAYS attached.
  • 503 Error Worker unreachable, OR worker/Redis split-brain (`error: worker_state_inconsistent`). In the split-brain case, the controller reaper reconciles at lease expiry; clients should NOT retry.

Schemas

Error

  • error (string, required) Human-readable error message.

QuotaExceededError

Per-VM service quota exceeded. The `error` token is a stable machine-readable code so SDKs can branch on it; `count` is the configured cap at denial time.

  • error ("vm_service_quota_exceeded", required)
  • count (integer, required)

DeleteResponse

  • id (string, required)
  • deleted (boolean, required)

VMStatus

Lifecycle status. Known values: `provisioning`, `running`, `stopped`, `pausing`, `paused`, `resuming`, `deleting`, `error`. Terminal failure statuses are `error` and `stopped`; transitional values (`provisioning`, `pausing`, `resuming`, `deleting`) indicate the VM is in flight. Additional values may be introduced in future server versions; clients should treat unknown values as "in transition" rather than as hard errors.

SnapshotStatus

Snapshot lifecycle status. Known values: `creating`, `ready`, `error`. Additional values may be introduced in future server versions.

TTL

Per-VM auto-action timer. The cycle ticks down while the VM is `running` and freezes on pause. `seconds` is the original cycle duration; refresh and PATCH-time updates reset to this value.

  • seconds (integer, required) Cycle duration. Refresh resets to this value. Capped at 1 year (31536000s); larger values are rejected with 400.
  • action ("pause" | "delete", required) Action taken on expiry. `pause` re-arms the cycle for the next running session; `delete` is terminal.

QuotaExceeded

429 body returned by `/v1/vms/{id}/resume` when the org's quota for one of the listed dimensions would be exceeded.

  • error (string, required)
  • dimension ("vcpu" | "memory_mib" | "disk_gib" | "snapshot_count", required)

MachineType

Machine size identifier (e.g. `c1m2`, `c2m4`). Controls CPU and memory allocation. Must be supplied on launch unless restoring from a snapshot.

VM

  • id (string, required)
  • name (string, required)
  • orgId (string, required)
  • machineName (string)
  • sourceName (string) Source snapshot or image name (empty on fresh boot).
  • firewall (FirewallPolicy)
  • effectiveFirewall (any) Read-only composed view: `firewall` (the user policy) unioned with per-service auto-rules from this VM's registered services. Each auto-rule has source CIDR `::/0` and a `description` of the form `auto: proxy service <name>`. The same policy is what the worker firewall actually enforces. Set `firewall` to mutate; this field is computed per-response from `firewall` and the current service registry, never persisted.
  • metadata (Metadata)
  • envVars (EnvVars)
  • publicIpv6 (string)
  • cpu (integer, required)
  • memoryMiB (integer, required)
  • diskGiB (integer, required)
  • status (VMStatus, required)
  • createdAt (string, required)
  • deletedAt (string)
  • ttl (any) Optional auto-action timer. Null when no TTL is configured. See `TTL` for semantics.
  • expiresAtMs (integer) Absolute timestamp in ms when the TTL fires. Set only while the VM is `running` (the countdown freezes on pause).
  • ttlRemainingMs (integer) Remaining cycle budget in ms. Set only while the VM is paused; restored to `expiresAtMs` on resume.
  • pausedAt (string) When the VM became paused; null otherwise.
  • volumes (VolumeAttachmentItem[]) Currently-attached volumes on this VM.
  • bucketMounts (BucketMount[]) Currently-attached bucket-mounts on this VM.

Snapshot

  • id (string, required)
  • name (string, required)
  • orgId (string, required)
  • vmId (string, required)
  • firewall (FirewallPolicy)
  • metadata (Metadata)
  • envVars (EnvVars)
  • services (SnapshotService[]) Captured service registrations from the source VM at snapshot time.
  • volumes (SnapshotVolumeAttachment[]) Volume attachments captured at snapshot time.
  • bucketMounts (SnapshotBucketMountAttachment[]) BucketMount metadata captured at snapshot time (no credentials).
  • status (SnapshotStatus, required)
  • createdAt (string, required)

PolicyAction

Allow/deny verb. Used both as the per-direction default posture and as each rule's action.

IngressRuleKind

Ingress rule kind. Only `cidr` is supported — inbound packets don't carry a domain the worker could match on without TLS interception.

EgressRuleKind

Egress rule kind. - `cidr`: match by destination IP/CIDR + port/proto. - `fqdn`: match by destination domain (resolved through the in-process DNS resolver) + port/proto. Resolved IPs land in a per-rule dynamic nft set; the chain emits one rule per fqdn rule keyed on (set, proto, port). Port/proto enforcement on fqdn rules is honest — the prior `kind: domain` shape with a shared allow-set silently ignored them. Fqdn values accept an optional leading `*.` wildcard (e.g. `*.example.com`). Bare wildcards and non-leading wildcards are rejected. Wildcards match one-or-more labels left of the suffix and do not match the apex (matches DNS wildcard semantics).

DNSMode

Toggles the meaning of `dns.domains`. - `allow`: allowlist — only listed domains can resolve; any other query returns NXDOMAIN. - `deny`: blocklist — listed domains return NXDOMAIN; all other queries resolve through the upstream resolver. Default is `deny` with an empty list, which means "resolve everything" — the safe default that preserves existing behavior when callers omit the `dns` block.

IngressRule

  • action (PolicyAction, required)
  • kind (IngressRuleKind, required)
  • value (string, required) CIDR (e.g. `::/0`, `10.0.0.0/8`). IPv4 and IPv6 CIDRs are both accepted in the schema; L3 enforcement coverage per family is a worker-side concern.
  • protocol ("tcp" | "udp" | "any", required)
  • ports (string, required) Single port (`443`), inclusive range (`8080-8090`), or `any`. When `protocol` is `any`, `ports` MUST be `any`.
  • description (string)

IngressPolicy

  • default (PolicyAction, required)
  • rules (IngressRule[])

EgressRule

  • action (PolicyAction, required)
  • kind (EgressRuleKind, required)
  • value (string, required) For `kind: cidr`, an IPv4 or IPv6 CIDR. For `kind: fqdn`, a domain name with optional leading `*.` wildcard. Must be reachable through the `dns` gate — a fqdn value blocked by `dns.mode`/`dns.domains` is rejected at PUT time as a dead rule.
  • protocol ("tcp" | "udp" | "any", required)
  • ports (string, required) Single port (`443`), inclusive range (`8080-8090`), or `any`. When `protocol` is `any`, `ports` MUST be `any`.
  • description (string)

EgressPolicy

  • default (PolicyAction, required)
  • rules (EgressRule[])

DNSPolicy

DNS-layer filtering, independent of egress L4 rules. The resolver applies the DNS gate BEFORE L4 enforcement; a domain blocked here returns NXDOMAIN regardless of what egress.rules says about its IPs. All fields are optional — the server defaults `mode` to `deny` when missing, `domains` to `[]`, and `blockBypass` to false (see `normalizeDNSPolicy` in `scheduler/internal/httpapi/firewall.go`).

  • mode (DNSMode)
  • domains (string[])
  • blockBypass (boolean) When true, the worker denies DoT (TCP 853) and the known public DoH endpoint IPs at the nft layer so guests cannot sidestep the in-process resolver. Default `false` — turning this on breaks workloads that legitimately reach `1.1.1.1` / `8.8.8.8` / etc. on TCP/443 for non-DoH reasons (e.g. services whose data plane lives on a Cloudflare anycast IP). Operators who enable DNS allowlist mode typically also flip this on explicitly.

FirewallPolicy

Top-level firewall policy with three independent axes. All sub-blocks are optional — the server substitutes the safe default (ingress deny / egress allow / dns mode=deny + empty) for missing blocks. Sending `firewall: null` on VM create is also valid.

  • ingress (IngressPolicy)
  • egress (EgressPolicy)
  • dns (DNSPolicy)

PatchFirewallRequest

Partial firewall update. Each block (`ingress`, `egress`, `dns`) is optional; when present, the supplied object replaces that block wholesale. To change a single rule, send the full block with the desired rule list. An empty body (`{}`) is a no-op.

  • ingress (IngressPolicy)
  • egress (EgressPolicy)
  • dns (DNSPolicy)

SnapshotService

Captured (name, port, h2c) tuple for a single service registration on a snapshotted VM. Carried across snapshot/ restore by `POST /v1/vms` (snapshot-restore branch) so the new VM gets the same service registrations the source VM had at snapshot time.

  • name (string, required)
  • port (integer, required)
  • h2c (boolean)

SnapshotRestoreWarnings

Reports best-effort failures during the snapshot-restore service-replay step. Only present when restoring from a snapshot AND the post-create bulk service registration failed. The VM is created successfully and usable; the user can manually re-register the listed services with one `POST /v1/vms/{id}/services` per service. Bulk service registration is atomic at Redis (one Lua call either writes all-N entries or zero), so partial state ("5 of 8 registered") is impossible — the response is always either a VM with all services registered or a VM with zero services and the full list returned here.

  • servicesRegistrationFailed (boolean, required) Always `true` when this object is present.
  • unregisteredServices (SnapshotService[]) Services from the snapshot that did not land on the new VM. Caller can re-register each via `POST /v1/vms/{id}/services`.
  • reason (string) Operator-facing diagnostic for the failure.

VMCreateResponse

VM object as returned by `POST /v1/vms`. On snapshot restore, an optional `snapshotRestoreWarnings` field may be present if the captured services failed to re-register on the new VM. Existing SDK callers that don't know about the field see the unchanged VM wire shape (`omitempty` keeps the field absent on cold boots and on warning-free restores).

CreateVMRequest

Boot behavior depends on which fields are set: - `snapshotId` set → restore from snapshot (takes precedence over `machineType` if both are sent). - Otherwise → fresh boot. `machineType` selects the size; if omitted or empty, defaults to `c1m2`.

  • name (string) User-facing name (trimmed + whitespace-collapsed, max 64 runes after normalization; longer values are truncated server-side). Auto-generated as `vm-<8-char-id-prefix>` if empty.
  • machineType (MachineType)
  • snapshotId (string) Snapshot ID to restore from.
  • diskGiB (integer) Override the default disk size (GiB).
  • firewall (FirewallPolicy)
  • metadata (Metadata)
  • envVars (EnvVars)
  • ttl (TTL)
  • volumes (VolumeAttachmentRequest[]) Cold-boot inline volume attachments (managed Volume IDs). On snapshot restore, this list authoritatively replaces the captured list. Omit to use the captured list.
  • bucketMounts (CreateBucketMountRequest[]) Cold-boot inline bucket-mounts. Same authoritative-replace semantics on snapshot restore.

UpdateVMRequest

At least one of `name`, `metadata`, or `ttl` must be provided. Sending `metadata: {}` clears all metadata; omitting it leaves existing metadata unchanged. Sending `ttl: null` explicitly clears the TTL; sending a `TTL` object replaces it; omitting the field leaves the current TTL unchanged.

  • name (string)
  • metadata (Metadata)
  • ttl (any)

CreateSnapshotRequest

  • vmId (string, required)
  • name (string) Snapshot name (trimmed + whitespace-collapsed, max 64 runes; longer values are truncated server-side). Auto-generated as `snapshot-<8-char-vmId-prefix>` if empty.

UpdateSnapshotRequest

Rename a snapshot. `name` is optional; if omitted or empty, the server regenerates the auto-name (`snapshot-<8-char-vmId-prefix>`).

  • name (string)

SnapshotImportSourceSpec

Discriminated source descriptor. `type` selects which other fields are consumed. The opposite-variant fields must be omitted; mixing them is a 400 at the API boundary.

  • type ("image" | "dockerfile", required) - `image`: pull an existing Docker / OCI image reference. - `dockerfile`: build a user-supplied Dockerfile against an uploaded build context.
  • image (string) OCI image reference (e.g. `ghcr.io/foo/bar:v1`, `nginx:1.27`, `alpine@sha256:…`). Required when `type=image`.
  • platform (string) OCI platform selector for multi-arch image indexes, format `<os>/<arch>` (e.g. `linux/amd64`). Defaults to `linux/amd64`. Image-variant only.
  • registryUsername (string) Optional username for private registry pulls. Applies to both source kinds: `type=image` authenticates the OCI pull, `type=dockerfile` authenticates the `FROM` pulls performed by `buildah` inside the sandbox VM.
  • registryPassword (string) Optional password / PAT / OAuth token for private registry pulls. Applies to both source kinds. Held in scheduler process memory between create and dispatch (never persisted) and wiped after the build VM is torn down.
  • registryHost (string) Registry hostname the `registryUsername` / `registryPassword` authenticate against (e.g. `docker.io`, `ghcr.io`, `1234.dkr.ecr.us-east-1.amazonaws.com`). **Required** when credentials are set on `type=dockerfile`: the baker keys the auth.json entry against this host. Tolerated but ignored for `type=image` (the host is derived from the image reference). Optional port: e.g. `registry.example.com:5000`.
  • contextRef (string) Opaque one-shot token returned by `POST /v1/snapshot-imports/context-presign`. Required when `type=dockerfile`. The platform validates that the referenced upload belongs to the calling org and consumes the token on use.
  • dockerfilePath (string) Path to the Dockerfile relative to the context root. Defaults to `Dockerfile`. Must not be absolute and must not contain `..`.
  • buildArgs (object) Optional `--build-arg KEY=VALUE` pairs forwarded to the build. Capped at 64 entries, 8 KiB total.
  • target (string) Optional multi-stage `--target` selector. Empty means the final stage.

CreateSnapshotImportRequest

Body for `POST /v1/snapshot-imports`. The discriminated `source` is the only image-bearing field; everything else is sizing or labels.

  • machineType (MachineType)
  • diskGiB (integer) Disk size for the produced snapshot. Defaults to the machine type's catalog default (typically 10 GiB).
  • name (string) Optional human-readable label for the resulting import and snapshot. If omitted, the import id is used.
  • source (SnapshotImportSourceSpec, required)

SnapshotImportEvent

One entry in an import's append-only event log. Phase + status pairs describe the sub-stages of `running` (preparing → network → pull → export → saving → warming).

  • phase (string, required) Pipeline sub-phase. Known values include `preparing`, `network`, `pull` (image source), `fetch_context`, `build` (dockerfile source), `export`, `saving`, `warming`, `done`.
  • status (string, required) Event status. Known values include `started`, `completed`, `failed`, `skipped`, `cancelled`.
  • timestampMs (integer, required) Unix-epoch milliseconds.
  • message (string) Optional user-safe summary. Never contains credentials or internal paths.

SnapshotImportSourceView

Publicly-rendered source descriptor returned on `GET /v1/snapshot-imports/{id}`. Strips secrets (`registryPassword`, raw context object keys) — only fields safe to echo back to the caller appear here.

  • type ("image" | "dockerfile", required)
  • image (string)
  • platform (string)
  • registryUsername (string)
  • registryHost (string) Registry hostname for dockerfile-source private builds. Empty for image-source (derived from the image reference, not stored).
  • dockerfilePath (string)
  • buildArgs (object)
  • target (string)
  • contextSizeBytes (integer)

SnapshotImportResponse

Current state of a snapshot import. Returned by `POST /v1/snapshot-imports` (initial `pending` state), `GET /v1/snapshot-imports/{id}`, `GET /v1/snapshot-imports` (in the array elements), and `POST /v1/snapshot-imports/{id}/cancel`.

  • id (string, required) Import id (UUID).
  • name (string)
  • source (SnapshotImportSourceView, required)
  • status (string, required) Current state. Known values: `pending` (queued, no worker yet), `claimed` (worker assigned, dispatch in flight), `running` (worker executing the pipeline), `succeeded` / `failed` / `cancelled` (terminal).
  • snapshotId (string) Set when `status` is `succeeded`. Fetch the corresponding Snapshot record via `GET /v1/snapshots/{id}`.
  • error (string) Set when `status` is `failed`. User-safe diagnostic.
  • events (SnapshotImportEvent[])
  • machineName (string)
  • cpu (integer)
  • memoryMiB (integer)
  • diskGiB (integer)
  • createdAt (string, required)
  • startedAt (string)
  • updatedAt (string)
  • finishedAt (string)

ContextPresignRequest

Body for `POST /v1/snapshot-imports/context-presign`. All fields optional; clients that know the upload size up front can supply `sizeBytes` to get an early rejection if the payload would exceed the platform cap.

  • sizeBytes (integer) Planned upload size. The server rejects this request with `400` when it exceeds the platform-wide cap (the same cap is also enforced by the signed URL itself).

ContextPresignResponse

One-shot upload handle for the dockerfile-source flow.

  • contextRef (string, required) Opaque token to pass as `source.contextRef` on the subsequent `POST /v1/snapshot-imports`. Single-use; the create call consumes the entry.
  • uploadUrl (string, required) Short-lived signed PUT URL. Upload the build-context ZIP archive here with `Content-Type: application/zip`.
  • expiresInSec (integer, required) TTL of `uploadUrl`, in seconds.
  • maxUploadBytes (integer, required) Server-side cap on upload size. The signed URL also enforces this server-side.

Metadata

Free-form string→string map. Server-enforced limits: up to 256 keys, key length 1–256 bytes, value length ≤4096 bytes, total JSON encoding ≤65536 bytes.

EnvVars

Environment variable string→string map injected into the VM at boot. Keys must be 1–256 bytes and match shell-variable name (`[A-Za-z_][A-Za-z0-9_]*`); values may not contain newline, carriage return, or null bytes. Total JSON encoding ≤65536 bytes.

ExecVMRequest

  • command (string[], required) Argv-style command. First element must be non-empty. For shell strings, wrap as `["sh", "-c", "<string>"]`.
  • timeoutSec (integer) Server-side execution timeout in seconds. Must be positive when provided; omit to use the server default.
  • stdin (string) Optional base64-encoded stdin blob, written to the child's stdin before the process starts reading much and then closed. Streaming stdin is not supported — pipe from a file inside the guest if you need that shape.

ExecEvent

One event in the NDJSON exec stream returned by `POST /v1/vms/{id}/exec` under `Accept: application/x-ndjson`. Short field names (`t`, `d`, `c`, `to`, `ms`) keep per-chunk overhead small since high-output commands can produce thousands of events per exec.

  • t ("o" | "e" | "x", required) Event type: `o` = stdout chunk, `e` = stderr chunk, `x` = terminal exit event.
  • d (string) For `o`/`e`: base64-encoded raw bytes of the chunk. For `x`: optional diagnostic string (e.g. spawn failure) when non-empty.
  • c (integer) Exit code. Present on `x` events only.
  • to (boolean) True if the command was killed by the timeout. `x` events only.
  • ms (integer) Guest-reported duration in milliseconds. `x` events only.

ExecVMResponse

Buffered response shape for `POST /v1/vms/{id}/exec` under `Accept: application/json`. The server collects the streamed events and returns this aggregate once the command exits. Per-stream output is capped at 4 MiB; overflow bytes are dropped and signalled via `stdoutTruncated` / `stderrTruncated`. Streaming clients (`Accept: application/x-ndjson`) receive every byte without a cap.

  • exitCode (integer, required)
  • stdout (string, required)
  • stderr (string, required)
  • timedOut (boolean, required)
  • stdoutTruncated (boolean, required) True if the collector dropped stdout bytes past the 4 MiB cap.
  • stderrTruncated (boolean, required) True if the collector dropped stderr bytes past the 4 MiB cap.
  • durationMs (integer, required)

FilePresignRequest

  • path (string, required) Absolute destination path inside the guest filesystem (where the file will land after `fetchFileToVm`). Used only to scope the staging object key; any value server-side is accepted here.

FilePresignResponse

Pair of signed URLs scoped to the same per-VM staging object. Usable in either direction: either side (client or VM) PUTs bytes to `uploadUrl`, and either side GETs them back via `downloadUrl`. URLs expire after `expiresInSec` seconds and the staging object is auto-deleted after about a day.

  • uploadUrl (string, required) Presigned PUT URL for the staging object. Accepts `Content-Type: application/octet-stream`. Used by the client on upload, or by the VM (via an exec'd `curl -T -`) on download.
  • downloadUrl (string, required) Presigned GET URL for the same staging object. Used by the VM (via `POST /v1/vms/{id}/files/fetch`) on upload, or by the client (via `httpx.stream` / `curl`) on download.
  • expiresInSec (integer, required) Lifetime of both URLs in seconds.
  • maxUploadBytes (integer, required) Upper bound on upload size (equals the VM's disk size in bytes).

FileFetchRequest

  • url (string, required) Must be the `downloadUrl` previously returned by `POST /v1/vms/{id}/files/presign` (URLs from other sources are rejected).
  • path (string, required) Absolute destination path inside the guest filesystem.
  • timeoutSec (integer) Per-fetch timeout in seconds.

ConsoleTokenResponse

  • token (string, required)
  • expiresInSec (integer, required)
  • websocketPath (string, required) Relative WebSocket path; combine with your API host as `wss://<host><websocketPath>?session=<token>`.

SshKey

  • name (string) Optional human label.
  • publicKey (string, required) OpenSSH-format public key, of the form `<type> <base64-blob>` — the optional comment is stripped server-side. Supported types: `ssh-ed25519`, `ssh-rsa`, `ecdsa-sha2-nistp{256,384,521}`, plus FIDO2 hardware-backed variants (`sk-...@openssh.com`).
  • fingerprint (string, required) OpenSSH SHA256 fingerprint, e.g. `SHA256:abc...`. This is the **identifier** — matches what `ssh-keygen -lf` prints and what your ssh client shows on first connect; pass it back as the `{fingerprint}` path segment to `deleteSshKey`.
  • createdAt (string, required)

SshKeyListResponse

  • keys (SshKey[], required)

AddSshKeyRequest

  • name (string) Optional human label.
  • publicKey (string, required) OpenSSH-format public key (`ssh-ed25519 AAA...`). Comments are stripped. Newlines are rejected.

OrgQuotaValues

  • vcpu (integer, required)
  • memoryMiB (integer, required)
  • diskGiB (integer, required)
  • snapshotCount (integer, required)
  • volumeCount (integer, required)
  • volumeGiB (integer, required)

OrgQuotaUsage

  • orgId (string, required)
  • limits (OrgQuotaValues, required)
  • usage (OrgQuotaValues, required)

Service

  • name (string, required) Service name (1–29 chars). Embedded in the public URL as `<name>--<vmIdHexNoHyphens>.proxy.<stack-domain>`.
  • port (integer, required) TCP port the service listens on inside the VM. Privileged ports (<1024) are rejected.
  • h2c (boolean, required) When true, the proxy speaks HTTP/2 cleartext (h2c) to the backend. Required for gRPC and h2c-only apps. When false (default), the proxy uses HTTP/1.1 — covers HTTP/1.1 apps, Server-Sent Events, and WebSocket pass-through.

RegisterServiceRequest

  • name (string, required)
  • port (integer, required)
  • h2c (boolean) Optional. When true, the proxy uses HTTP/2 cleartext to the backend (required for gRPC). Defaults to false (HTTP/1.1).

UpdateServiceRequest

  • port (integer, required) New TCP port. Same value as the existing entry is a no-op.
  • h2c (boolean) Optional. When true, the proxy uses HTTP/2 cleartext to the backend. Same value as the existing entry is a no-op; a different value updates the registered transport.

Volume

  • id (string, required)
  • name (string, required)
  • orgId (string, required)
  • accessMode (string, required) Access mode. Known values: `rw`, `ro`. Future server versions may introduce additional values.
  • sizeGiB (integer, required)
  • status (string, required) Lifecycle status. Known values: - `creating` — the substrate-create saga is in flight. Set by the server briefly between the customer's `POST /v1/volumes` and the worker substrate provisioning; attach attempts are rejected with `VOL_NOT_READY` until the saga commits. Clients polling immediately after create may observe this state. - `ready` — substrate is up; attachable. - `deleting` — cleanup is in progress; not attachable. Future server versions may introduce additional values.
  • pendingSizeGiB (integer) When non-zero, a resize saga is in flight; `sizeGiB` is still the pre-resize value and `pendingSizeGiB` is the target. Set briefly between `PATCH /v1/volumes/{id}` and the substrate resize commit. Clients polling immediately after a resize may observe a non-zero value.
  • mountedCount (integer, required) Number of currently-running VMs with this volume attached (paused VMs are NOT counted).
  • usedGiB (integer) Bytes used inside the volume (rounded down to GiB). Fetched on-demand from the substrate; omitted when the substrate is unreachable.
  • createdAt (string, required)

CreateVolumeRequest

  • name (string, required)
  • sizeGiB (integer, required)
  • accessMode ("rw" | "ro", required)

UpdateVolumeRequest

At least one of `name`, `sizeGiB`, or `accessMode` must be present. `accessMode` requires `mountedCount == 0`. `sizeGiB` shrink requires `usedGiB <= newSizeGiB`.

  • name (string)
  • sizeGiB (integer)
  • accessMode ("rw" | "ro")

VolumeAttachmentRequest

  • volumeId (string, required)
  • mountPath (string, required) Absolute path; must start with /mnt/ or /data/.
  • readOnly (boolean)

VolumeAttachmentItem

  • volumeId (string, required)
  • mountPath (string, required)
  • readOnly (boolean)
  • mountStatus (string, required) Known values: `mounted`, `failed`, `pending`. `pending` appears on attachments to paused VMs (mount happens on resume) and briefly during in-flight hot-attach.
  • statusMessage (string)

VolumeAttachmentItemWithVm

DetachVolumeResponse

  • detached (boolean, required)
  • warnings (DetachWarning[])

DetachWarning

  • type (string, required) Known values: `ack_timeout`, `guest_unresponsive`.
  • message (string, required)

BucketMount

  • id (string, required)
  • vmId (string, required)
  • bucketUri (string, required) `gs://...` or `s3://...`; future schemes may be added.
  • mountPath (string, required)
  • readOnly (boolean)
  • mountStatus (string, required) Known values: `mounted`, `failed`, `pending`.
  • statusMessage (string)
  • createdAt (string, required)

BucketMountCredentials

Customer-provided credentials. Never returned in API responses. Discriminated union: the `type` property selects the per-provider shape so SDKs surface typed per-type values.

GcpServiceAccountCredentials

  • type ("gcp-service-account-json", required)
  • value (object, required)

AwsCredentials

  • type ("aws-credentials", required)
  • value (object, required)

CreateBucketMountRequest

  • bucketUri (string, required) Customer's GCS or S3 bucket URI. `gs://<bucket>[/prefix]` or `s3://<bucket>[/prefix]`.
  • mountPath (string, required)
  • readOnly (boolean)
  • credentials (BucketMountCredentials, required)

UpdateBucketMountRequest

  • credentials (BucketMountCredentials, required)

AttachmentWarnings

  • skippedSnapshotVolumes (SnapshotVolumeSkip[])
  • failedVolumeAttachments (FailedVolumeAttachment[])
  • skippedSnapshotBucketMounts (SnapshotBucketMountSkip[])
  • failedBucketMountAttachments (FailedBucketMountAttachment[])

SnapshotVolumeSkip

  • volumeId (string, required)
  • mountPath (string, required)
  • reason (string, required) Known values: `deleted`, `deleting`, `cross_org`, `vol_ro_ceiling_after_patch`.

FailedVolumeAttachment

  • volumeId (string, required)
  • mountPath (string, required)
  • statusMessage (string, required)

SnapshotBucketMountSkip

  • bucketUri (string, required)
  • mountPath (string, required)
  • reason (string, required) Known values: `credentials_invalid`, `bucket_unreachable`, `credentials_unavailable`.

FailedBucketMountAttachment

  • bucketUri (string, required)
  • mountPath (string, required)
  • statusMessage (string, required)

SnapshotVolumeAttachment

  • volumeId (string, required)
  • mountPath (string, required)
  • readOnly (boolean)

SnapshotBucketMountAttachment

  • bucketUri (string, required)
  • mountPath (string, required)
  • readOnly (boolean)