POST /api/vms
Updated
Create a classic Linux VM. Use a stable vm_id when calling this from
automation so a retry does not create a second VM.
| Property | Value |
|---|---|
| Scope | readwrite |
| Request body | JSON |
| Success | 201 Created or 202 Accepted when image provisioning continues in the background |
Endpoint
${baseUrl}/api/vms Create a classic Linux VM from a registry or cached cloud image.
Use your host URL and API key as shell variables:
baseUrl="https://agent.example.com"
VIRT_AGENT_API_KEY="<your-api-key>" cURL template
curl --request POST "${baseUrl}/api/vms" \
--header "Authorization: Bearer ${VIRT_AGENT_API_KEY}" \
--header "Content-Type: application/json" \
--data @- <<'JSON'
{
"vm_id": "${id}",
"vcpus": 2,
"memory_mb": 2048,
"firmware_path": "/usr/share/virtainer/CLOUDHV.fd",
"image": {
"image_id": "replace-with-image-id",
"content_sha256": "replace-with-registry-sha256",
"fetch_url": "replace-with-registry-download-url",
"distro": "debian",
"version": "13"
},
"disk": {
"mode": "copy",
"size_gib": 20
},
"cloud_init": {
"user": "root",
"ssh_authorized_keys": [
"ssh-ed25519 AAAA..."
],
"console_autologin": false
},
"auto_boot": true
}
JSON Request
Section titled “Request”Use GET /api/registry/images or GET /api/registry/images/{id} to populate
the image fields.
| Object | Field | Type | Required | Description |
|---|---|---|---|---|
| Header | Authorization | bearer token | yes | Bearer <api-key> from a readwrite key. |
| Body | vm_id | string | no | Stable VM id. Omit to let the agent generate a UUID v4. Explicit ids must match ^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$. |
vcpus | integer | yes | vCPU count. Must be at least 1. | |
memory_mb | integer | yes | Memory in MiB. Must be at least 64. | |
kernel_path | string | conditionally | Absolute PVH kernel path on the Lite host. Exactly one of kernel_path or firmware_path is required. | |
firmware_path | string | conditionally | Absolute firmware path on the Lite host. Exactly one of kernel_path or firmware_path is required. | |
disk | object | conditionally | Agent-owned root disk copy settings. Required unless image injects the base image into the disk copy path. | |
image | object | no | Remote or registry image descriptor. When present, the agent downloads and verifies the image, then uses it as disk.base_path. | |
volumes | array of objects | no | Extra blank data volumes attached at create time. Omit or pass an empty array for no data volumes. | |
cmdline | string | no | Kernel command line. Omit to use the host default. | |
nets | array of objects | no | Network interfaces, in order: index 0 becomes the guest’s eth0. If cloud_init is present and nets is omitted or empty, the handler attaches one interface on the default VM network. At most one interface may carry an IPv4 gateway, and at most one an IPv6 gateway. | |
platform | object | no | SMBIOS platform values. | |
cloud_init | object | no | NoCloud seed configuration rendered by the agent and attached to the VM. Its network block configures the first interface only; the rest take their guest address from their own ipv4. | |
nested | boolean | no | Whether the guest may itself run KVM. Omit to follow the hypervisor default. Requesting true on a host without nested virtualization returns 400 rather than a VM that silently lacks it. | |
cpu_affinity | string | no | Host logical CPUs to pin the vCPUs to, in cpuset notation such as “0-3” or “0,2,4-6”. Omit for no pinning. | |
cpu_topology | object | no | Guest-visible CPU topology. Omit for the hypervisor default. Setting it removes this VM’s vCPU hot-add headroom, because the topology is fixed at boot. | |
pci_devices | array of strings | no | Host PCI devices to pass through, as full BDF strings such as “0000:07:00.0”. Every device in the same IOMMU group must be listed together; GET /api/host/pci reports the grouping. Setting it removes this VM’s memory hot-add headroom, because passthrough pins guest memory. | |
disk_bps_total | integer | no | Root-disk throughput ceiling in bytes per second. Omit or 0 for unlimited. | |
disk_iops_total | integer | no | Root-disk ceiling in IO operations per second. Omit or 0 for unlimited. | |
auto_boot | boolean | no | Default false. When true, the agent creates the VM and immediately boots it in the same request. | |
| disk | mode | string | no | Only copy is accepted. Omitted means copy. |
base_path | string | conditionally | Absolute host path to a trusted qcow2 base image. Required when image is not provided. | |
size_gib | integer | no | Root disk size in GiB, 1..65535. Omit to keep the base virtual size or host default. The agent will not shrink below the base image size. | |
| image | image_id | string | yes | Image content id. Stored in the VM view as image.image_id. |
content_sha256 | string | yes | Expected SHA-256 digest of the downloaded image content. | |
fetch_url | string | yes | URL the Lite host can fetch. | |
distro | string | no | Display metadata, such as debian or fedora. | |
version | string | no | Display metadata, such as 13 or 42. | |
| volumes[] | size_gib | integer | yes | Blank data volume size in GiB. Must be at least 1. Volumes are attached in request order. |
| nets[] | mode | string | no | Only bridge is accepted. Omitted means bridge. |
bridge | string | no | Host bridge interface. Omit to use the resolved default bridge. | |
mac | string | no | Guest MAC address. Omit to let the agent generate one. | |
network_id | string | no | Host network id from GET /api/networks. If provided, the agent resolves that network’s bridge. | |
vlan_tag | integer | no | 802.1Q VLAN tag metadata. | |
ipv4 | object | no | Static IPv4 metadata: address, optional gateway, optional dns_servers. | |
ipv6 | object | no | Static IPv6 metadata: address, optional gateway, optional dns_servers. | |
mtu | integer | no | Interface MTU. Omit to use host defaults. | |
isolated | boolean | no | Per-VM bridge port isolation override. Omit to inherit host policy. | |
spoof_guard | boolean | no | Per-VM MAC/IP anti-spoof override. Omit to inherit host policy. | |
rate_limit_mbps | integer | no | Symmetric network rate limit in Mbps. Accepted range is 1..100000; omit for no limit. | |
allowed_ips | array of strings | no | Extra source addresses or CIDRs anti-spoof lets through, beyond the interface’s own address. Use it for guest-side routing, NAT, VPN egress, or a floating VRRP address. It only extends a pinned address: on an interface with neither a static ipv4 nor a learned DHCP binding it stays inert. | |
| nets[].ipv4 / nets[].ipv6 | address | string | yes | CIDR address, for example 10.77.23.2/24 or 2001:db8::2/64. |
gateway | string | no | Gateway IP address without CIDR suffix. | |
dns_servers | array of strings | no | DNS server IP addresses. | |
| cpu_topology | sockets | integer | yes | Guest-visible socket count. |
cores | integer | yes | Cores per socket. | |
threads | integer | yes | Threads per core. sockets × cores × threads must equal vcpus exactly, or the request is rejected. | |
| platform | serial_number | string | no | SMBIOS serial number. Maximum 512 bytes; control characters, NUL, newline, and carriage return are rejected. |
| cloud_init | hostname | string | no | Guest hostname. Omit to derive it from vm_id. Explicit values must be at most 63 bytes, use only letters, digits, and -, and cannot start or end with -. |
user | string | no | Login user. Omit for root. | |
ssh_authorized_keys | array of strings | yes when using cloud-init login | Public SSH keys written into the guest. Provide at least one key for automated access. | |
password | string | no | Optional password for the cloud-init user. API-key VM views do not return this value. | |
network | object | no | Static guest network config rendered into NoCloud network-config. Omit for guest DHCP. | |
console_autologin | boolean | no | Default false. When true, serial console login drops directly to a shell. | |
sudo | boolean | no | Default false. Grants sudo to non-root users. | |
instance_id | string | no | Advanced cloud-init instance identity. Omit unless preserving first-boot identity across rebuilds or migration. | |
raw_user_data | null | no | Reserved placeholder. Non-null values are rejected. | |
raw_network_config | null | no | Reserved placeholder. Non-null values are rejected. | |
| cloud_init.network | ipv4 | object | yes | Static IPv4 config for the guest. |
ipv6 | object | no | Static IPv6 config for the guest. | |
| cloud_init.network.ipv4 / ipv6 | address | string | yes | CIDR address. |
gateway | string | no | Gateway IP address without CIDR suffix. | |
dns | array of strings | no | DNS server IP addresses. This cloud-init field is named dns, not dns_servers. |
Response
Section titled “Response”| Status | Body | Description |
|---|---|---|
201 Created | VmView | VM was created. If auto_boot is true, the returned view reflects the post-boot host state. |
202 Accepted | provision view | Image provisioning was accepted and continues in the background. Poll GET /api/vms/{id}. |
Both success statuses return a classic VmView. With 201 Created, the view
describes the created VM. With 202 Accepted, it is an in-progress view:
state is created, display_state is downloading or creating, and fields
whose resources do not exist yet can be empty or omitted.
Fields marked optional are omitted from the JSON object when
they have no value. Fields marked nullable are always present
and use null when they do not apply.
VmView
| Field | Type | Presence | Description |
|---|---|---|---|
id | string | always | VM id. |
state | string | always | Stored lifecycle state: created, running, paused, stopped, or failed. |
display_state | string | always | User-facing state. In addition to lifecycle states, classic VM provisioning can report booting, downloading, creating, or provision_failed. |
vcpus | integer | always | Assigned vCPU count. |
memory_mb | integer | always | Assigned memory in MiB. |
kernel_path | string or null | nullable | PVH kernel path when direct kernel boot is used. |
firmware_path | string or null | nullable | Firmware path when firmware boot is used. |
disk_path | string | always | Root disk path on the Lite host. An in-progress classic provision returns an empty string until the disk exists. |
volumes | array of VmVolume | always | Attached data volumes, in disk order. The item shape is documented below. |
seed_disk | SeedDisk | optional | The host-generated cloud-init seed disk. Present only on a VM with cloud_init. It is not a data volume and cannot be detached, but it does occupy a guest device letter. |
cmdline | string | always | Kernel command line. |
console_socket_path | string | always | Host serial-console socket path. An in-progress classic provision returns an empty string. |
failure_reason | FailureReason | optional | Human-readable and machine-readable failure details. |
pid | integer | optional | Cloud Hypervisor process id when known. |
nets | array of VmNetwork | always | Network interfaces in order: index 0 is the guest's eth0. Empty array when the VM has no interface. |
platform | VmPlatform | optional | SMBIOS platform values. |
disk | VmDisk | optional | Agent-managed root disk metadata. |
image | VmImage | optional | Classic VM source image identity. |
cloud_init | CloudInitView | optional | Classic VM cloud-init view. API-key responses omit cloud_init.password. |
io_limit | DiskIoLimit | optional | Root-disk throughput ceiling. Omitted when the disk is unthrottled. |
nested | boolean | optional | Whether the guest may itself run KVM. Omitted when never set explicitly, which means the hypervisor default applies. |
cpu_affinity | array of integers | optional | Host logical CPUs the vCPUs are pinned to. Omitted when unpinned. |
cpu_topology | CpuTopology | optional | Guest-visible CPU topology. Omitted when the hypervisor default applies. |
pci_devices | array of strings | always | Passed-through host PCI devices, as full BDF strings such as 0000:07:00.0. Empty array when nothing is passed through. |
balloon_mb | integer | optional | Memory reclaim target in MiB. Omitted when no memory is being reclaimed. |
created_at | RFC 3339 string | optional | Creation timestamp. |
discovered_ipv4 | string | optional | Observed guest IPv4 address. |
guest_panicked_at | RFC 3339 string | optional | When a guest kernel panic was observed over pvpanic. state stays running: the hypervisor is alive and the guest is not. |
shutting_down | boolean | optional | Present as true while ACPI shutdown is pending; omitted when false. |
mode | string | always | Always vm on this endpoint. |
volumes[] (VmVolume)
| Field | Type | Presence | Description |
|---|---|---|---|
id | string | always | Stable per-VM volume id, such as data0. |
path | string | always | Volume file path on the Lite host. |
virtual_size_bytes | integer | always | Virtual size visible to the guest, in bytes. |
fmt | string | always | Volume image format. Currently qcow2. |
readonly | boolean | always | Whether the volume is attached read-only. |
owned | boolean | always | Whether the agent owns and manages the volume file. true means deleting the VM erases it. |
volume_id | string | optional | Id of the matching first-class volume, whose lifetime is independent of this VM. Omitted for a VM-owned disk. |
device | string | always | Guest block device, such as /dev/vdc. Derived from disk order, so detaching an earlier disk shifts this one down a letter. Mount by UUID or label in the guest, never by device letter. |
disk_bytes | integer | optional | Bytes the volume file actually occupies, which is smaller than virtual_size_bytes for a sparse image. Only the single-VM read fills this in; list responses omit it rather than pay a stat per row. |
seed_disk (SeedDisk)
| Field | Type | Presence | Description |
|---|---|---|---|
device | string | always | Guest block device holding the cloud-init seed. It sits directly after the root disk, so data volumes on a cloud-init VM start one letter later than on a VM without one. |
size_bytes | integer | always | Size the guest sees. Fixed, and small: the seed holds only the rendered cloud-init files. |
io_limit (DiskIoLimit)
| Field | Type | Presence | Description |
|---|---|---|---|
bps_total | integer | optional | Combined read and write ceiling in bytes per second. Omitted when unlimited. |
iops_total | integer | optional | Combined read and write ceiling in IO operations per second. Omitted when unlimited. |
cpu_topology (CpuTopology)
| Field | Type | Presence | Description |
|---|---|---|---|
sockets | integer | always | Guest-visible socket count. |
cores | integer | always | Cores per socket. |
threads | integer | always | Threads per core. sockets × cores × threads always equals vcpus. |
failure_reason (FailureReason)
| Field | Type | Presence | Description |
|---|---|---|---|
detected_at | RFC 3339 string | always | Time the failure was detected. |
summary | string | always | One-line operator-readable explanation. |
detail | object | always | Tagged machine-readable detail. kind is one of socket_gone, socket_dead, ping_timeout, process_exit, process_signal, missing_pid, disk_missing, invalid_vm_id, boot_timeout, or provision_failed. |
detail.kind | string | always | Failure-detail discriminator. |
detail.code | integer | conditional | Process exit code when kind is process_exit. |
detail.signum | integer | conditional | Signal number when kind is process_signal. |
detail.path | string | conditional | Missing disk path when kind is disk_missing. |
detail.id | string | conditional | Invalid VM id when kind is invalid_vm_id. |
detail.seconds | integer | conditional | Timeout duration when kind is boot_timeout. |
cause | string | always | Higher-level cause: operator_shutdown, guest_shutdown, ch_crash, vmm_hang, disk_missing, boot_timeout, provision_failed, record_invalid, or unknown. |
nets[] (VmNetwork)
| Field | Type | Presence | Description |
|---|---|---|---|
mode | string | always | Network mode. Currently bridge. |
bridge | string | always | Host bridge interface. |
tap_name | string | always | Agent-created tap interface. |
mac | string | always | Guest MAC address. |
network_id | string | optional | Host network id. |
vlan_tag | integer | optional | 802.1Q VLAN tag. |
ipv4 | VmNetworkIpConfig | optional | IPv4 assignment metadata. |
ipv6 | VmNetworkIpConfig | optional | IPv6 assignment metadata. |
mtu | integer | optional | Interface MTU. |
isolated | boolean | optional | Per-VM bridge port-isolation override. |
spoof_guard | boolean | optional | Per-VM anti-spoof override. |
rate_limit_mbps | integer | optional | Symmetric network rate limit in Mbps. |
allowed_ips | array of strings | optional | Extra source addresses or CIDRs anti-spoof lets through, beyond the interface's own address. Omitted when empty. |
dhcp_snooped_ipv4 | string | optional | Guest IPv4 learned from the upstream DHCP ACK. Authoritative, and the preferred source for discovered_ipv4. Only set on an interface with no static ipv4. |
last_observed_ipv4 | string | optional | Last IPv4 seen for this MAC in the host neighbor table. Guest-asserted, so it is a display fallback only, and it survives ARP expiry. |
nets[].ipv4 / nets[].ipv6 (VmNetworkIpConfig)
| Field | Type | Presence | Description |
|---|---|---|---|
address | string | always | CIDR address. |
gateway | string | optional | Gateway address without a CIDR suffix. |
dns_servers | array of strings | optional | DNS server addresses. |
platform (VmPlatform)
| Field | Type | Presence | Description |
|---|---|---|---|
serial_number | string | optional | SMBIOS serial number. |
disk (VmDisk)
| Field | Type | Presence | Description |
|---|---|---|---|
mode | string | always | Root disk mode. Currently copy. |
base_path | string | always | Canonical source image path. |
virtual_size_bytes | integer | always | Virtual size visible to the guest, in bytes. |
owned | boolean | always | Whether the agent owns and manages the root disk file. |
image_fmt | string | optional | Root disk image format. Currently qcow2. |
qsd_pid | integer | optional | Storage process id. |
qsd_start_time | integer | optional | Storage process start time in boot-relative clock ticks. |
qsd_blk_socket | string | optional | Root-disk block socket path. |
qsd_qmp_socket | string | optional | Storage control socket path. |
runtime | VolumeRef | optional | Backend-independent runtime identity. |
disk.runtime (VolumeRef)
| Field | Type | Presence | Description |
|---|---|---|---|
backend | string | always | Storage backend. Currently qsd. |
pid | integer | always | Storage process id. |
start_time | integer | always | Storage process start time in boot-relative clock ticks. |
blk_socket | string | always | Root-disk block socket path. |
qmp_socket | string | always | Storage control socket path. |
image (VmImage)
| Field | Type | Presence | Description |
|---|---|---|---|
image_id | string | always | Source image id. |
distro | string | optional | Distribution name. |
version | string | optional | Distribution version. |
cloud_init (CloudInitView)
| Field | Type | Presence | Description |
|---|---|---|---|
hostname | string | always | Guest hostname. |
user | string | always | Guest account configured by cloud-init. |
ssh_authorized_keys | array of strings | always | SSH public keys. |
password | string | optional | Guest password. This field is always omitted from API-key responses. |
network | CloudInitNetwork | optional | Rendered static guest network input. |
instance_id | string | always | NoCloud instance identity. |
console_autologin | boolean | always | Whether serial-console auto-login is enabled. |
sudo | boolean | always | Whether a non-root cloud-init user receives sudo access. |
cloud_init.network (CloudInitNetwork)
| Field | Type | Presence | Description |
|---|---|---|---|
ipv4 | CloudInitIpConfig | always | Static IPv4 configuration. |
ipv6 | CloudInitIpConfig | optional | Static IPv6 configuration. |
cloud_init.network.ipv4 / ipv6
| Field | Type | Presence | Description |
|---|---|---|---|
address | string | always | CIDR address. |
gateway | string | optional | Gateway address without a CIDR suffix. |
dns | array of strings | optional | DNS server addresses. This field is named dns, not dns_servers. |
202 Accepted is used for asynchronous image provisioning. Treat the response as
an in-progress VM view and poll by id until display_state leaves
downloading or creating.
Examples
Section titled “Examples”Call example
Section titled “Call example”baseUrl="https://agent.example.com"VIRT_AGENT_API_KEY="<your-api-key>"
curl --request POST "${baseUrl}/api/vms" \ --header "Authorization: Bearer ${VIRT_AGENT_API_KEY}" \ --header "Content-Type: application/json" \ --data @- <<'JSON'{ "vm_id": "demo-api-vm", "vcpus": 2, "memory_mb": 2048, "firmware_path": "/usr/share/virtainer/CLOUDHV.fd", "image": { "image_id": "debian-13-x86_64", "content_sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "fetch_url": "https://images.example.com/debian-13-x86_64.qcow2", "distro": "debian", "version": "13" }, "disk": { "mode": "copy", "size_gib": 20 }, "nets": [ { "network_id": "default" } ], "cloud_init": { "user": "root", "ssh_authorized_keys": [ "ssh-ed25519 <your-public-ssh-key> demo@example" ] }, "auto_boot": true}JSONResponse example
Section titled “Response example”{ "id": "demo-api-vm", "state": "running", "display_state": "booting", "vcpus": 2, "memory_mb": 2048, "kernel_path": null, "firmware_path": "/usr/share/virtainer/CLOUDHV.fd", "disk_path": "/var/lib/virtainer/vms/demo-api-vm/root.qcow2", "volumes": [], "cmdline": "", "console_socket_path": "/run/virtainer/demo-api-vm/console.sock", "pid": 4242, "nets": [ { "mode": "bridge", "bridge": "vmbr0", "tap_name": "vtdemoapi0", "mac": "02:00:00:12:34:56", "network_id": "default" } ], "pci_devices": [], "disk": { "mode": "copy", "base_path": "/var/lib/virtainer/images/debian-13-x86_64.qcow2", "size_gib": 20 }, "image": { "image_id": "debian-13-x86_64", "distro": "debian", "version": "13" }, "cloud_init": { "user": "root", "ssh_authorized_keys": [ "ssh-ed25519 <your-public-ssh-key> demo@example" ], "console_autologin": false, "sudo": false }, "created_at": "2026-07-03T10:00:00Z", "mode": "vm"}Errors
Section titled “Errors”| Status | Code | Description |
|---|---|---|
400 | BAD_REQUEST | Validation failed, such as invalid ids, missing disk base image, both boot methods present, unsupported disk mode, invalid hostname, or non-null reserved raw cloud-init fields. |
401 | UNAUTHORIZED | API key is missing or invalid. |
403 | FORBIDDEN | Key is read-only or endpoint is outside the key allowlist. |
409 | ALREADY_EXISTS, INVALID_STATE, or capacity conflict | VM id already exists, state changed during an operation, or host admission rejected the requested resources. |
422 | none | JSON body could not be deserialized, for example a required field is missing or has the wrong type. This response is plain text, not the Virtainer JSON error envelope. |
500 | CH_ERROR, NETWORK_ERROR, STORAGE_ERROR, or INTERNAL | Host-side creation, image, disk, network, or Cloud Hypervisor work failed. |