---
title: "Launch a VM"
method: POST
path: "/v1/vms"
tags: ["vms"]
---

# Launch a VM

`POST /v1/vms`

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.

## Request body

- 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` string — Machine size identifier (e.g. `c1m2`, `c2m4`). Controls CPU and memory allocation. Must be supplied on launch unless restoring from a snapshot.
  - `snapshotId` string — Snapshot ID to restore from.
  - `diskGiB` integer — Override the default disk size (GiB).
  - `firewall` FirewallPolicy
    - `mode` string, required — Firewall mode. Known values: `open` (allow all inbound traffic), `restricted` (deny by default; only rules listed in `ingress` are allowed). Additional values may be introduced in future server versions.
    - `ingress` FirewallRule[]
      - `protocol` string, required — IP protocol. Known values: `tcp`, `udp`. Additional values may be introduced in future server versions.
      - `portStart` integer, required — Start of port range (inclusive). Required.
      - `portEnd` integer — End of port range (inclusive). Omit for single-port rules.
      - `sourceCidrs` string[] — Allowed source CIDRs in IPv6 notation (e.g. `2001:db8::/32`). Omit or empty to allow any source. IPv4 CIDRs are rejected.
      - `description` string
  - `metadata` 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` 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.
  - `ttl` 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.

## Response `201`

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

- 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).
  - `id` string, required
  - `name` string, required
  - `orgId` string, required
  - `machineName` string
  - `sourceName` string — Source snapshot or image name (empty on fresh boot).
  - `firewall` FirewallPolicy
    - `mode` string, required — Firewall mode. Known values: `open` (allow all inbound traffic), `restricted` (deny by default; only rules listed in `ingress` are allowed). Additional values may be introduced in future server versions.
    - `ingress` FirewallRule[]
      - `protocol` string, required — IP protocol. Known values: `tcp`, `udp`. Additional values may be introduced in future server versions.
      - `portStart` integer, required — Start of port range (inclusive). Required.
      - `portEnd` integer — End of port range (inclusive). Omit for single-port rules.
      - `sourceCidrs` string[] — Allowed source CIDRs in IPv6 notation (e.g. `2001:db8::/32`). Omit or empty to allow any source. IPv4 CIDRs are rejected.
      - `description` string
  - `effectiveFirewall` FirewallPolicy
    - `mode` string, required — Firewall mode. Known values: `open` (allow all inbound traffic), `restricted` (deny by default; only rules listed in `ingress` are allowed). Additional values may be introduced in future server versions.
    - `ingress` FirewallRule[]
      - `protocol` string, required — IP protocol. Known values: `tcp`, `udp`. Additional values may be introduced in future server versions.
      - `portStart` integer, required — Start of port range (inclusive). Required.
      - `portEnd` integer — End of port range (inclusive). Omit for single-port rules.
      - `sourceCidrs` string[] — Allowed source CIDRs in IPv6 notation (e.g. `2001:db8::/32`). Omit or empty to allow any source. IPv4 CIDRs are rejected.
      - `description` string
  - `metadata` 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` 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.
  - `publicIpv6` string
  - `cpu` integer, required
  - `memoryMiB` integer, required
  - `diskGiB` integer, required
  - `status` string, required — 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.
  - `createdAt` string, date-time, required
  - `deletedAt` string, date-time, nullable
  - `ttl` 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.
  - `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, date-time, nullable — When the VM became paused; null otherwise.
  - `snapshotRestoreWarnings` 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`.
      - `name` string, required
      - `port` integer, required
      - `h2c` boolean
    - `reason` string — Operator-facing diagnostic for the failure.

## Other responses

- `202` — VM is queued; poll for readiness
- `400` — Invalid request
- `401` — Missing or invalid credentials
- `403` — Org quota exceeded
- `404` — Snapshot or base image not found
- `500` — Internal server error
- `502` — Upstream service error
- `503` — Service temporarily unavailable

## Changes

- **2026-05-09** `480d9714a7a5` — 9 breaking, 6 warning, 9 info
  - the response's body type/format changed from `object`/`` to ``/`` for status `201`
  - removed the required property `cpu` from the response with the `201` status
  - removed the required property `createdAt` from the response with the `201` status
  - removed the required property `diskGiB` from the response with the `201` status
  - …20 more
- **2026-04-22** `fdb53b5a79b3` — 17 breaking, 28 info
  - removed `subschema #1, subschema #2` from the request body `oneOf` list
  - removed the enum value `c1m2` of the request property `machineType`
  - removed the enum value `c2m4` of the request property `machineType`
  - removed the enum value `c4m8` of the request property `machineType`
  - …41 more

[Change history](https://skmtc.dev/fastvm-org/apis/fastvm-api/changes/v1/vms/post.md)

---

[API](https://skmtc.dev/fastvm-org/apis/fastvm-api.md) · [All operations](https://skmtc.dev/fastvm-org/apis/fastvm-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/fastvm-org/fastvm-api/revisions/480d9714a7a5/schema)
