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, and on snapshot restores it may include an optional `snapshotRestoreWarnings` field if 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).
  • 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-VM authorized SSH key management. Register pubkeys here, then `ssh root@<vm.publicIpv6>` directly — no proxy or wrapper needed. Snapshot images carry zero customer keys; the per-VM authorized set is pushed to the running guest on every add/remove.

GET /v1/vms/{id}/ssh-keys

List authorized SSH keys for a VM

Returns every SSH public key registered for this VM. This is the canonical "who can ssh in" set — pushed to the guest agent on every add/remove and rewritten verbatim into `/root/.ssh/authorized_keys_fastvm` inside the guest. SSH itself is plain `ssh root@<vm.publicIpv6>` against the VM's public IPv6 address (visible on the VM resource); make sure port 22 is open in the VM's firewall.

Auth required (X-API-Key).

Parameters

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

Responses

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

POST /v1/vms/{id}/ssh-keys

Register an SSH public key

Adds one authorized SSH public key to this VM. The fingerprint is derived server-side and returned. Duplicate fingerprints return 409. Up to 32 keys per VM. The new set is pushed to the running guest synchronously (best-effort if the VM isn't running yet). After this call, `ssh root@<vm.publicIpv6>` works from any host with IPv6 connectivity to the VM (port 22 must be open in the VM firewall).

Auth required (X-API-Key).

Parameters

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

Request body: AddSshKeyRequest

Responses

  • 201 SshKey Key registered
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 404 Error Resource not found
  • 409 Error Key with this fingerprint is already registered
  • 500 Error Internal server error

DELETE /v1/vms/{id}/ssh-keys/{fingerprint}

Remove an authorized SSH key

Deletes one key by fingerprint. The new set is pushed to the running guest synchronously. Existing SSH sessions on the VM are NOT terminated — the key simply won't authorize new connections.

Auth required (X-API-Key).

Parameters

  • id (path, string, required) VM ID (UUID).
  • 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

Builds

Build snapshots from a Docker image ref or Dockerfile

POST /v1/builds

Build a snapshot from an image ref or Dockerfile

Submits an asynchronous build. The scheduler creates a build VM, runs `buildah pull` (image-only path) or `buildah bud` (Dockerfile path) inside it, snapshots the result, and tears the VM down. At least one of `imageRef` or `dockerfileContent` must be provided. If `dockerfileContent` is set, the worker writes it verbatim into `/tmp/buildctx/Dockerfile` — buildah handles multi-stage, `SHELL`, `RUN --mount`, etc. natively. For `COPY` instructions that need files, upload the build context first via `POST /v1/build-contexts/presign` and pass the returned download URL as `contextDownloadUrl`. Response is `202 Accepted` with a build ID; poll `GET /v1/builds/{id}` until `status` is `completed` or `failed`.

Auth required (X-API-Key).

Request body: CreateBuildRequest

Responses

  • 202 BuildResponse Build accepted; poll `GET /v1/builds/{id}` for status
  • 400 Error Invalid request
  • 401 Error Missing or invalid credentials
  • 403 Error Org quota exceeded
  • 413 Error `contextDownloadUrl` references an object larger than the server-side cap (1 GiB). The cap is also returned as `maxUploadBytes` from `POST /v1/build-contexts/presign`, so the SDK can advise users before upload — this 413 is the authoritative server-side enforcement.
  • 500 Error Internal server error
  • 501 Error File staging is not configured on this cluster (no `FILE_STAGING_BUCKET`). Returned only when `contextDownloadUrl` is supplied — builds without a context work fine on staging-disabled clusters.

GET /v1/builds/{id}

Get build status

Returns the current state of a build. While the build is in progress, `status` is `pending` or `running` and `progress` contains a human-readable string describing the current phase (e.g. `Pulling image`, `Building (3 steps)`, `Settling VM`). On success, `status` is `completed` and `snapshotId` references a `ready` snapshot — fetch it via `GET /v1/snapshots/{id}`. On failure, `status` is `failed` and `error` carries the worker's diagnostic.

Auth required (X-API-Key).

Parameters

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

Responses

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

POST /v1/build-contexts/presign

Mint signed URLs for uploading a build context tarball

Returns a pair of short-lived signed URLs targeting a per-org staging location. Tar+gzip your build-context directory, PUT it to `uploadUrl` with `Content-Type: application/gzip`, then pass `downloadUrl` as `contextDownloadUrl` on `POST /v1/builds`. Unlike `/v1/vms/{id}/files/presign`, this endpoint isn't keyed to a specific VM — context uploads happen *before* the build VM exists.

Auth required (X-API-Key).

Responses

  • 200 FilePresignResponse Signed URLs + upload size ceiling
  • 401 Error Missing or invalid credentials
  • 500 Error Internal server error
  • 501 Error File staging is not configured on this deployment

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

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.

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.
  • 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)

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)

CreateBuildRequest

At least one of `imageRef` or `dockerfileContent` must be set. If only `imageRef` is provided, the build VM pulls that image and rsyncs its rootfs over the VM's `/`. If `dockerfileContent` is provided, the build VM writes it verbatim to `/tmp/buildctx/Dockerfile` and runs `buildah bud`.

  • name (string) Optional human-readable name for the resulting snapshot. If omitted, the build ID is used.
  • imageRef (string) Docker image reference (e.g. `python:3.13-slim`, `ghcr.io/user/repo:tag`). Used directly on the no-Dockerfile path, and as a fallback `FROM` source otherwise.
  • dockerfileContent (string) Raw Dockerfile content to feed to `buildah bud` inside the build VM. Multi-stage, `SHELL`, `RUN --mount`, and every standard Dockerfile feature is supported (handled natively by buildah). Container-runtime metadata (`CMD`, `ENTRYPOINT`, `EXPOSE`, `LABEL`, `HEALTHCHECK`) is consumed by buildah but does not surface on the resulting FastVM snapshot — when the snapshot boots, systemd takes over, not the container's CMD.
  • machineType (MachineType)
  • diskGiB (integer) Disk size for the build VM. Defaults to 10 GiB if omitted.
  • contextDownloadUrl (string) Presigned GET URL for a `tar.gz` of the build context. The worker downloads and extracts this into `/tmp/buildctx` before invoking buildah, so `COPY` instructions resolve against the user's files. Obtain via `POST /v1/build-contexts/presign`.

BuildResponse

Build state snapshot. Returned by `POST /v1/builds` (initial `pending` state) and `GET /v1/builds/{id}` (current state on each poll).

  • id (string, required) Build ID (UUID). Use this to poll status.
  • name (string)
  • status (string, required) Current state. Known values: `pending` (accepted, not yet started), `running` (worker is executing), `completed` (snapshot is ready), `failed` (build did not produce a snapshot). Additional values may be introduced in future server versions; clients should treat unknown values as "in progress" rather than as hard errors.
  • snapshotId (string) Set when `status` is `completed`. Fetch the corresponding Snapshot record via `GET /v1/snapshots/{id}`.
  • imageRef (string, required)
  • progress (string) Human-readable phase string while the build runs (e.g. `creating build VM`, `buildah pull`, `buildah bud`, `applying image`, `settling VM`, `creating snapshot`). Not present after a terminal status.
  • error (string) Set when `status` is `failed`. Diagnostic from the worker (truncated to ~4 KiB).
  • createdAt (string, required)

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)

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.