{
  "components": {
    "schemas": {
      "AddressMode": {
        "description": "How an interface's address is configured (`OVN.md` §8).\n\nThe control-plane vocabulary for what `nextvirt-host-types`'\n`AddressMode` describes on the agent side — `nextvirt-core`\ntranslates between them, the same split [`FenceKind`] already uses\nand for the same reason: neither crate depends on the other.",
        "enum": [
          "dhcp",
          "static",
          "manual",
          "loopback",
          "unconfigured"
        ],
        "type": "string"
      },
      "AdoptVmRequest": {
        "description": "Body of `POST /api/v1/vms/actions/adopt` (M2, contract §4.9, architecture\ndecision A11): registers a VM the host agent already runs but this\ncontrol plane does not yet know about, without re-provisioning it.\n\nScoped to the same shape as [`CreateVmRequest`] — a single boot disk from\na registered image, a single nic — because the host agent's own `GET\n/api/v1/vms` reports only lean status (id/name/power), never a full spec;\nanything the agent's status alone cannot corroborate would have to be\nguessed, and this contract does not guess at VM configuration.",
        "properties": {
          "cpus": {
            "description": "Virtual CPUs.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          },
          "datastore_id": {
            "description": "Datastore the orphan's boot disk actually lives on.",
            "format": "uuid",
            "type": "string"
          },
          "host_id": {
            "description": "Host reporting the orphan (see its `host.orphan_vm` event for the id below).",
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "description": "The orphan's own id, exactly as the agent reports it.\n\nThis becomes the new `Vm.id` — it is never rewritten to a freshly\ngenerated one, since the agent's own on-disk state (sockets, disk\npaths) is already keyed by it.",
            "format": "uuid",
            "type": "string"
          },
          "image_id": {
            "description": "Image the orphan's boot disk actually came from; also supplies the boot settings.",
            "format": "uuid",
            "type": "string"
          },
          "memory_bytes": {
            "description": "Guest memory.",
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          },
          "network_id": {
            "description": "Network the orphan's first interface is actually attached to.",
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "host_id",
          "id",
          "name",
          "cpus",
          "memory_bytes",
          "image_id",
          "datastore_id",
          "network_id"
        ],
        "type": "object"
      },
      "ApiKey": {
        "description": "An API key, as listed. Never carries its hash or plaintext.",
        "properties": {
          "created_at": {
            "description": "Creation time.",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "Stable identifier; also the `key_id` recorded as the actor in tasks and events.",
            "format": "uuid",
            "type": "string"
          },
          "last_used_at": {
            "description": "Last successful authentication, if any.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "description": "Operator-chosen label.",
            "type": "string"
          },
          "scopes": {
            "description": "Scope strings granted (§2.4); `\"*\"` grants every capability.",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "id",
          "name",
          "scopes",
          "created_at"
        ],
        "type": "object"
      },
      "ApiKeyList": {
        "description": "Response of `GET /api/v1/api-keys`.",
        "properties": {
          "api_keys": {
            "description": "Keys, ordered by creation time.",
            "items": {
              "$ref": "#/components/schemas/ApiKey"
            },
            "type": "array"
          },
          "count": {
            "description": "Number of keys.",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "api_keys",
          "count"
        ],
        "type": "object"
      },
      "AttachIsoRequest": {
        "description": "Body of `POST /api/v1/vms/{id}/actions/attach-iso` (M2, contract §4.9).\nAn \"ISO\" is just a registered `Image` (§4.8) attached as the optical\ndrive instead of cloned as a disk — no separate resource type.",
        "properties": {
          "image_id": {
            "description": "The image to attach; must live on the same host as the VM.",
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "image_id"
        ],
        "type": "object"
      },
      "AttachNetworkRequest": {
        "description": "Body of `POST /api/v1/routers/{id}/interfaces`.",
        "properties": {
          "gateway": {
            "description": "The router's address on it, in CIDR form (`10.10.1.1/24`).",
            "type": "string"
          },
          "network_id": {
            "description": "The `ovn` network to attach.",
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "network_id",
          "gateway"
        ],
        "type": "object"
      },
      "BackupPolicy": {
        "description": "Desired backup policy for a VM (IDEAS.md \"nfd integration\", phase 1).\n\ndatastore-kind agnostic, unlike [`ReplicationSpec`]: a backup reads a\ndisk wherever it lives and writes chunks to a volume, so no substrate\nconstraint applies. Setting this does not itself run a backup; the\ncontrol plane's backup scheduler picks up VMs whose `interval_seconds`\nhas elapsed since [`VmStatus::last_backed_up_at`].",
        "properties": {
          "interval_seconds": {
            "description": "Minimum seconds between backup runs; enforced with a floor of 60,\nthe same floor [`ReplicationSpec::interval_seconds`] carries —\nanything tighter would schedule against the agent's own poll rate.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          },
          "keep": {
            "description": "How many of this VM's most recent backups to keep; `None` keeps\nthem all. Pruning happens on the agent after each successful run,\nand the reclaimed bytes arrive with the volume's garbage\ncollection.",
            "format": "int32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "interval_seconds"
        ],
        "type": "object"
      },
      "BootSpec": {
        "description": "How the guest is started.",
        "oneOf": [
          {
            "description": "Direct kernel boot: the hypervisor loads the kernel itself.",
            "properties": {
              "cmdline": {
                "description": "Kernel command line.",
                "type": "string"
              },
              "initramfs": {
                "description": "Optional initramfs path relative to the datastore.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "kernel": {
                "description": "Kernel path relative to the boot disk's datastore.",
                "type": "string"
              },
              "kind": {
                "enum": [
                  "kernel"
                ],
                "type": "string"
              }
            },
            "required": [
              "kernel",
              "cmdline",
              "kind"
            ],
            "type": "object"
          },
          {
            "description": "UEFI firmware boot from the boot disk.",
            "properties": {
              "kind": {
                "enum": [
                  "firmware"
                ],
                "type": "string"
              }
            },
            "required": [
              "kind"
            ],
            "type": "object"
          }
        ]
      },
      "Capability": {
        "description": "One authorized action class; every mutating route maps to exactly one.",
        "enum": [
          "read",
          "write_vm",
          "power_vm",
          "console_vm",
          "write_hosts",
          "write_storage",
          "write_network",
          "write_users",
          "write_system",
          "chaos"
        ],
        "type": "string"
      },
      "ChaosFreezeRequest": {
        "description": "Body of `POST /api/v1/hosts/{id}/actions/chaos-freeze` (`CHAOS.md` §6).\nAbsent body — or an absent field — takes the default duration.",
        "properties": {
          "duration_secs": {
            "description": "Override the default freeze duration. Clamped server-side; not\nenforced beyond the pause itself — see `fallback_deadline` on\n[`ChaosFreezeResponse`] for the real, computed safety boundary.",
            "format": "int64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "ChaosFreezeResponse": {
        "description": "Response of `POST /api/v1/hosts/{id}/actions/chaos-freeze` (`CHAOS.md`\n§6) — a bare `{ host }` understates what this action needs to tell the\ncaller.",
        "properties": {
          "fallback_deadline": {
            "description": "`host.last_seen_at + OOB_MIN_OFFLINE` at the moment the freeze was\nissued — the real instant the existing OOB reaper becomes eligible to\nforce-stop this host for real if it is still silent. Freeze duration\nis not enforced (`CHAOS.md` §5), so this, not the requested duration,\nis the actual deadline. Absent only if the host has never been\nsuccessfully polled, which the `Online`/`Maintenance` precondition\nmakes unlikely but not impossible.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "host": {
            "$ref": "#/components/schemas/Host",
            "description": "The host, with `chaos_frozen: true`."
          }
        },
        "required": [
          "host"
        ],
        "type": "object"
      },
      "CloneVmRequest": {
        "description": "Body of `POST /api/v1/vms/{id}/actions/clone` (M2, contract §4.9).\n\nNothing else is settable: the clone inherits the source's cpus, memory,\nboot settings, disks (cloned, not shared), description, tags, and\nautostart flag; each NIC gets a freshly generated MAC so the two VMs\nnever collide on their shared network. It never auto-starts.",
        "properties": {
          "name": {
            "description": "Unique DNS-label-like name for the new VM.",
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "Cluster": {
        "description": "A named grouping of hosts.",
        "properties": {
          "created_at": {
            "description": "Creation time.",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "Stable identifier.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          },
          "updated_at": {
            "description": "Last modification time.",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "ClusterList": {
        "description": "Response of `GET /api/v1/clusters`.",
        "properties": {
          "clusters": {
            "description": "Clusters, ordered by name.",
            "items": {
              "$ref": "#/components/schemas/Cluster"
            },
            "type": "array"
          },
          "count": {
            "description": "Number of clusters.",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "clusters",
          "count"
        ],
        "type": "object"
      },
      "ClusterResponse": {
        "description": "Response wrapping one cluster.",
        "properties": {
          "cluster": {
            "$ref": "#/components/schemas/Cluster",
            "description": "The cluster."
          }
        },
        "required": [
          "cluster"
        ],
        "type": "object"
      },
      "CreateApiKeyRequest": {
        "description": "Body of `POST /api/v1/api-keys`.",
        "properties": {
          "name": {
            "description": "Operator-chosen label.",
            "type": "string"
          },
          "scopes": {
            "description": "Scope strings to grant (§2.4); `\"*\"` allowed.",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "name",
          "scopes"
        ],
        "type": "object"
      },
      "CreateApiKeyResponse": {
        "description": "Response of `POST /api/v1/api-keys`: the record plus its plaintext, shown exactly once.",
        "properties": {
          "api_key": {
            "$ref": "#/components/schemas/ApiKey",
            "description": "The stored key record."
          },
          "secret": {
            "description": "Plaintext key: `\"nvk_\"` followed by 64 hex characters. Never shown again.",
            "type": "string"
          }
        },
        "required": [
          "api_key",
          "secret"
        ],
        "type": "object"
      },
      "CreateClusterRequest": {
        "description": "Body of `POST /api/v1/clusters`.",
        "properties": {
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "CreateDatastoreRequest": {
        "description": "Body of `POST /api/v1/datastores`.",
        "properties": {
          "host_id": {
            "description": "Host the datastore belongs to.",
            "format": "uuid",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/DatastoreKind",
            "description": "Storage backend."
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          },
          "path": {
            "description": "ZFS dataset name or directory path on the host.",
            "type": "string"
          }
        },
        "required": [
          "name",
          "host_id",
          "kind",
          "path"
        ],
        "type": "object"
      },
      "CreateHostRequest": {
        "description": "Body of `POST /api/v1/hosts`.",
        "properties": {
          "address": {
            "description": "`host:port` of the agent's API.",
            "type": "string"
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          }
        },
        "required": [
          "name",
          "address"
        ],
        "type": "object"
      },
      "CreateHostResponse": {
        "description": "Response of `POST /api/v1/hosts`: the host plus its one-time join token.",
        "properties": {
          "host": {
            "$ref": "#/components/schemas/Host",
            "description": "The registered host."
          },
          "join_token": {
            "description": "Bearer token to configure on the agent. Shown exactly once; only its hash is stored.",
            "type": "string"
          }
        },
        "required": [
          "host",
          "join_token"
        ],
        "type": "object"
      },
      "CreateImageRequest": {
        "description": "Body of `POST /api/v1/images`.",
        "properties": {
          "datastore_id": {
            "description": "Datastore holding the image file.",
            "format": "uuid",
            "type": "string"
          },
          "kernel": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/KernelBoot",
                "description": "Boot settings for guests cloned from this image."
              }
            ]
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          },
          "path": {
            "description": "Path of the raw image relative to the datastore.",
            "type": "string"
          },
          "size_bytes": {
            "description": "Size of the raw image.",
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "name",
          "datastore_id",
          "path",
          "size_bytes"
        ],
        "type": "object"
      },
      "CreateNetworkRequest": {
        "description": "Body of `POST /api/v1/networks`.",
        "properties": {
          "bridge": {
            "description": "Bridge interface name on the host.",
            "type": "string"
          },
          "host_id": {
            "description": "Host the network exists on.",
            "format": "uuid",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/NetworkKind",
            "description": "Realization on the host (default `bridge`)."
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          },
          "vlan": {
            "description": "802.1Q VLAN tag, if any.",
            "format": "int32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "name",
          "host_id",
          "bridge"
        ],
        "type": "object"
      },
      "CreateRouterRequest": {
        "description": "Body of `POST /api/v1/routers`.",
        "properties": {
          "cluster_id": {
            "description": "Cluster this router serves, if any.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "CreateSnapshotRequest": {
        "description": "Body of `POST /api/v1/vms/{id}/snapshots`.",
        "properties": {
          "live": {
            "description": "Capture memory/device state too, without stopping the VM first (M3,\narchitecture §5.16/A25). Defaults to `false` (M2's original disk-only\nbehavior, VM must already be stopped) so every existing caller is\nunaffected. Kernel-boot only — a firmware-boot VM answers `422\ninvalid_boot`.",
            "type": "boolean"
          },
          "name": {
            "description": "Unique (per VM) DNS-label-like name.",
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "CreateUserRequest": {
        "description": "Body of `POST /api/v1/users`.",
        "properties": {
          "password": {
            "description": "Password; hashed with argon2id before storage.",
            "type": "string"
          },
          "role": {
            "$ref": "#/components/schemas/Role",
            "description": "Role."
          },
          "username": {
            "description": "Unique, DNS-label-like login name.",
            "type": "string"
          }
        },
        "required": [
          "username",
          "password",
          "role"
        ],
        "type": "object"
      },
      "CreateVmRequest": {
        "description": "Body of `POST /api/v1/vms`.",
        "properties": {
          "anti_affinity_group": {
            "description": "Soft anti-affinity group (M3); see [`VmSpec::anti_affinity_group`].\n\nUnlike `description`/`tags`/`autostart`/`replication` — all PATCH-only,\nset afterward — this must be settable here: the scheduler consults it\nexactly once, synchronously, inside `Core::create_vm`, before any\nlater `PATCH` could ever run.",
            "type": [
              "string",
              "null"
            ]
          },
          "cpus": {
            "description": "Virtual CPUs.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          },
          "datastore_id": {
            "description": "Datastore for the boot disk.",
            "format": "uuid",
            "type": "string"
          },
          "disk_size_bytes": {
            "description": "Boot disk size; defaults to the image size.",
            "format": "int64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "host_id": {
            "description": "Pin to a host; the scheduler chooses when absent.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "image_id": {
            "description": "Image to clone the boot disk from; also supplies the boot settings.",
            "format": "uuid",
            "type": "string"
          },
          "memory_bytes": {
            "description": "Guest memory.",
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          },
          "network_id": {
            "description": "Network for the first interface.",
            "format": "uuid",
            "type": "string"
          },
          "start": {
            "description": "Start the VM once created (default `true`).",
            "type": "boolean"
          }
        },
        "required": [
          "name",
          "cpus",
          "memory_bytes",
          "image_id",
          "datastore_id",
          "network_id"
        ],
        "type": "object"
      },
      "CsrfResponse": {
        "description": "Response of `POST /api/v1/auth/csrf`.",
        "properties": {
          "csrf_token": {
            "description": "Fresh CSRF token.",
            "type": "string"
          }
        },
        "required": [
          "csrf_token"
        ],
        "type": "object"
      },
      "Datastore": {
        "description": "A datastore on a host.",
        "properties": {
          "available_bytes": {
            "description": "Free space from the last poll.",
            "format": "int64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "capacity_bytes": {
            "description": "Total capacity from the last poll.",
            "format": "int64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "cluster_id": {
            "description": "Cluster this datastore is shared into, if any (M3, second slice) —\nonce set, every host in that cluster may use it, not just `host_id`.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "description": "Creation time.",
            "format": "date-time",
            "type": "string"
          },
          "host_id": {
            "description": "Host the datastore belongs to.",
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "description": "Stable identifier.",
            "format": "uuid",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/DatastoreKind",
            "description": "Storage backend."
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          },
          "path": {
            "description": "ZFS dataset name (`tank/nextvirt`) or directory path (`/var/lib/…`).",
            "type": "string"
          },
          "updated_at": {
            "description": "Last modification time.",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "host_id",
          "kind",
          "path",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "DatastoreKind": {
        "description": "Storage backend of a datastore.",
        "enum": [
          "zfs",
          "file",
          "nfs",
          "iscsi",
          "volume"
        ],
        "type": "string"
      },
      "DatastoreList": {
        "description": "Response of `GET /api/v1/datastores`.",
        "properties": {
          "count": {
            "description": "Number of datastores.",
            "minimum": 0,
            "type": "integer"
          },
          "datastores": {
            "description": "Datastores, ordered by name.",
            "items": {
              "$ref": "#/components/schemas/Datastore"
            },
            "type": "array"
          }
        },
        "required": [
          "datastores",
          "count"
        ],
        "type": "object"
      },
      "DatastoreResponse": {
        "description": "Response wrapping one datastore.",
        "properties": {
          "datastore": {
            "$ref": "#/components/schemas/Datastore",
            "description": "The datastore."
          }
        },
        "required": [
          "datastore"
        ],
        "type": "object"
      },
      "DiskSpec": {
        "description": "One virtual disk.",
        "properties": {
          "boot": {
            "description": "Whether this is the boot disk.",
            "type": "boolean"
          },
          "datastore_id": {
            "description": "Datastore holding the disk.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "description": "Disk name, unique within the VM.",
            "type": "string"
          },
          "size_bytes": {
            "description": "Provisioned size.",
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          },
          "source_image_id": {
            "description": "Image the disk was cloned from, if any.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "name",
          "datastore_id",
          "size_bytes",
          "boot"
        ],
        "type": "object"
      },
      "Event": {
        "description": "One audit entry.",
        "properties": {
          "actor": {
            "description": "Principal responsible (username, API-key id, or `system`).",
            "type": "string"
          },
          "at": {
            "description": "When it happened.",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "Stable identifier.",
            "format": "uuid",
            "type": "string"
          },
          "kind": {
            "description": "Dotted event name (`vm.created`, `host.offline`, `auth.login`).",
            "type": "string"
          },
          "message": {
            "description": "Human-readable summary.",
            "type": "string"
          },
          "target_id": {
            "description": "The resource involved, when it exists.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "target_type": {
            "$ref": "#/components/schemas/TargetType",
            "description": "Kind of the resource involved."
          }
        },
        "required": [
          "id",
          "at",
          "kind",
          "actor",
          "target_type",
          "message"
        ],
        "type": "object"
      },
      "EventList": {
        "description": "Response of `GET /api/v1/events`.",
        "properties": {
          "count": {
            "description": "Number of events in this page.",
            "minimum": 0,
            "type": "integer"
          },
          "events": {
            "description": "Events, newest first.",
            "items": {
              "$ref": "#/components/schemas/Event"
            },
            "type": "array"
          },
          "next_cursor": {
            "description": "Opaque cursor for the next page (decision C7); absent once exhausted.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "events",
          "count"
        ],
        "type": "object"
      },
      "ExpectRestartRequest": {
        "description": "Body of `POST /api/v1/hosts/{id}/actions/expect-restart` (`HA-RESTART.md`\n§11.5). Absent body — or an absent field — takes the default grace\nwindow.",
        "properties": {
          "expected_within_secs": {
            "description": "Override the default grace window. Clamped server-side so neither a\ntoo-small value (the reaper could never evaluate it even once) nor a\ntoo-large one (a forgotten declaration would leave a lasting blind\nspot) can defeat the mechanism's own purpose.",
            "format": "int64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "Features": {
        "description": "What this build can do, for UI gating.",
        "properties": {
          "console": {
            "description": "VM serial consoles.",
            "type": "boolean"
          },
          "continuous_rebalancing": {
            "description": "Continuous rebalancing of hot hosts within a cluster (M3, architecture §5.5.1).",
            "type": "boolean"
          },
          "image_upload": {
            "description": "Uploading or importing images (M2).",
            "type": "boolean"
          },
          "live_migration": {
            "description": "Live migration between hosts (M3).",
            "type": "boolean"
          },
          "live_snapshots": {
            "description": "Live (memory+disk) VM snapshots, without stopping the VM (M3).",
            "type": "boolean"
          },
          "orchestrated_upgrades": {
            "description": "Orchestrated per-host agent upgrades (M3, architecture §5.10).",
            "type": "boolean"
          },
          "snapshots": {
            "description": "VM snapshots (M2).",
            "type": "boolean"
          },
          "vnc": {
            "description": "VM VNC (graphical) consoles, firmware-boot VMs only.",
            "type": "boolean"
          },
          "zfs": {
            "description": "ZFS datastores.",
            "type": "boolean"
          }
        },
        "required": [
          "console",
          "vnc",
          "zfs",
          "snapshots",
          "live_migration",
          "live_snapshots",
          "image_upload",
          "continuous_rebalancing",
          "orchestrated_upgrades"
        ],
        "type": "object"
      },
      "FeaturesResponse": {
        "description": "Response of `GET /api/v1/system/features`.",
        "properties": {
          "features": {
            "$ref": "#/components/schemas/Features",
            "description": "What this build can do."
          }
        },
        "required": [
          "features"
        ],
        "type": "object"
      },
      "FenceKind": {
        "description": "How a host can be forced into a state where its VMs are provably stopped\n(M3, `HA-RESTART.md`).\n\nDeliberately a kind rather than a boolean: out-of-band power fencing is the\nplanned second (`HA-RESTART.md` §8, slice B), and recording *which* fence a\nhost proved is cheap now and expensive to retrofit.",
        "enum": [
          "watchdog"
        ],
        "type": "string"
      },
      "HealthResponse": {
        "description": "Response of `GET /health` — unauthenticated liveness probe.",
        "properties": {
          "status": {
            "$ref": "#/components/schemas/HealthStatus",
            "description": "Coarse health."
          },
          "version": {
            "description": "Control-plane version.",
            "type": "string"
          }
        },
        "required": [
          "status",
          "version"
        ],
        "type": "object"
      },
      "HealthStatus": {
        "description": "Coarse health of the control plane.",
        "enum": [
          "ok",
          "degraded"
        ],
        "type": "string"
      },
      "Host": {
        "description": "A registered hypervisor host.",
        "properties": {
          "address": {
            "description": "`host:port` of the agent's API.",
            "type": "string"
          },
          "agent_version": {
            "description": "Agent version from the last successful poll.",
            "type": [
              "string",
              "null"
            ]
          },
          "capacity": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/HostCapacity",
                "description": "Resources from the last successful poll."
              }
            ]
          },
          "chaos_frozen": {
            "description": "Whether this host is currently believed to be paused by a\nchaos-freeze experiment (`CHAOS.md` §6). Always shown, never omitted\n(unlike `restart_expected_until`): this flag exists specifically to\nbe seen, and guards `POST /api/v1/hosts/{id}/actions/arm-fence`\nwhile `true`.",
            "type": "boolean"
          },
          "cluster_id": {
            "description": "Cluster this host belongs to, if any (M3's first slice — pure grouping).",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "description": "Creation time.",
            "format": "date-time",
            "type": "string"
          },
          "fencing": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/HostFencing",
                "description": "Fencing this host reported on its last successful poll (M3,\n`HA-RESTART.md`).\n\nSticky, like [`Host::capacity`]: a failed poll keeps the last known\nvalue, which is the point — HA restart needs to know what the fence\nlooked like precisely when the host has stopped answering. `None` means\nno usable fence was ever reported, and such a host must never have its\nVMs restarted elsewhere, because nothing can prove they stopped."
              }
            ]
          },
          "id": {
            "description": "Stable identifier.",
            "format": "uuid",
            "type": "string"
          },
          "interfaces": {
            "description": "This host's network interfaces, as reported on the last successful\npoll (`OVN.md` §8).\n\nSticky, like [`Host::fencing`]: written on every successful poll, kept\non a failed one — a host that has gone quiet is exactly when an\noperator most wants to know what its interfaces looked like.",
            "items": {
              "$ref": "#/components/schemas/HostInterface"
            },
            "type": "array"
          },
          "last_poll_credential": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PollCredential",
                "description": "Which credential the last successful poll actually used, when this\nhost has ever been polled since the column existed."
              }
            ]
          },
          "last_seen_at": {
            "description": "When the agent last answered a poll.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "maintenance": {
            "description": "Whether an operator has placed the host in maintenance.",
            "type": "boolean"
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          },
          "require_mtls": {
            "description": "Whether the control plane presents a client certificate to this host\ninstead of the bearer token (`MTLS.md` §5, slice 4 — the cutover).\n\nPer host rather than fleet-wide (`MTLS.md` §7): the same shape every\nother risky switch in this schema already takes — `maintenance`,\n`ha` — a single decisive flip has a much worse failure mode than a\nfleet left in a mixed state while it rolls out.",
            "type": "boolean"
          },
          "restart_expected_until": {
            "description": "When an operator's declaration that this host is about to restart\nlapses, if one is currently active (`HA-RESTART.md` §11.5). While\n`Utc::now()` is before this, the HA reaper's out-of-band branch does\nnot re-`force_off` this host even though it otherwise qualifies —\nsee `POST /api/v1/hosts/{id}/actions/expect-restart`.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "state": {
            "$ref": "#/components/schemas/HostState",
            "description": "Derived liveness state."
          },
          "tls_port": {
            "description": "The agent's mTLS listener port, as last reported on a successful poll\n(`MTLS.md` slice 2).\n\n`None` until the first poll after enrollment — [`Self::require_mtls`]\ncannot actually take effect before this is `Some(_)`, since it is the\nonly place the control plane ever learns the port; the plain address\nin [`Self::address`] never carries it.",
            "format": "int32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "updated_at": {
            "description": "Last modification time.",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "address",
          "state",
          "maintenance",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "HostCapacity": {
        "description": "Resources reported by the host agent on its last successful poll.",
        "properties": {
          "cpus": {
            "description": "Logical CPUs.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          },
          "memory_available_bytes": {
            "description": "Memory the kernel reports as available.",
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          },
          "memory_bytes": {
            "description": "Total physical memory.",
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          },
          "vm_count": {
            "description": "VMs the agent currently manages.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "cpus",
          "memory_bytes",
          "memory_available_bytes",
          "vm_count"
        ],
        "type": "object"
      },
      "HostFencing": {
        "description": "A host's fencing, as of its last successful poll.\n\nThe control-plane vocabulary for what `nextvirt-host-types`' `FencingInfo`\ndescribes on the agent side; `nextvirt-core` translates between them, since\nneither crate depends on the other.",
        "properties": {
          "armed": {
            "description": "Whether the fence is currently running.\n\nOnly an armed fence turns silence into proof. An unarmed one is a\ncapability, not a guarantee.",
            "type": "boolean"
          },
          "identity": {
            "description": "The driver's self-reported name, e.g. `i6300ESB timer`. Operator-facing\nonly; nothing branches on it.",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/FenceKind",
            "description": "Which mechanism this is."
          },
          "last_boot_was_fence": {
            "description": "Whether the host's last boot was caused by its own fence firing.\n\nPositive confirmation that a fence worked, rather than the control plane\ninferring it from how long the host was quiet.",
            "type": "boolean"
          },
          "timeout_secs": {
            "description": "Seconds this host survives without being polled, once armed.\n\nThe floor on how long HA restart must wait before it may treat silence\nas proof, and therefore the floor on recovery time.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "kind",
          "identity",
          "timeout_secs",
          "armed",
          "last_boot_was_fence"
        ],
        "type": "object"
      },
      "HostInterface": {
        "description": "One of a host's network interfaces, as last reported on a poll\n(`OVN.md` §8).",
        "properties": {
          "addresses": {
            "description": "Live addresses in CIDR form, e.g. `10.0.250.121/24`.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "gateway": {
            "description": "Default gateway configured for this interface, when it has one.",
            "type": [
              "string",
              "null"
            ]
          },
          "mode": {
            "$ref": "#/components/schemas/AddressMode",
            "description": "How its address is configured, from the persistent configuration."
          },
          "name": {
            "description": "Kernel name, e.g. `eth0` or `br0`.",
            "type": "string"
          }
        },
        "required": [
          "name",
          "mode"
        ],
        "type": "object"
      },
      "HostInterfaceChangeResponse": {
        "description": "Response of `PUT /api/v1/hosts/{id}/interfaces/{name}`.\n\n**The change is not confirmed by this response.** It reverts on its own\nunless this control plane reaches the host again before\n[`Self::rollback_at_unix`] — normally on its own, at the next successful\npoll, or immediately via `POST /api/v1/hosts/{id}/interfaces/confirm`.\nDeliberately carries no token: the token this exchange runs on is an\ninternal correlation between the control plane and the agent, never\nhanded to a caller who has no way to use it directly (confirmation is\ntokenless from the operator's side by design — see that endpoint's own\ndoc).",
        "properties": {
          "expected_addresses": {
            "description": "Addresses the interface is expected to have once applied.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "rollback_at_unix": {
            "description": "Unix seconds after which the agent restores the previous\nconfiguration on its own, if nothing has confirmed the change by then.",
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "rollback_at_unix"
        ],
        "type": "object"
      },
      "HostList": {
        "description": "Response of `GET /api/v1/hosts`.",
        "properties": {
          "count": {
            "description": "Number of hosts.",
            "minimum": 0,
            "type": "integer"
          },
          "hosts": {
            "description": "Hosts, ordered by name.",
            "items": {
              "$ref": "#/components/schemas/Host"
            },
            "type": "array"
          }
        },
        "required": [
          "hosts",
          "count"
        ],
        "type": "object"
      },
      "HostResponse": {
        "description": "Response wrapping one host.",
        "properties": {
          "host": {
            "$ref": "#/components/schemas/Host",
            "description": "The host."
          }
        },
        "required": [
          "host"
        ],
        "type": "object"
      },
      "HostState": {
        "description": "Liveness and scheduling state of a host.",
        "enum": [
          "pending",
          "online",
          "degraded",
          "offline",
          "maintenance"
        ],
        "type": "string"
      },
      "Image": {
        "description": "A registered disk image.",
        "properties": {
          "created_at": {
            "description": "Creation time.",
            "format": "date-time",
            "type": "string"
          },
          "datastore_id": {
            "description": "Datastore holding the image file.",
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "description": "Stable identifier.",
            "format": "uuid",
            "type": "string"
          },
          "kernel": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/KernelBoot",
                "description": "Boot settings for guests cloned from this image; `None` means firmware boot."
              }
            ]
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          },
          "path": {
            "description": "Path of the raw image relative to the datastore.",
            "type": "string"
          },
          "size_bytes": {
            "description": "Size of the raw image.",
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          },
          "updated_at": {
            "description": "Last modification time.",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "datastore_id",
          "path",
          "size_bytes",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "ImageList": {
        "description": "Response of `GET /api/v1/images`.",
        "properties": {
          "count": {
            "description": "Number of images.",
            "minimum": 0,
            "type": "integer"
          },
          "images": {
            "description": "Images, ordered by name.",
            "items": {
              "$ref": "#/components/schemas/Image"
            },
            "type": "array"
          }
        },
        "required": [
          "images",
          "count"
        ],
        "type": "object"
      },
      "ImageResponse": {
        "description": "Response wrapping one image.",
        "properties": {
          "image": {
            "$ref": "#/components/schemas/Image",
            "description": "The image."
          }
        },
        "required": [
          "image"
        ],
        "type": "object"
      },
      "ImportImageAccepted": {
        "description": "Response of `POST /api/v1/images/import`.",
        "properties": {
          "image": {
            "$ref": "#/components/schemas/Image",
            "description": "The image, `size_bytes` still `0` until the `image_import` task finishes."
          },
          "task": {
            "$ref": "#/components/schemas/Task",
            "description": "The `image_import` task tracking the download."
          }
        },
        "required": [
          "image",
          "task"
        ],
        "type": "object"
      },
      "ImportImageRequest": {
        "description": "Body of `POST /api/v1/images/import` (M2): the agent downloads `url`\ninto `datastore_id` as a new image, verifying `sha256` when given.",
        "properties": {
          "datastore_id": {
            "description": "Datastore to download into.",
            "format": "uuid",
            "type": "string"
          },
          "kernel": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/KernelBoot",
                "description": "Boot settings for guests cloned from this image."
              }
            ]
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          },
          "sha256": {
            "description": "Expected SHA-256 of the downloaded file, hex-encoded; verified once\nthe download completes, before the image is usable.",
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "description": "Source URL; the agent fetches it directly (not routed through the\ncontrol plane, matching the host-to-host philosophy elsewhere in this\ndesign — the control plane would otherwise be a bandwidth bottleneck\nfor something with no need to pass through it).",
            "type": "string"
          }
        },
        "required": [
          "name",
          "datastore_id",
          "url"
        ],
        "type": "object"
      },
      "KernelBoot": {
        "description": "Direct-kernel-boot settings for guests cloned from an image.",
        "properties": {
          "cmdline": {
            "description": "Kernel command line.",
            "type": "string"
          },
          "initramfs": {
            "description": "Optional initramfs path relative to the datastore.",
            "type": [
              "string",
              "null"
            ]
          },
          "kernel": {
            "description": "Kernel path relative to the image's datastore.",
            "type": "string"
          }
        },
        "required": [
          "kernel",
          "cmdline"
        ],
        "type": "object"
      },
      "LoginRequest": {
        "description": "Body of `POST /api/v1/auth/login`.",
        "properties": {
          "password": {
            "description": "Password.",
            "type": "string"
          },
          "remember_me": {
            "description": "Requests a 7-day idle timeout instead of the default 30 minutes (M2).",
            "type": [
              "boolean",
              "null"
            ]
          },
          "username": {
            "description": "Login name.",
            "type": "string"
          }
        },
        "required": [
          "username",
          "password"
        ],
        "type": "object"
      },
      "LoginResponse": {
        "description": "Response of a successful login.",
        "properties": {
          "csrf_token": {
            "description": "Fresh CSRF token for subsequent state-changing requests.",
            "type": "string"
          },
          "user": {
            "$ref": "#/components/schemas/UserInfo",
            "description": "The authenticated user."
          }
        },
        "required": [
          "user",
          "csrf_token"
        ],
        "type": "object"
      },
      "MetricSample": {
        "description": "One CPU/memory reading at a point in time.",
        "properties": {
          "cpu_percent": {
            "description": "See `nextvirt_host_types::ResourceUsage::cpu_percent`'s doc for range\nand meaning — a host or VM's own live measurement, carried through\nunchanged.",
            "format": "double",
            "type": "number"
          },
          "memory_bytes": {
            "description": "See `nextvirt_host_types::ResourceUsage::memory_bytes`.",
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          },
          "sampled_at": {
            "description": "When this reading was taken.",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "sampled_at",
          "cpu_percent",
          "memory_bytes"
        ],
        "type": "object"
      },
      "MetricsResponse": {
        "description": "Response of `GET /api/v1/hosts/{id}/metrics` and `GET\n/api/v1/vms/{id}/metrics`, oldest first.",
        "properties": {
          "samples": {
            "description": "Samples within the requested range, oldest first.",
            "items": {
              "$ref": "#/components/schemas/MetricSample"
            },
            "type": "array"
          }
        },
        "required": [
          "samples"
        ],
        "type": "object"
      },
      "MigrateVmRequest": {
        "description": "Body of `POST /api/v1/vms/{id}/actions/migrate` (M3). Kernel-boot only — a firmware-boot VM's\nmigrate action answers `422 invalid_boot`, not this endpoint's other, unrelated `501` reason\n(cloud-substrate, M5, not yet built).",
        "properties": {
          "host_id": {
            "description": "The host to move the VM to; must already be able to reach every disk's datastore and every\nNIC's network the VM references (`nextvirt_core::eligible_hosts`).",
            "format": "uuid",
            "type": "string"
          },
          "live": {
            "description": "`true` moves a running VM via Cloud Hypervisor's live-migration protocol, keeping it running\nthroughout except a brief final cutover; requires the VM to already be `running`. `false`\nstops it first (if running), moves it, and restarts it at the target if it was running before —\naccepting a brief downtime in exchange for not needing the live protocol at all.",
            "type": "boolean"
          }
        },
        "required": [
          "host_id",
          "live"
        ],
        "type": "object"
      },
      "Network": {
        "description": "A virtual network on a host.",
        "properties": {
          "bridge": {
            "description": "Bridge interface name on the host (`br0`).",
            "type": "string"
          },
          "cluster_id": {
            "description": "Cluster this network is shared into, if any (M3, second slice) —\nonce set, every host in that cluster is expected to already have a\nlike-named bridge, and may use it, not just `host_id`.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "description": "Creation time.",
            "format": "date-time",
            "type": "string"
          },
          "host_id": {
            "description": "Host the network exists on.",
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "description": "Stable identifier.",
            "format": "uuid",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/NetworkKind",
            "description": "Realization on the host."
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          },
          "updated_at": {
            "description": "Last modification time.",
            "format": "date-time",
            "type": "string"
          },
          "vlan": {
            "description": "802.1Q VLAN tag applied to VM traffic, if any.",
            "format": "int32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "host_id",
          "kind",
          "bridge",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "NetworkKind": {
        "description": "How a network is realized on the host.",
        "enum": [
          "bridge",
          "ovn"
        ],
        "type": "string"
      },
      "NetworkList": {
        "description": "Response of `GET /api/v1/networks`.",
        "properties": {
          "count": {
            "description": "Number of networks.",
            "minimum": 0,
            "type": "integer"
          },
          "networks": {
            "description": "Networks, ordered by name.",
            "items": {
              "$ref": "#/components/schemas/Network"
            },
            "type": "array"
          }
        },
        "required": [
          "networks",
          "count"
        ],
        "type": "object"
      },
      "NetworkResponse": {
        "description": "Response wrapping one network.",
        "properties": {
          "network": {
            "$ref": "#/components/schemas/Network",
            "description": "The network."
          }
        },
        "required": [
          "network"
        ],
        "type": "object"
      },
      "NicSpec": {
        "description": "One virtual network interface.",
        "properties": {
          "mac": {
            "description": "MAC address; generated by the control plane when absent.",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "description": "Interface name, unique within the VM.",
            "type": "string"
          },
          "network_id": {
            "description": "Network the interface attaches to.",
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "name",
          "network_id"
        ],
        "type": "object"
      },
      "OobFenceResponse": {
        "description": "Response of `GET /api/v1/hosts/{id}/oob-fence` — everything except the\nsecret (`HA-RESTART.md` §11.8's \"shown once\" discipline, same as\n[`CreateHostResponse`]'s join token).",
        "properties": {
          "accept_invalid_certs": {
            "description": "Whether TLS certificate verification is skipped for this target.",
            "type": "boolean"
          },
          "base_url": {
            "description": "The Proxmox node's own API base.",
            "type": "string"
          },
          "created_at": {
            "description": "When this configuration was first set.",
            "format": "date-time",
            "type": "string"
          },
          "node": {
            "description": "The Proxmox node name the VMID lives on.",
            "type": "string"
          },
          "token_id": {
            "description": "Full token id — not a secret by itself, and useful for an operator\nconfirming which credential is configured.",
            "type": "string"
          },
          "updated_at": {
            "description": "When this configuration was last replaced.",
            "format": "date-time",
            "type": "string"
          },
          "vmid": {
            "description": "The VMID this fence targets.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "base_url",
          "node",
          "vmid",
          "token_id",
          "accept_invalid_certs",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "PollCredential": {
        "description": "Which credential the *last successful* poll actually used\n(`MTLS.md` §4.5) — observable so a fleet cannot believe it is running\non mTLS while a fallback has quietly kept it on the token, which is\nworse than never having enabled the cutover at all.",
        "enum": [
          "token",
          "mtls"
        ],
        "type": "string"
      },
      "PutHostInterfaceRequest": {
        "description": "Body of `PUT /api/v1/hosts/{id}/interfaces/{name}` (`OVN.md` §8).",
        "properties": {
          "address": {
            "description": "Address in CIDR form, e.g. `10.0.250.50/24`. Required for `static`,\nrejected for `dhcp`.",
            "type": [
              "string",
              "null"
            ]
          },
          "confirm_within_secs": {
            "description": "Seconds to wait for this control plane to reach the host again on the\nnew configuration before the agent reverts it on its own — clamped by\nthe agent to 30-600, defaulting to 120. Left unset to take the\nagent's own default.",
            "format": "int64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "gateway": {
            "description": "Default gateway. Optional even for `static`.",
            "type": [
              "string",
              "null"
            ]
          },
          "mode": {
            "$ref": "#/components/schemas/AddressMode",
            "description": "Mode to switch to. Only `dhcp` and `static` are settable; `manual`,\n`loopback`, and `unconfigured` describe an interface rather than\nconfigure one — the agent refuses those the same way it refuses them\ntoday."
          }
        },
        "required": [
          "mode"
        ],
        "type": "object"
      },
      "PutOobFenceRequest": {
        "description": "Body of `PUT /api/v1/hosts/{id}/oob-fence` (`HA-RESTART.md` §11.8).\n\nDeliberately not part of [`UpdateHostRequest`]: unlike [`FenceKind`]\nabove, out-of-band fencing is never agent-reported, so it is configured\nthrough its own sub-resource rather than folded into the general host\nPATCH — the same reason mTLS enrollment (`MTLS.md`) is its own action\nrather than a field on that request.",
        "properties": {
          "accept_invalid_certs": {
            "description": "Skip TLS certificate verification against `base_url`. Off by\ndefault: Proxmox's own default installation is self-signed\n(`HA-RESTART.md` §11.6), so an operator sets this explicitly rather\nthan inheriting the trade silently.",
            "type": "boolean"
          },
          "base_url": {
            "description": "The Proxmox node's own API base, e.g. `https://10.0.250.161:8006`.\nNo trailing slash.",
            "type": "string"
          },
          "node": {
            "description": "The Proxmox node name the VMID lives on — distinct from any nextVIRT\nhost name or address; Proxmox addresses VMs by `(node, vmid)`, not by\nthe VM's own network address.",
            "type": "string"
          },
          "token_id": {
            "description": "Full token id, `user@realm!tokenid`.",
            "type": "string"
          },
          "token_secret": {
            "description": "The token's own secret. Accepted here, never returned by any\nendpoint afterward — the same \"shown once\" discipline as an API\nkey's secret or the host join token.",
            "type": "string"
          },
          "vmid": {
            "description": "The VMID this fence targets.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "base_url",
          "node",
          "vmid",
          "token_id",
          "token_secret"
        ],
        "type": "object"
      },
      "ReplicationSpec": {
        "description": "Desired asynchronous DR replication policy for a VM (M4, contract §4.9,\narchitecture decision A16).\n\nHost-substrate only: every one of the VM's disks and `target_datastore_id`\nitself must resolve to a `zfs`-kind datastore — enforced by\n`nextvirt-core::vms::update_vm` when this is set, not by this type.\nSetting it does not itself start a transfer; the control plane's\nreplication scheduler picks up VMs whose `interval_seconds` has elapsed\nsince [`VmStatus::last_replicated_at`].",
        "properties": {
          "interval_seconds": {
            "description": "Minimum seconds between replication cycles; enforced with a floor of 60.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          },
          "target_datastore_id": {
            "description": "Datastore on `target_host_id` that receives the replica's disks; must be `zfs`-kind.",
            "format": "uuid",
            "type": "string"
          },
          "target_host_id": {
            "description": "Host that receives the replica.",
            "format": "uuid",
            "type": "string"
          },
          "target_network_id": {
            "description": "Network on `target_host_id` the replica's interface attaches to once\npromoted — the source VM's own `spec.nics[].network_id` is never\nreusable here, since a `Network` row is scoped to the host it lives\non (the same reason [`CreateVmRequest::network_id`] exists at all).",
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "target_host_id",
          "target_datastore_id",
          "target_network_id",
          "interval_seconds"
        ],
        "type": "object"
      },
      "Role": {
        "description": "Coarse role assigned to a user account.",
        "enum": [
          "viewer",
          "operator",
          "admin"
        ],
        "type": "string"
      },
      "Router": {
        "description": "A logical router.",
        "properties": {
          "cluster_id": {
            "description": "Cluster this router serves, if any.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "description": "Creation time.",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "Stable identifier.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          },
          "updated_at": {
            "description": "Last modification time.",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "RouterInterface": {
        "description": "One router interface: the router's presence on a single network.\n\nHas no id of its own. An interface is part of its router rather than an\nindependent resource — it is addressed by the pair it joins, and deleting\nthe router takes it along.",
        "properties": {
          "created_at": {
            "description": "Attachment time.",
            "format": "date-time",
            "type": "string"
          },
          "gateway": {
            "description": "The router's **own address** on that network, in CIDR form\n(`10.10.1.1/24`) — what guests use as their default gateway, not the\nsubnet. The prefix length is what tells OVN the subnet, which is why\na bare address is refused.",
            "type": "string"
          },
          "mac": {
            "description": "MAC the router answers with on this network, generated at attach time.",
            "type": "string"
          },
          "network_id": {
            "description": "Network it faces. An `ovn` network, and at most one router may attach\nto it.",
            "format": "uuid",
            "type": "string"
          },
          "router_id": {
            "description": "Router this interface belongs to.",
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "router_id",
          "network_id",
          "gateway",
          "mac",
          "created_at"
        ],
        "type": "object"
      },
      "RouterInterfaceList": {
        "description": "Response of `GET /api/v1/routers/{id}/interfaces`.",
        "properties": {
          "count": {
            "description": "Number of interfaces.",
            "minimum": 0,
            "type": "integer"
          },
          "interfaces": {
            "description": "Interfaces, oldest first.",
            "items": {
              "$ref": "#/components/schemas/RouterInterface"
            },
            "type": "array"
          }
        },
        "required": [
          "interfaces",
          "count"
        ],
        "type": "object"
      },
      "RouterInterfaceResponse": {
        "description": "Response of `POST /api/v1/routers/{id}/interfaces`.",
        "properties": {
          "interface": {
            "$ref": "#/components/schemas/RouterInterface",
            "description": "The interface just attached."
          }
        },
        "required": [
          "interface"
        ],
        "type": "object"
      },
      "RouterList": {
        "description": "Response of `GET /api/v1/routers`.",
        "properties": {
          "count": {
            "description": "Number of routers.",
            "minimum": 0,
            "type": "integer"
          },
          "routers": {
            "description": "Routers, ordered by name.",
            "items": {
              "$ref": "#/components/schemas/Router"
            },
            "type": "array"
          }
        },
        "required": [
          "routers",
          "count"
        ],
        "type": "object"
      },
      "RouterResponse": {
        "description": "Response of `POST`/`GET`/`PATCH` on a single router.",
        "properties": {
          "router": {
            "$ref": "#/components/schemas/Router",
            "description": "The router."
          }
        },
        "required": [
          "router"
        ],
        "type": "object"
      },
      "SetPasswordRequest": {
        "description": "Body of `POST /api/v1/users/{id}/actions/set-password`.",
        "properties": {
          "password": {
            "description": "New password; hashed with argon2id before storage.",
            "type": "string"
          }
        },
        "required": [
          "password"
        ],
        "type": "object"
      },
      "SetupRequest": {
        "description": "Body of `POST /api/v1/setup`; only accepted while [`SetupStatus::needs_setup`].",
        "properties": {
          "password": {
            "description": "Password, at least 12 characters.",
            "type": "string"
          },
          "username": {
            "description": "Login name of the first administrator.",
            "type": "string"
          }
        },
        "required": [
          "username",
          "password"
        ],
        "type": "object"
      },
      "SetupStatus": {
        "description": "Response of `GET /api/v1/setup/status`.",
        "properties": {
          "needs_setup": {
            "description": "Whether no administrator exists yet and `POST /api/v1/setup` is open.",
            "type": "boolean"
          }
        },
        "required": [
          "needs_setup"
        ],
        "type": "object"
      },
      "Snapshot": {
        "description": "One point-in-time snapshot of a VM's disks.",
        "properties": {
          "created_at": {
            "description": "When the snapshot was taken.",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "Stable identifier.",
            "format": "uuid",
            "type": "string"
          },
          "live": {
            "description": "Whether this also captured memory/device state (M3, architecture\n§5.16/A25), taken without stopping the VM first — as opposed to a\ndisk-only snapshot (M2), which requires the VM already stopped.\nRecorded at capture time since nothing else distinguishes the two\nonce captured, and reverting to one needs to know which kind it is.",
            "type": "boolean"
          },
          "name": {
            "description": "Unique (per VM) DNS-label-like name.",
            "type": "string"
          },
          "vm_id": {
            "description": "The VM this snapshot was taken of.",
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "vm_id",
          "name",
          "created_at"
        ],
        "type": "object"
      },
      "SnapshotAccepted": {
        "description": "Response of `POST /api/v1/vms/{id}/snapshots`.",
        "properties": {
          "snapshot": {
            "$ref": "#/components/schemas/Snapshot",
            "description": "The snapshot; the underlying disk copy exists once the task succeeds."
          },
          "task": {
            "$ref": "#/components/schemas/Task",
            "description": "The `vm_snapshot` task tracking it."
          }
        },
        "required": [
          "snapshot",
          "task"
        ],
        "type": "object"
      },
      "SnapshotList": {
        "description": "Response of `GET /api/v1/vms/{id}/snapshots`.",
        "properties": {
          "count": {
            "description": "Number of snapshots.",
            "minimum": 0,
            "type": "integer"
          },
          "snapshots": {
            "description": "Snapshots, newest first.",
            "items": {
              "$ref": "#/components/schemas/Snapshot"
            },
            "type": "array"
          }
        },
        "required": [
          "snapshots",
          "count"
        ],
        "type": "object"
      },
      "StopMode": {
        "description": "How a stop action asks the guest to power off.",
        "enum": [
          "graceful",
          "hard"
        ],
        "type": "string"
      },
      "StopRequest": {
        "description": "Body of `POST /api/v1/vms/{id}/actions/stop`.",
        "properties": {
          "mode": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/StopMode",
                "description": "How to stop the guest; defaults to `graceful` (M1 always stops hard — see [`StopMode`])."
              }
            ]
          },
          "timeout_seconds": {
            "description": "Seconds to wait for a graceful stop before forcing it off (M2; ignored in M1).",
            "format": "int32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "SystemInfo": {
        "description": "Response of `GET /api/v1/system/info`.",
        "properties": {
          "hostname": {
            "description": "Hostname of the machine running the control plane.",
            "type": "string"
          },
          "hosts": {
            "description": "Registered hosts.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          },
          "hosts_online": {
            "description": "Hosts currently online.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          },
          "started_at": {
            "description": "When this process started.",
            "format": "date-time",
            "type": "string"
          },
          "tasks_active": {
            "description": "Tasks queued or running.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          },
          "version": {
            "description": "Control-plane version.",
            "type": "string"
          },
          "vms": {
            "description": "Defined VMs.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          },
          "vms_running": {
            "description": "VMs currently running.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "version",
          "hostname",
          "started_at",
          "hosts",
          "hosts_online",
          "vms",
          "vms_running",
          "tasks_active"
        ],
        "type": "object"
      },
      "SystemSettings": {
        "description": "Body of `GET`/`PATCH /api/v1/system/settings` (M2).",
        "properties": {
          "event_retention_days": {
            "description": "Days to keep audit events before the background sweep prunes them.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          },
          "updated_at": {
            "description": "When these settings were last changed; the `ETag` source (contract §2.7).",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "event_retention_days",
          "updated_at"
        ],
        "type": "object"
      },
      "TargetType": {
        "description": "Kind of resource a task or event refers to.",
        "enum": [
          "vm",
          "host",
          "cluster",
          "datastore",
          "network",
          "image",
          "system"
        ],
        "type": "string"
      },
      "Task": {
        "description": "A unit of asynchronous work.",
        "properties": {
          "created_at": {
            "description": "When the task was accepted.",
            "format": "date-time",
            "type": "string"
          },
          "created_by": {
            "description": "Principal that requested the task (username or API-key id).",
            "type": "string"
          },
          "error": {
            "description": "Failure detail when `state` is `failed`.",
            "type": [
              "string",
              "null"
            ]
          },
          "finished_at": {
            "description": "When the task reached a terminal state.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "description": "Stable identifier.",
            "format": "uuid",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/TaskKind",
            "description": "What the task does."
          },
          "message": {
            "description": "Latest human-readable step.",
            "type": [
              "string",
              "null"
            ]
          },
          "progress": {
            "description": "Completion, 0–100.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          },
          "started_at": {
            "description": "When execution began.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "state": {
            "$ref": "#/components/schemas/TaskState",
            "description": "Lifecycle state."
          },
          "target_id": {
            "description": "The resource acted on, when it exists.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "target_type": {
            "$ref": "#/components/schemas/TargetType",
            "description": "Kind of the resource acted on."
          }
        },
        "required": [
          "id",
          "kind",
          "state",
          "target_type",
          "progress",
          "created_by",
          "created_at"
        ],
        "type": "object"
      },
      "TaskAccepted": {
        "description": "`202 Accepted` body of every action endpoint.",
        "properties": {
          "task": {
            "$ref": "#/components/schemas/Task",
            "description": "The task tracking the accepted work."
          }
        },
        "required": [
          "task"
        ],
        "type": "object"
      },
      "TaskKind": {
        "description": "What a task does.",
        "enum": [
          "vm_create",
          "vm_start",
          "vm_stop",
          "vm_reset",
          "vm_pause",
          "vm_resume",
          "vm_delete",
          "image_import",
          "image_upload",
          "vm_snapshot",
          "vm_snapshot_revert",
          "vm_snapshot_delete",
          "vm_clone",
          "vm_attach_iso",
          "vm_detach_iso",
          "ha_restart",
          "vm_replicate",
          "vm_backup",
          "vm_backup_restore",
          "vm_backup_verify",
          "vm_replica_promote",
          "vm_migrate",
          "host_upgrade",
          "host_enroll",
          "oob_fence"
        ],
        "type": "string"
      },
      "TaskList": {
        "description": "Response of `GET /api/v1/tasks`.",
        "properties": {
          "count": {
            "description": "Number of tasks in this page.",
            "minimum": 0,
            "type": "integer"
          },
          "next_cursor": {
            "description": "Opaque cursor for the next page (decision C7); absent once exhausted.",
            "type": [
              "string",
              "null"
            ]
          },
          "tasks": {
            "description": "Tasks, newest first.",
            "items": {
              "$ref": "#/components/schemas/Task"
            },
            "type": "array"
          }
        },
        "required": [
          "tasks",
          "count"
        ],
        "type": "object"
      },
      "TaskResponse": {
        "description": "Response wrapping one task.",
        "properties": {
          "task": {
            "$ref": "#/components/schemas/Task",
            "description": "The task."
          }
        },
        "required": [
          "task"
        ],
        "type": "object"
      },
      "TaskState": {
        "description": "Lifecycle state of a task.",
        "enum": [
          "queued",
          "running",
          "succeeded",
          "failed",
          "cancelled"
        ],
        "type": "string"
      },
      "UpdateClusterRequest": {
        "description": "Body of `PATCH /api/v1/clusters/{id}`; absent fields are left unchanged.",
        "properties": {
          "name": {
            "description": "New name.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "UpdateDatastoreRequest": {
        "description": "Body of `PATCH /api/v1/datastores/{id}`; absent means unchanged.\n\n`cluster_id` follows JSON Merge Patch (RFC 7396): absent leaves it\nunchanged, `null` clears it, a value replaces it.",
        "properties": {
          "cluster_id": {
            "description": "New cluster to share this datastore into, or `null` to stop sharing it.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "description": "New name.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "UpdateHostRequest": {
        "description": "Body of `PATCH /api/v1/hosts/{id}`; absent fields are left unchanged.\n\n`cluster_id` follows JSON Merge Patch (RFC 7396): absent leaves it\nunchanged, `null` clears it, a value replaces it.",
        "properties": {
          "address": {
            "description": "New agent address.",
            "type": [
              "string",
              "null"
            ]
          },
          "cluster_id": {
            "description": "New cluster, or `null` to remove it.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "maintenance": {
            "description": "Enter or leave maintenance.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "name": {
            "description": "New name.",
            "type": [
              "string",
              "null"
            ]
          },
          "require_mtls": {
            "description": "Switch this host onto (or back off) mTLS (`MTLS.md` §5, slice 4).\n\nSetting this `true` for a host with no valid certificate on record is\naccepted, not refused: `MTLS.md` §4.1's rule is that the *poller*\nfalls back to the token when there is nothing to present, not that\nthis flag must be set in the one order an operator happens to try.",
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "UpdateImageRequest": {
        "description": "Body of `PATCH /api/v1/images/{id}`; absent means unchanged.\n\n`kernel` follows JSON Merge Patch (RFC 7396): absent leaves it unchanged,\n`null` clears it (the image becomes firmware-boot), a value replaces it.",
        "properties": {
          "kernel": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/KernelBoot",
                "description": "New boot settings, or `null` to switch to firmware boot."
              }
            ]
          },
          "name": {
            "description": "New name.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "UpdateNetworkRequest": {
        "description": "Body of `PATCH /api/v1/networks/{id}`; absent means unchanged.\n\n`vlan` and `cluster_id` follow JSON Merge Patch (RFC 7396): absent leaves\nthem unchanged, `null` clears them, a value replaces them.",
        "properties": {
          "cluster_id": {
            "description": "New cluster to share this network into, or `null` to stop sharing it.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "description": "New name.",
            "type": [
              "string",
              "null"
            ]
          },
          "vlan": {
            "description": "New VLAN tag, or `null` to remove it.",
            "format": "int32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "UpdateRouterRequest": {
        "description": "Body of `PATCH /api/v1/routers/{id}`; absent means unchanged.",
        "properties": {
          "name": {
            "description": "New name.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "UpdateSettingsRequest": {
        "description": "Body of `PATCH /api/v1/system/settings`; absent fields are left unchanged.",
        "properties": {
          "event_retention_days": {
            "description": "New retention window in days (1..=3650).",
            "format": "int32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "UpdateUserRequest": {
        "description": "Body of `PATCH /api/v1/users/{id}`; absent means unchanged.",
        "properties": {
          "role": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Role",
                "description": "New role."
              }
            ]
          }
        },
        "type": "object"
      },
      "UpdateVmRequest": {
        "description": "Body of `PATCH /api/v1/vms/{id}`.\n\n`name` requires the VM to be stopped; `cpus`/`memory_bytes` answer `501`\n(M1); `description`/`tags`/`autostart`/`replication`/`anti_affinity_group`\napply any time, running or not (M2/M3/M4). A request naming a field that\ncan't apply right now changes nothing at all — see\n`nextvirt-core::vms::update_vm`'s doc for why this is all-or-nothing per\nrequest, not best-effort per field.\n\n`replication` and `anti_affinity_group` follow JSON Merge Patch (RFC\n7396), like [`crate::network::UpdateNetworkRequest::vlan`]: absent leaves\nthem unchanged, `null` clears them, a value replaces them (`replication`\nis additionally subject to the ZFS-only scope check documented on\n[`ReplicationSpec`]). Setting `anti_affinity_group` on an already-placed\nVM has no retroactive effect — it only influences a *future* placement\ndecision, and there isn't one until this VM is deleted/recreated or, in a\nlater M3 slice, migrated.",
        "properties": {
          "anti_affinity_group": {
            "description": "New anti-affinity group (M3), or `null` to remove it. See\n[`VmSpec::anti_affinity_group`]; has no retroactive effect on a VM\nthat is already placed.",
            "type": [
              "string",
              "null"
            ]
          },
          "autostart": {
            "description": "New autostart flag (M2); applies any time. See [`VmSpec::autostart`]\nfor why setting it doesn't yet change hostd's own behavior.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "backup": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/BackupPolicy",
                "description": "New backup policy (IDEAS.md \"nfd integration\", phase 1), or `null`\nto remove it. See [`BackupPolicy`]; no datastore-kind constraint\napplies, unlike `replication`."
              }
            ]
          },
          "cpus": {
            "description": "New vCPU count; the VM must be stopped (M2). A pure spec rewrite —\napplied the next time the VM boots, not a live hot-plug.",
            "format": "int32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "description": {
            "description": "New description (M2); applies any time.",
            "type": [
              "string",
              "null"
            ]
          },
          "ha": {
            "description": "New HA flag (M3, `HA-RESTART.md`); applies any time. See\n[`VmSpec::ha`].\n\nRefused with `ha_not_available` for a VM whose disks sit on an `iscsi`\ndatastore, while §5.13's exclusivity gap is open.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "memory_bytes": {
            "description": "New memory size; the VM must be stopped (M2). A pure spec rewrite —\napplied the next time the VM boots, not a live hot-plug.",
            "format": "int64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "description": "New name; the VM must be stopped.",
            "type": [
              "string",
              "null"
            ]
          },
          "replication": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ReplicationSpec",
                "description": "New replication policy (M4), or `null` to remove it. See\n[`ReplicationSpec`] for the ZFS-only scope check applied when this is set."
              }
            ]
          },
          "tags": {
            "description": "New tags (M2); applies any time.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "UpgradeBlockedPolicy": {
        "description": "What an orchestrated upgrade does about a VM on the host that cannot\nsafely be moved or left mid-operation (M3, architecture §5.10,\ndecision A30).",
        "enum": [
          "proceed",
          "abort"
        ],
        "type": "string"
      },
      "UpgradeHostRequest": {
        "description": "Body of `POST /api/v1/hosts/{id}/actions/upgrade` (M3, architecture §5.10).\n\nEvery field is optional; an absent body is the default of both.",
        "properties": {
          "evacuate": {
            "description": "Live-migrate this host's running VMs to cluster-mates before the agent\nrestarts, and migrate them back afterwards.\n\nDefaults to `false`: upgrading `nextvirt-hostd` does not disturb\nrunning VMs, which are independent processes the restarted agent\nre-adopts (architecture §5.10, §6.9). Set it when the *host*, not just\nthe agent, is going away — a reboot, a kernel change, or a release\nbuild run on the host itself.",
            "type": "boolean"
          },
          "on_blocked": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/UpgradeBlockedPolicy",
                "description": "What to do about a VM the upgrade cannot handle cleanly.\n\nTwo families, and this governs both. *Always* applicable, whatever\n`evacuate` is set to: a VM with a task in flight (replicate, create,\ndelete, snapshot, clone), because the operator's own restart of the\nagent aborts work the agent owns as a child process. Applicable only\nwith `evacuate: true`: a VM that cannot be live-migrated off (firmware\nboot, non-shared storage, or no cluster-mate with headroom). Defaults\nto `proceed`."
              }
            ]
          }
        },
        "type": "object"
      },
      "User": {
        "description": "A user account, as listed and managed through the users collection.\n\nCarries no password and no expanded capabilities; compare\n[`crate::auth::UserInfo`], returned only to the user themself at login.",
        "properties": {
          "created_at": {
            "description": "Creation time.",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "Stable identifier.",
            "format": "uuid",
            "type": "string"
          },
          "role": {
            "$ref": "#/components/schemas/Role",
            "description": "Role."
          },
          "updated_at": {
            "description": "Last modification time.",
            "format": "date-time",
            "type": "string"
          },
          "username": {
            "description": "Unique, DNS-label-like login name.",
            "type": "string"
          }
        },
        "required": [
          "id",
          "username",
          "role",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "UserInfo": {
        "description": "The authenticated user as returned by login and `GET /api/v1/auth/me`.\n\nDistinct from [`crate::user::User`]: this shape additionally expands `role`\ninto the [`Capability`] list so a client never has to hardcode the mapping.",
        "properties": {
          "capabilities": {
            "description": "Capabilities `role` expands to, in declaration order.",
            "items": {
              "$ref": "#/components/schemas/Capability"
            },
            "type": "array"
          },
          "created_at": {
            "description": "Creation time.",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "Stable account identifier.",
            "format": "uuid",
            "type": "string"
          },
          "role": {
            "$ref": "#/components/schemas/Role",
            "description": "Role."
          },
          "updated_at": {
            "description": "Last modification time.",
            "format": "date-time",
            "type": "string"
          },
          "username": {
            "description": "Login name.",
            "type": "string"
          }
        },
        "required": [
          "id",
          "username",
          "role",
          "capabilities",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "UserInfoResponse": {
        "description": "Response wrapping one user in an authentication context.",
        "properties": {
          "user": {
            "$ref": "#/components/schemas/UserInfo",
            "description": "The authenticated user."
          }
        },
        "required": [
          "user"
        ],
        "type": "object"
      },
      "UserList": {
        "description": "Response of `GET /api/v1/users`.",
        "properties": {
          "count": {
            "description": "Number of users.",
            "minimum": 0,
            "type": "integer"
          },
          "users": {
            "description": "Users, ordered by username.",
            "items": {
              "$ref": "#/components/schemas/User"
            },
            "type": "array"
          }
        },
        "required": [
          "users",
          "count"
        ],
        "type": "object"
      },
      "UserResponse": {
        "description": "Response wrapping one user.",
        "properties": {
          "user": {
            "$ref": "#/components/schemas/User",
            "description": "The user."
          }
        },
        "required": [
          "user"
        ],
        "type": "object"
      },
      "Vm": {
        "description": "A virtual machine.",
        "properties": {
          "created_at": {
            "description": "Creation time.",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "Stable identifier.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "description": "Unique DNS-label-like name.",
            "type": "string"
          },
          "spec": {
            "$ref": "#/components/schemas/VmSpec",
            "description": "Desired configuration."
          },
          "status": {
            "$ref": "#/components/schemas/VmStatus",
            "description": "Observed state."
          },
          "updated_at": {
            "description": "Last modification time.",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "spec",
          "status",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "VmBackup": {
        "description": "One backup run of a VM, in control-plane vocabulary — the projection of\nthe host agent's record (`HostBackupRecord`) across the API boundary,\nmapped rather than re-exported so the two vocabularies keep their own\nnames for the same facts, the same reason every other host type is\ntranslated by `nextvirt-core` instead of leaking through.",
        "properties": {
          "backup_id": {
            "description": "The run's id; also names the volume subdirectory it wrote.",
            "format": "uuid",
            "type": "string"
          },
          "created_at_unix": {
            "description": "Unix seconds, UTC — when the run captured the disks.",
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          },
          "disks": {
            "description": "Every disk, in spec order.",
            "items": {
              "$ref": "#/components/schemas/VmBackupDisk"
            },
            "type": "array"
          },
          "paused": {
            "description": "Whether the VM was paused for the capture (crash-consistent image).",
            "type": "boolean"
          }
        },
        "required": [
          "backup_id",
          "created_at_unix",
          "paused",
          "disks"
        ],
        "type": "object"
      },
      "VmBackupDisk": {
        "description": "One disk captured by a backup run.",
        "properties": {
          "chunks_reused": {
            "minimum": 0,
            "type": "integer"
          },
          "chunks_written": {
            "description": "Chunks the run uploaded (the increment), and chunks the volume\nalready held (the dedup) — an unchanged re-backup is all reuse.",
            "minimum": 0,
            "type": "integer"
          },
          "manifest_id": {
            "description": "The content-addressed manifest id (`m-…`) a restore resolves the\ndisk back through.",
            "type": "string"
          },
          "name": {
            "description": "Disk name, as `VmSpec::disks` spells it.",
            "type": "string"
          },
          "sha256": {
            "description": "The disk's whole-file SHA-256, lowercase hex.",
            "type": "string"
          },
          "size_bytes": {
            "description": "The disk's byte count, as the manifest records it.",
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "name",
          "manifest_id",
          "size_bytes",
          "sha256",
          "chunks_written",
          "chunks_reused"
        ],
        "type": "object"
      },
      "VmBackupList": {
        "description": "Response of `GET /api/v1/vms/{id}/backups`: the backup catalog of one\nVM, oldest first.",
        "properties": {
          "backups": {
            "items": {
              "$ref": "#/components/schemas/VmBackup"
            },
            "type": "array"
          },
          "count": {
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "backups",
          "count"
        ],
        "type": "object"
      },
      "VmCreateAccepted": {
        "description": "`202 Accepted` response of `POST /api/v1/vms`: the VM in its initial state plus the provisioning task.",
        "properties": {
          "task": {
            "$ref": "#/components/schemas/Task",
            "description": "The `vm_create` task tracking provisioning."
          },
          "vm": {
            "$ref": "#/components/schemas/Vm",
            "description": "The VM, `status.power` = `creating`."
          }
        },
        "required": [
          "vm",
          "task"
        ],
        "type": "object"
      },
      "VmList": {
        "description": "Response of `GET /api/v1/vms`.",
        "properties": {
          "count": {
            "description": "Number of VMs.",
            "minimum": 0,
            "type": "integer"
          },
          "vms": {
            "description": "VMs, ordered by name.",
            "items": {
              "$ref": "#/components/schemas/Vm"
            },
            "type": "array"
          }
        },
        "required": [
          "vms",
          "count"
        ],
        "type": "object"
      },
      "VmPowerState": {
        "description": "Observed power state of a VM.",
        "enum": [
          "creating",
          "stopped",
          "running",
          "paused",
          "error",
          "deleting",
          "replicating",
          "migrating",
          "snapshotting"
        ],
        "type": "string"
      },
      "VmResponse": {
        "description": "Response wrapping one VM.",
        "properties": {
          "vm": {
            "$ref": "#/components/schemas/Vm",
            "description": "The VM."
          }
        },
        "required": [
          "vm"
        ],
        "type": "object"
      },
      "VmSpec": {
        "description": "Desired configuration of a VM.",
        "properties": {
          "anti_affinity_group": {
            "description": "Soft anti-affinity group (M3): the cluster-aware scheduler\n(`nextvirt_core::choose_host`) avoids placing this VM on a host that\nalready runs another VM in the same group, when an equally\nroom-having host exists that doesn't — never a hard placement\nfailure. Free-form, like `tags`; no first-class validation.",
            "type": [
              "string",
              "null"
            ]
          },
          "autostart": {
            "description": "Bring this VM back when its host loses it, host substrate only (M2).\n\nActed on by the **control plane**, not the agent — the agent is never\nsent this flag. Only the control plane knows whether a VM has been\nrestarted somewhere else, so an agent deciding locally could start a\nsecond copy of a VM HA restart had already moved\n(`nextvirt-core::autostart`'s module doc has the full argument).\n\nBecause \"its VMM is gone\" and \"its VMM crashed\" are the same observable,\nthis also restarts a VM whose VMM crashed on its own, rate-limited by a\ncooldown rather than retried every poll.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "backup": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/BackupPolicy",
                "description": "Backup policy (IDEAS.md \"nfd integration\", phase 1). The volume\nitself is host-side configuration (`NEXTVIRT_BACKUP_LOCATION` on the\nagent), because the volume is a property of the host's storage\nplumbing, not of any one VM — the same split [`ReplicationSpec`]\nmakes in reverse, where the *target* is per-VM because it names\nother hosts' datastores."
              }
            ]
          },
          "boot": {
            "$ref": "#/components/schemas/BootSpec",
            "description": "Boot method."
          },
          "cpus": {
            "description": "Virtual CPUs.",
            "format": "int32",
            "minimum": 0,
            "type": "integer"
          },
          "description": {
            "description": "Human-readable description (M2).",
            "type": [
              "string",
              "null"
            ]
          },
          "disks": {
            "description": "Disks, boot disk first.",
            "items": {
              "$ref": "#/components/schemas/DiskSpec"
            },
            "type": "array"
          },
          "ha": {
            "description": "Restart this VM on another host when its own host is **proven** dead\n(M3, `HA-RESTART.md`), host substrate only.\n\nOff unless explicitly set, and deliberately harder to satisfy than it\nlooks. \"Proven\" means the host had an *armed fence* and has been silent\nfor longer than that fence's timeout, so the host has necessarily reset\nitself and its VMs are stopped. An unfenced host that goes quiet is\nnever acted on, however long it stays quiet — silence alone is the\nabsence of evidence, not evidence.\n\nSetting this on a VM whose disks are not reachable from another host in\nthe cluster achieves nothing: there is nowhere to restart it. Setting it\non an `iscsi`-backed VM is refused outright while §5.13's exclusivity\ngap is open.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "memory_bytes": {
            "description": "Guest memory.",
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          },
          "nics": {
            "description": "Network interfaces.",
            "items": {
              "$ref": "#/components/schemas/NicSpec"
            },
            "type": "array"
          },
          "replication": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ReplicationSpec",
                "description": "Asynchronous DR replication policy, host-substrate only (M4)."
              }
            ]
          },
          "tags": {
            "description": "Free-form labels (M2); a first-class, queryable form is M4.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          }
        },
        "required": [
          "cpus",
          "memory_bytes",
          "boot",
          "disks",
          "nics"
        ],
        "type": "object"
      },
      "VmStatus": {
        "description": "Observed state of a VM.",
        "properties": {
          "attached_iso_id": {
            "description": "The image currently attached as the optical drive, if any (M2,\ncontract §4.9's `attach-iso`/`detach-iso`). A hot-plug fact observed\nfrom the running VMM, not part of the desired boot configuration —\nit can be lost if the VMM process itself is ever respawned (an agent\nrestart or host reboot), unlike an ordinary stop/start of the guest,\nwhich leaves the same VMM process (and this attachment) untouched.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "host_id": {
            "description": "Host the VM is placed on.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "last_backed_up_at": {
            "description": "When the last successful backup run completed (IDEAS.md \"nfd\nintegration\", phase 1). Written by the `vm_backup` task; the backup\nscheduler's due computation reads it.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "last_replicated_at": {
            "description": "Either side (M4): when the last successful replication cycle\ncompleted. Set on both the source and target rows by the same task.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "message": {
            "description": "Latest human-readable detail, typically the last error.",
            "type": [
              "string",
              "null"
            ]
          },
          "power": {
            "$ref": "#/components/schemas/VmPowerState",
            "description": "Power state."
          },
          "replica_source_id": {
            "description": "Target side (M4): which `Vm` this is a replica of. Kept as\nprovenance after `promote-replica`, since the target's own\n`spec.replication` is never set — there is nothing else to clear.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "replica_vm_id": {
            "description": "Source side (M4): the replica `Vm` this VM's `vm_replicate` task\ncreated on `spec.replication.target_host_id`, once the first cycle\nsucceeds. `None` until then, even while `spec.replication` is set.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "description": "When the status last changed.",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "power",
          "updated_at"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "api_key": {
        "description": "API key issued from Settings → API keys. Carries the scopes granted at creation.",
        "in": "header",
        "name": "X-API-Key",
        "type": "apiKey"
      },
      "session_cookie": {
        "description": "Session cookie set by POST /api/v1/auth/login. State-changing requests also require the X-CSRF-Token header.",
        "in": "cookie",
        "name": "id",
        "type": "apiKey"
      }
    }
  },
  "info": {
    "description": "Register KVM hosts and manage their virtual machines.",
    "license": {
      "name": ""
    },
    "title": "nextVIRT Control Plane API",
    "version": "0.1.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/api/v1/api-keys": {
      "get": {
        "operationId": "list_api_keys",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyList"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "api-keys"
        ],
        "x-nextvirt-capability": "write:system",
        "x-nextvirt-milestone": "M1"
      },
      "post": {
        "operationId": "create_api_key",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiKeyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateApiKeyResponse"
                }
              }
            },
            "description": ""
          },
          "422": {
            "description": "invalid_scope"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "api-keys"
        ],
        "x-nextvirt-capability": "write:system",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/api-keys/{id}": {
      "delete": {
        "operationId": "delete_api_key",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ deleted } — effective on the next request"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "api-keys"
        ],
        "x-nextvirt-capability": "write:system",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/auth/csrf": {
      "post": {
        "operationId": "refresh_csrf",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CsrfResponse"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Mints a fresh CSRF token for the current session.",
        "tags": [
          "auth"
        ],
        "x-nextvirt-capability": "none",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/auth/login": {
      "post": {
        "operationId": "login",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LoginResponse"
                }
              }
            },
            "description": "Sets the session cookie"
          },
          "401": {
            "description": "invalid_credentials"
          },
          "429": {
            "description": "rate_limited: 5 failures per (username, IP) locks that pair for 15 minutes"
          }
        },
        "summary": "Authenticates a username and password, starting a session.",
        "tags": [
          "auth"
        ],
        "x-nextvirt-capability": "none",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/auth/logout": {
      "post": {
        "operationId": "logout",
        "responses": {
          "200": {
            "description": "{ logged_out: true }"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Ends the session.",
        "tags": [
          "auth"
        ],
        "x-nextvirt-capability": "none",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/auth/me": {
      "get": {
        "description": "Meaningful only for a session: an API key has no backing user account.",
        "operationId": "me",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserInfoResponse"
                }
              }
            },
            "description": ""
          },
          "400": {
            "description": "authenticated with an API key, which has no user identity"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "The authenticated user, expanded with their capabilities.",
        "tags": [
          "auth"
        ],
        "x-nextvirt-capability": "none",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/clusters": {
      "get": {
        "operationId": "list_clusters",
        "parameters": [
          {
            "description": "Filters by exact name (an alternate key lookup: zero or one result).",
            "in": "query",
            "name": "name",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClusterList"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "clusters"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M3"
      },
      "post": {
        "operationId": "create_cluster",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateClusterRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClusterResponse"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "name_taken"
          },
          "422": {
            "description": "invalid_name"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "clusters"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M3"
      }
    },
    "/api/v1/clusters/{id}": {
      "delete": {
        "operationId": "delete_cluster",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ deleted }"
          },
          "409": {
            "description": "cluster_referenced"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "clusters"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M3"
      },
      "get": {
        "operationId": "get_cluster",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClusterResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "clusters"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M3"
      },
      "patch": {
        "operationId": "update_cluster",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateClusterRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClusterResponse"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "clusters"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M3"
      }
    },
    "/api/v1/datastores": {
      "get": {
        "operationId": "list_datastores",
        "parameters": [
          {
            "in": "query",
            "name": "host_id",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "name",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "cluster_id",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DatastoreList"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "datastores"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      },
      "post": {
        "operationId": "create_datastore",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDatastoreRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DatastoreResponse"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "name_taken"
          },
          "422": {
            "description": "unknown_host"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "datastores"
        ],
        "x-nextvirt-capability": "write:storage",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/datastores/{id}": {
      "delete": {
        "operationId": "delete_datastore",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ deleted }"
          },
          "409": {
            "description": "datastore_referenced"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "datastores"
        ],
        "x-nextvirt-capability": "write:storage",
        "x-nextvirt-milestone": "M1"
      },
      "get": {
        "operationId": "get_datastore",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DatastoreResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "datastores"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      },
      "patch": {
        "operationId": "update_datastore",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateDatastoreRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DatastoreResponse"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "datastores"
        ],
        "x-nextvirt-capability": "write:storage",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/datastores/{id}/actions/refresh": {
      "post": {
        "description": "M1 has no live host agent yet, so this refreshes from the same poller path\n`poll_host` uses rather than opening a second, action-specific agent call.",
        "operationId": "refresh_datastore",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DatastoreResponse"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Asks the host agent for the datastore's usage right now.",
        "tags": [
          "datastores"
        ],
        "x-nextvirt-capability": "write:storage",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/events": {
      "get": {
        "operationId": "list_events",
        "parameters": [
          {
            "in": "query",
            "name": "kind",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "target_type",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "target_id",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "actor",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "format": "int32",
              "minimum": 0,
              "type": [
                "integer",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventList"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "events"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/hosts": {
      "get": {
        "operationId": "list_hosts",
        "parameters": [
          {
            "description": "Filters by exact liveness state.",
            "in": "query",
            "name": "state",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "description": "Filters by exact name (an alternate key lookup: zero or one result).",
            "in": "query",
            "name": "name",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "description": "Filters to hosts belonging to this cluster.",
            "in": "query",
            "name": "cluster_id",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostList"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      },
      "post": {
        "operationId": "create_host",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateHostRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateHostResponse"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "name_taken"
          },
          "422": {
            "description": "invalid_name or invalid_address"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/hosts/{id}": {
      "delete": {
        "operationId": "delete_host",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ deleted }"
          },
          "409": {
            "description": "host_referenced"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M1"
      },
      "get": {
        "operationId": "get_host",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      },
      "patch": {
        "operationId": "update_host",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateHostRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostResponse"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/hosts/{id}/actions/arm-fence": {
      "post": {
        "description": "Destructive in a way no other host action is: from here the host survives\nonly while this control plane keeps talking to it. That is the point — it\nis what lets HA restart treat silence as proof the host's VMs stopped —\nbut it means a control-plane outage, a management-network break, or a\nstale address for this host will reset it.",
        "operationId": "arm_host_fence",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "host_not_reachable"
          },
          "422": {
            "description": "fence_unavailable, fence_not_crash_safe, fence_unsupported"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Arms a host's watchdog fence, so the host resets if this control plane\nstops polling it (M3, `HA-RESTART.md`).",
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M3"
      }
    },
    "/api/v1/hosts/{id}/actions/cancel-expected-restart": {
      "post": {
        "description": "Idempotent: cancelling when nothing is declared still succeeds.",
        "operationId": "cancel_expected_host_restart",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Cancels an active `expect-restart` declaration early, restoring\nout-of-band fencing eligibility immediately rather than waiting out the\nrest of the window (`HA-RESTART.md` §11.5).",
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M3"
      }
    },
    "/api/v1/hosts/{id}/actions/chaos-freeze": {
      "post": {
        "description": "Never enforced: nothing automatically thaws this host. The response\ncarries the real fallback deadline instead of a promised duration —\n`last_seen_at + OOB_MIN_OFFLINE`, the same boundary that already governs\nevery silent host in this system, made visible rather than newly\nenforced (`CHAOS.md` §5).",
        "operationId": "chaos_freeze_host",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChaosFreezeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChaosFreezeResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "chaos_requires_oob_fence, host_not_reachable, chaos_requires_watchdog_disarmed, chaos_host_state_unknown, chaos_freeze_raced_with_arm_fence"
          },
          "422": {
            "description": "oob_fence_unreachable"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Pauses this host in place for chaos testing, via the same out-of-band\ncredential fencing already uses (`CHAOS.md` §6). Admin-only\n(`Capability::Chaos`) — one tier above the `WriteHosts` every other host\naction needs, matching the individual-human-authorization bar every real\nfault-injection this project has run already met.",
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "ops:chaos",
        "x-nextvirt-milestone": "M6"
      }
    },
    "/api/v1/hosts/{id}/actions/chaos-thaw": {
      "post": {
        "description": "Never refused on host state, the same asymmetry `disarm_host_fence`\nalready has relative to `arm_host_fence` — ending an experiment must\nalways work.",
        "operationId": "chaos_thaw_host",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "chaos_requires_oob_fence, chaos_host_not_frozen, chaos_host_state_unknown"
          },
          "422": {
            "description": "oob_fence_unreachable"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Ends an active chaos-freeze, resuming the host for real (`CHAOS.md` §6).\nAdmin-only (`Capability::Chaos`), like `chaos-freeze`.",
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "ops:chaos",
        "x-nextvirt-milestone": "M6"
      }
    },
    "/api/v1/hosts/{id}/actions/disarm-fence": {
      "post": {
        "description": "Never refused on state grounds — disarming is the safe direction, and\nrefusing it would only make a stuck host harder to rescue.",
        "operationId": "disarm_host_fence",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "422": {
            "description": "fence_unavailable, fence_unsupported"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Releases a host's watchdog fence (M3, `HA-RESTART.md`).",
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M3"
      }
    },
    "/api/v1/hosts/{id}/actions/enroll": {
      "post": {
        "description": "The body is optional: `POST` with no body is `evacuate: false`,\n`on_blocked: proceed` — the same bodiless shape its `enter-maintenance`\nneighbour has.\nEnrolls a host for mTLS (M4, `MTLS.md` slice 3): fetches its certificate\nrequest, signs it with this control plane's CA, and installs the result.",
        "operationId": "enroll_host",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAccepted"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "enroll_in_progress"
          },
          "422": {
            "description": "pki_not_configured: this control plane has no certificate authority loaded"
          },
          "502": {
            "description": "host_error: the agent did not answer, or refused the certificate"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Orchestrates an agent upgrade on one host (M3, contract §4.5, architecture §5.10).",
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M4"
      }
    },
    "/api/v1/hosts/{id}/actions/enter-maintenance": {
      "post": {
        "operationId": "enter_host_maintenance",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostResponse"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/hosts/{id}/actions/exit-maintenance": {
      "post": {
        "operationId": "exit_host_maintenance",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostResponse"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/hosts/{id}/actions/expect-restart": {
      "post": {
        "description": "Never refused on state grounds, the same as disarming — this can only\never suppress a power-off, never cause one.",
        "operationId": "expect_host_restart",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExpectRestartRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Declares that an operator is about to intentionally restart this host,\nsuppressing the HA reaper's out-of-band fencing branch until the window\nlapses (`HA-RESTART.md` §11.5's fix for the OOB-refence-on-restart race).",
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M3"
      }
    },
    "/api/v1/hosts/{id}/actions/poll": {
      "post": {
        "operationId": "poll_host",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostResponse"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/hosts/{id}/actions/rotate-token": {
      "post": {
        "description": "The control plane starts using the new token immediately; the agent\nkeeps accepting only the one baked into its own `NEXTVIRT_HOST_TOKEN` at\nstartup, so this host will fail auth until an operator updates that\nvalue and restarts the agent — the returned `join_token` is exactly what\nto put there, shown once, the same as at registration.",
        "operationId": "rotate_host_token",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateHostResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Replaces a host's bearer join token (contract §4.5).",
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M2"
      }
    },
    "/api/v1/hosts/{id}/actions/upgrade": {
      "post": {
        "operationId": "upgrade_host",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpgradeHostRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAccepted"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "upgrade_in_progress | cluster_upgrade_in_progress | host_not_online | host_in_maintenance | vm_busy | no_capacity"
          },
          "422": {
            "description": "host_not_evacuable"
          },
          "502": {
            "description": "host_error: the agent did not answer the baseline probe"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M3"
      }
    },
    "/api/v1/hosts/{id}/interfaces/confirm": {
      "post": {
        "operationId": "confirm_host_interface",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "confirmed"
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "no_pending_change"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Confirms a host's outstanding interface change immediately, rather than\nwaiting for the next poll (`OVN.md` §8). Tokenless — the control plane\nrelays whatever it is holding for this host, which is the entire proof\nthe mechanism needs: reaching the agent at all.",
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M4"
      }
    },
    "/api/v1/hosts/{id}/interfaces/{name}": {
      "put": {
        "description": "**Not confirmed by this response.** It reverts on its own unless this\ncontrol plane reaches the host again before the returned\n`rollback_at_unix` — normally at the next successful poll, or\nimmediately via `POST .../interfaces/confirm`.",
        "operationId": "put_host_interface",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "name",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PutHostInterfaceRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostInterfaceChangeResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "422": {
            "description": "invalid_interface, unknown_interface"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Applies a provisional change to one of a host's network interfaces\n(`OVN.md` §8).",
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M4"
      }
    },
    "/api/v1/hosts/{id}/metrics": {
      "get": {
        "operationId": "host_metrics",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "How far back to look, `<N>h` or `<N>d`; defaults to `24h`.",
            "in": "query",
            "name": "range",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MetricsResponse"
                }
              }
            },
            "description": ""
          },
          "400": {
            "description": "bad_request"
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "A host's CPU/memory history (M2, contract §4.5).",
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M2"
      }
    },
    "/api/v1/hosts/{id}/oob-fence": {
      "delete": {
        "operationId": "delete_host_oob_fence",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "removed"
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Removes a host's out-of-band fencing configuration.",
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M3"
      },
      "get": {
        "operationId": "get_host_oob_fence",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OobFenceResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found: the host does not exist, or has no out-of-band fencing configured"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "This host's out-of-band fencing configuration, if any — never the\ncredential (`HA-RESTART.md` §11.8's \"shown once\" discipline).",
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M3"
      },
      "put": {
        "description": "Verified against the real target before it is stored — see\n`Core::put_oob_fence`'s own doc for what that verification checks.",
        "operationId": "put_host_oob_fence",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PutOobFenceRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OobFenceResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "422": {
            "description": "oob_fence_unreachable: the target could not be reached or the credential was refused | oob_fence_substrate_managed: the target is already managed by Proxmox's own HA layer"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Configures (or replaces) a host's out-of-band fencing target\n(`HA-RESTART.md` §11.8).",
        "tags": [
          "hosts"
        ],
        "x-nextvirt-capability": "write:hosts",
        "x-nextvirt-milestone": "M3"
      }
    },
    "/api/v1/images": {
      "get": {
        "operationId": "list_images",
        "parameters": [
          {
            "in": "query",
            "name": "datastore_id",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "name",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageList"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "images"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      },
      "post": {
        "operationId": "create_image",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateImageRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageResponse"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "name_taken"
          },
          "422": {
            "description": "unknown_datastore or invalid_size"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "images"
        ],
        "x-nextvirt-capability": "write:storage",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/images/import": {
      "post": {
        "operationId": "import_image",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportImageRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportImageAccepted"
                }
              }
            },
            "description": ""
          },
          "422": {
            "description": "invalid_url or unknown_datastore"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "images"
        ],
        "x-nextvirt-capability": "write:storage",
        "x-nextvirt-milestone": "M2"
      }
    },
    "/api/v1/images/{id}": {
      "delete": {
        "operationId": "delete_image",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ deleted }"
          },
          "409": {
            "description": "image_referenced"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "images"
        ],
        "x-nextvirt-capability": "write:storage",
        "x-nextvirt-milestone": "M1"
      },
      "get": {
        "operationId": "get_image",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "images"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      },
      "patch": {
        "operationId": "update_image",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateImageRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageResponse"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "images"
        ],
        "x-nextvirt-capability": "write:storage",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/images/{id}/upload": {
      "put": {
        "operationId": "upload_image",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/octet-stream": {}
          },
          "description": "the raw file"
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAccepted"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "images"
        ],
        "x-nextvirt-capability": "write:storage",
        "x-nextvirt-milestone": "M2"
      }
    },
    "/api/v1/networks": {
      "get": {
        "operationId": "list_networks",
        "parameters": [
          {
            "in": "query",
            "name": "host_id",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "name",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "cluster_id",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NetworkList"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "networks"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      },
      "post": {
        "operationId": "create_network",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateNetworkRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NetworkResponse"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "name_taken"
          },
          "422": {
            "description": "unknown_host or invalid_vlan"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "networks"
        ],
        "x-nextvirt-capability": "write:network",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/networks/{id}": {
      "delete": {
        "operationId": "delete_network",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ deleted }"
          },
          "409": {
            "description": "network_referenced"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "networks"
        ],
        "x-nextvirt-capability": "write:network",
        "x-nextvirt-milestone": "M1"
      },
      "get": {
        "operationId": "get_network",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NetworkResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "networks"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      },
      "patch": {
        "operationId": "update_network",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateNetworkRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NetworkResponse"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "networks"
        ],
        "x-nextvirt-capability": "write:network",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/routers": {
      "get": {
        "operationId": "list_routers",
        "parameters": [
          {
            "in": "query",
            "name": "name",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "cluster_id",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RouterList"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "routers"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M4"
      },
      "post": {
        "operationId": "create_router",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRouterRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RouterResponse"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "name_taken"
          },
          "422": {
            "description": "invalid_name, ovn_not_configured, ovn_unavailable"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "routers"
        ],
        "x-nextvirt-capability": "write:network",
        "x-nextvirt-milestone": "M4"
      }
    },
    "/api/v1/routers/{id}": {
      "delete": {
        "operationId": "delete_router",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ deleted }"
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "routers"
        ],
        "x-nextvirt-capability": "write:network",
        "x-nextvirt-milestone": "M4"
      },
      "get": {
        "operationId": "get_router",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RouterResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "routers"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M4"
      },
      "patch": {
        "operationId": "update_router",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateRouterRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RouterResponse"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "routers"
        ],
        "x-nextvirt-capability": "write:network",
        "x-nextvirt-milestone": "M4"
      }
    },
    "/api/v1/routers/{id}/interfaces": {
      "get": {
        "operationId": "list_router_interfaces",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RouterInterfaceList"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "routers"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M4"
      },
      "post": {
        "operationId": "attach_network_to_router",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttachNetworkRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RouterInterfaceResponse"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "the network already has a router"
          },
          "422": {
            "description": "invalid_gateway, invalid_network, ovn_unavailable"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "routers"
        ],
        "x-nextvirt-capability": "write:network",
        "x-nextvirt-milestone": "M4"
      }
    },
    "/api/v1/routers/{id}/interfaces/{network_id}": {
      "delete": {
        "operationId": "detach_network_from_router",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "network_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ detached }"
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "routers"
        ],
        "x-nextvirt-capability": "write:network",
        "x-nextvirt-milestone": "M4"
      }
    },
    "/api/v1/setup": {
      "post": {
        "operationId": "create_admin",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetupRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserInfoResponse"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "setup_already_done"
          },
          "422": {
            "description": "invalid_name or invalid_password"
          }
        },
        "summary": "Creates the first administrator. Closed forever once any user exists.",
        "tags": [
          "setup"
        ],
        "x-nextvirt-capability": "none",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/setup/status": {
      "get": {
        "operationId": "status",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetupStatus"
                }
              }
            },
            "description": ""
          }
        },
        "summary": "Whether the control plane has any users yet.",
        "tags": [
          "setup"
        ],
        "x-nextvirt-capability": "none",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/system/features": {
      "get": {
        "operationId": "features",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeaturesResponse"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "What this build can do, for UI gating.",
        "tags": [
          "system"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/system/info": {
      "get": {
        "operationId": "system_info",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SystemInfo"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "The dashboard summary.",
        "tags": [
          "system"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/system/settings": {
      "get": {
        "operationId": "get_settings",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SystemSettings"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "The one settings row (contract §4.4, M2).",
        "tags": [
          "system"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M2"
      },
      "patch": {
        "operationId": "update_settings",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSettingsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SystemSettings"
                }
              }
            },
            "description": ""
          },
          "412": {
            "description": "precondition_failed"
          },
          "422": {
            "description": "invalid_retention"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Applies a partial update to the settings row; absent fields are left unchanged.",
        "tags": [
          "system"
        ],
        "x-nextvirt-capability": "write:system",
        "x-nextvirt-milestone": "M2"
      }
    },
    "/api/v1/tasks": {
      "get": {
        "operationId": "list_tasks",
        "parameters": [
          {
            "in": "query",
            "name": "state",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "kind",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "target_id",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "format": "int32",
              "minimum": 0,
              "type": [
                "integer",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskList"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "tasks"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/tasks/{id}": {
      "get": {
        "operationId": "get_task",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "tasks"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/tasks/{id}/actions/cancel": {
      "post": {
        "operationId": "cancel_task",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskResponse"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "task_finished"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "tasks"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/users": {
      "get": {
        "operationId": "list_users",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserList"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "users"
        ],
        "x-nextvirt-capability": "write:users",
        "x-nextvirt-milestone": "M1"
      },
      "post": {
        "operationId": "create_user",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUserRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserResponse"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "name_taken"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "users"
        ],
        "x-nextvirt-capability": "write:users",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/users/{id}": {
      "delete": {
        "operationId": "delete_user",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ deleted }"
          },
          "409": {
            "description": "self_delete or last_admin"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "users"
        ],
        "x-nextvirt-capability": "write:users",
        "x-nextvirt-milestone": "M1"
      },
      "get": {
        "operationId": "get_user",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "users"
        ],
        "x-nextvirt-capability": "write:users",
        "x-nextvirt-milestone": "M1"
      },
      "patch": {
        "operationId": "update_user",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateUserRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserResponse"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "last_admin"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "users"
        ],
        "x-nextvirt-capability": "write:users",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/users/{id}/actions/set-password": {
      "post": {
        "operationId": "set_user_password",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetPasswordRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserResponse"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Changes a password: requires `write:users`, or that the caller is `id` themself.",
        "tags": [
          "users"
        ],
        "x-nextvirt-capability": "write:users",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/vms": {
      "get": {
        "operationId": "list_vms",
        "parameters": [
          {
            "in": "query",
            "name": "host_id",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "power",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "name",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VmList"
                }
              }
            },
            "description": ""
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      },
      "post": {
        "operationId": "create_vm",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateVmRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VmCreateAccepted"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "unknown image, datastore, or network"
          },
          "409": {
            "description": "name_taken, host_not_online, host_in_maintenance, or no_capacity"
          },
          "422": {
            "description": "disk_too_small, host_mismatch, or invalid_name"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "write:vm",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/vms/actions/adopt": {
      "post": {
        "operationId": "adopt_vm",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdoptVmRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VmResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "unknown host, image, datastore, or network, or the agent no longer reports this vm id"
          },
          "409": {
            "description": "name_taken or id_taken"
          },
          "422": {
            "description": "host_mismatch or invalid_name"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Registers a VM the host agent already runs into inventory (contract\n§4.9, architecture decision A11) — synchronous, `200` not `202`: the VM\nalready exists on its host, so there is no task, nothing to wait for.",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "write:vm",
        "x-nextvirt-milestone": "M2"
      }
    },
    "/api/v1/vms/{id}": {
      "delete": {
        "operationId": "delete_vm",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAccepted"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "vm_busy"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "write:vm",
        "x-nextvirt-milestone": "M1"
      },
      "get": {
        "operationId": "get_vm",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VmResponse"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      },
      "patch": {
        "operationId": "update_vm",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateVmRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VmResponse"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "vm_not_stopped"
          },
          "501": {
            "description": "not_implemented: cpus/memory_bytes resize in M1"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "write:vm",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/vms/{id}/actions/attach-iso": {
      "post": {
        "operationId": "attach_iso",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttachIsoRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAccepted"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "vm_not_running"
          },
          "422": {
            "description": "host_mismatch"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Attaches an image read-only to a running VM's optical drive, replacing\nwhatever was there (M2, contract §4.9). A dedicated route ahead of\n`/actions/{action}`, same reasoning as `clone` above.",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "write:vm",
        "x-nextvirt-milestone": "M2"
      }
    },
    "/api/v1/vms/{id}/actions/backup": {
      "post": {
        "operationId": "backup_vm",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAccepted"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "backup_in_progress"
          },
          "502": {
            "description": "the agent refused: no volume configured, or the sidecar failed"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Streams every disk of a VM into its backup volume through the host's\n`nfd` sidecar (IDEAS.md \"nfd integration\", phase 1). A running VM is\npaused for the capture and resumed after — crash-consistent, the class\nof image a power cut would leave. The volume itself is host-side\nconfiguration (`NEXTVIRT_BACKUP_LOCATION` on the agent); this endpoint\nneeds no body.",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "write:vm",
        "x-nextvirt-milestone": "M4"
      }
    },
    "/api/v1/vms/{id}/actions/clone": {
      "post": {
        "operationId": "clone_vm",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CloneVmRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VmCreateAccepted"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "vm_not_stopped or name_taken"
          },
          "422": {
            "description": "invalid_name"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Defines a clone of the VM's current disk state under a new name and\nstarts the task that provisions it (M2, contract §4.9). A dedicated,\nliteral route registered ahead of `/actions/{action}` above — axum\nmatches the more specific static segment first — since a clone's\nresponse (`VmCreateAccepted`, a new `Vm` plus its task) does not fit the\ngeneric `action` handler's `TaskAccepted`-only shape.",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "write:vm",
        "x-nextvirt-milestone": "M2"
      }
    },
    "/api/v1/vms/{id}/actions/detach-iso": {
      "post": {
        "operationId": "detach_iso",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAccepted"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "vm_not_running"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Detaches whatever is in a running VM's optical drive, if anything (M2,\ncontract §4.9). Idempotent on the host side.",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "write:vm",
        "x-nextvirt-milestone": "M2"
      }
    },
    "/api/v1/vms/{id}/actions/migrate": {
      "post": {
        "operationId": "migrate_vm",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MigrateVmRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAccepted"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "illegal_transition | vm_busy | migration_in_progress | migration_target_busy | host_not_online | host_in_maintenance"
          },
          "422": {
            "description": "invalid_boot | host_mismatch"
          },
          "501": {
            "description": "not_implemented: cloud-substrate VM, see relocate (M5, not yet built)"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Moves a VM to another host, cold or live (M3, contract §4.9, architecture\n§5.15). Kernel-boot only — a firmware-boot VM answers `422 invalid_boot`\nhere, not this same endpoint's other, unrelated `501` reason (M5's\nstill-unbuilt cloud-substrate case, see `relocate`).",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "ops:power",
        "x-nextvirt-milestone": "M3"
      }
    },
    "/api/v1/vms/{id}/actions/promote-replica": {
      "post": {
        "operationId": "promote_replica",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAccepted"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "illegal_transition | replication_in_progress"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Promotes a replica target into an ordinary, standalone VM (M4, contract\n§4.9, architecture decision A16), once every disk has arrived via async\nDR replication. Finalizes the already-received disks on the target host\nand leaves the VM `stopped` for the operator to start explicitly.",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "write:vm",
        "x-nextvirt-milestone": "M4"
      }
    },
    "/api/v1/vms/{id}/actions/{action}": {
      "post": {
        "operationId": "vm_action",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "start | stop | reset | pause | resume",
            "in": "path",
            "name": "action",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StopRequest"
              }
            }
          },
          "description": "only inspected for `stop`",
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAccepted"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "unknown action"
          },
          "409": {
            "description": "illegal_transition"
          },
          "501": {
            "description": "not_implemented: graceful stop on a kernel-boot VM"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "The one route behind all five power actions; `{action}` is `start` /\n`stop` / `reset` / `pause` / `resume` (contract §4.9). A body is read for\nevery action but only `stop` inspects it (`StopRequest`); the others\nsimply ignore whatever, if anything, was sent. `mode: graceful` (M2)\nneeds a firmware-boot VM — `Core::vm_action` is where that is actually\nenforced, since only it knows the VM's boot kind.",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "ops:power",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/vms/{id}/backups": {
      "get": {
        "operationId": "list_vm_backups",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VmBackupList"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "422": {
            "description": "vm_not_placed"
          },
          "502": {
            "description": "the agent's catalog could not be read"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "The VM's backup catalog, projected from its host agent's records, oldest\nfirst. Retention pruning is deliberately absent (see\n`nextvirt-core::backup`'s module doc for why \"delete the row\" would\nfree no space and lie about it).",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M4"
      }
    },
    "/api/v1/vms/{id}/backups/{backup_id}/actions/restore": {
      "post": {
        "operationId": "restore_vm_backup",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "backup_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAccepted"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "vm_not_stopped | backup_in_progress"
          },
          "422": {
            "description": "disk_no_longer_exists | disk_size_mismatch"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Restores one backup back onto the VM's disks (IDEAS.md \"nfd\nintegration\", phase 1). The VM must be stopped — this overwrites every\ndisk with exactly the bytes the backup captured, verifying each chunk\nas it lands.",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "write:vm",
        "x-nextvirt-milestone": "M4"
      }
    },
    "/api/v1/vms/{id}/backups/{backup_id}/actions/verify": {
      "post": {
        "operationId": "verify_vm_backup",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "backup_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAccepted"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "422": {
            "description": "vm_not_placed"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Proves one backup still restores: every disk is reassembled to scratch\nspace on the VM's host, verified against its recorded digests, and the\nscratch deleted. The VM itself is untouched and may be running. Runs\nnightly by the scheduler for every backed-up VM; this is the on-demand\nform.",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M4"
      }
    },
    "/api/v1/vms/{id}/console": {
      "get": {
        "description": "A transparent frame-for-frame proxy to `nextvirt-hostd`'s own console\nbridge, which implements the actual writer/observer arbitration and ring\nbuffer (architecture §6.5) — this handler's only jobs are authorizing the\nrequest, resolving which host to reach, and rejecting what it can\ndetermine before ever upgrading (unknown VM, VM not running) with an\nordinary HTTP status rather than a WebSocket close code.",
        "operationId": "vm_console",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "101": {
            "description": "WebSocket upgrade: serial console bytes"
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "vm_not_running"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Serial console over a WebSocket bridged to the host agent (contract §4.9).",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "ops:console",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/api/v1/vms/{id}/metrics": {
      "get": {
        "operationId": "vm_metrics",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "How far back to look, `<N>h` or `<N>d`; defaults to `24h`.",
            "in": "query",
            "name": "range",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MetricsResponse"
                }
              }
            },
            "description": ""
          },
          "400": {
            "description": "bad_request"
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "A VM's CPU/memory history (M2, contract §4.9).",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M2"
      }
    },
    "/api/v1/vms/{id}/snapshots": {
      "get": {
        "operationId": "list_snapshots",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SnapshotList"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Every snapshot of the VM, newest first (M2, contract §4.9).",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M2"
      },
      "post": {
        "operationId": "create_snapshot",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSnapshotRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SnapshotAccepted"
                }
              }
            },
            "description": ""
          },
          "409": {
            "description": "vm_not_stopped or name_taken"
          },
          "422": {
            "description": "invalid_name"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Captures the VM's current disk state under a new snapshot (M2, contract\n§4.9). `Snapshot` has no `updated_at` — nothing about it ever changes in\nplace — so unlike every other create/update response in this module,\nthis one carries no `ETag`.",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "write:vm",
        "x-nextvirt-milestone": "M2"
      }
    },
    "/api/v1/vms/{id}/snapshots/{snapshot_id}": {
      "delete": {
        "operationId": "delete_snapshot",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "snapshot_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAccepted"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Removes a snapshot (M2, contract §4.9).",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "write:vm",
        "x-nextvirt-milestone": "M2"
      }
    },
    "/api/v1/vms/{id}/snapshots/{snapshot_id}/actions/revert": {
      "post": {
        "operationId": "revert_snapshot",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "snapshot_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskAccepted"
                }
              }
            },
            "description": ""
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "vm_not_stopped"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Restores every disk to `snapshot_id`'s captured state (M2, contract §4.9).",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "write:vm",
        "x-nextvirt-milestone": "M2"
      }
    },
    "/api/v1/vms/{id}/vnc": {
      "get": {
        "description": "A transparent frame-for-frame proxy to `nextvirt-hostd`'s own vnc bridge —\nsee `console`'s doc for the shape this shares; the only difference is\nwhich host route it opens and that RFB carries no text control channel.",
        "operationId": "vm_vnc",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "101": {
            "description": "WebSocket upgrade: raw RFB protocol bytes"
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "vm_not_running"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "VNC console over a WebSocket bridged to the host agent (contract §4.9,\nfirmware-boot VMs only).",
        "tags": [
          "vms"
        ],
        "x-nextvirt-capability": "ops:console",
        "x-nextvirt-milestone": "M2"
      }
    },
    "/health": {
      "get": {
        "operationId": "health",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            },
            "description": "Serving normally"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            },
            "description": "A dependency is unhealthy"
          }
        },
        "summary": "Liveness probe: `200 ok` while the store answers, `503 degraded` otherwise.",
        "tags": [
          "system"
        ],
        "x-nextvirt-capability": "none",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/metrics": {
      "get": {
        "operationId": "prometheus_metrics",
        "responses": {
          "200": {
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Prometheus exposition text"
          }
        },
        "summary": "The latest known CPU/memory reading for every host and VM, as Prometheus\nexposition text (architecture §11, M2) — a fleet-wide gauge snapshot, not\nthe per-target time series `GET .../metrics?range=` (§4.5/§4.9) serves.\nEach host agent's own `/metrics` reports only itself with no label, since\nit has nothing else to report on; this endpoint aggregates every target\nthe sampler has ever reached, so `host_id`/`vm_id` labels are required\nhere to tell them apart.",
        "tags": [
          "system"
        ],
        "x-nextvirt-capability": "none",
        "x-nextvirt-milestone": "M1"
      }
    },
    "/ws": {
      "get": {
        "description": "Upgrades to a WebSocket carrying `hello`, then one frame per change in a\nsubscribed topic. Requires the `read` capability like every other read.",
        "operationId": "notifications",
        "responses": {
          "101": {
            "description": "WebSocket upgrade: the §2.9 notification protocol"
          },
          "401": {
            "description": "unauthenticated"
          },
          "403": {
            "description": "forbidden"
          }
        },
        "security": [
          {
            "api_key": []
          },
          {
            "session_cookie": []
          }
        ],
        "summary": "Live notification stream (contract §2.9).",
        "tags": [
          "notifications"
        ],
        "x-nextvirt-capability": "read:*",
        "x-nextvirt-milestone": "M1"
      }
    }
  },
  "servers": [
    {
      "description": "This server",
      "url": "/"
    }
  ],
  "tags": [
    {
      "description": "First-run administrator setup",
      "name": "setup"
    },
    {
      "description": "Session login, logout, identity, CSRF",
      "name": "auth"
    },
    {
      "description": "User accounts",
      "name": "users"
    },
    {
      "description": "API keys for automation",
      "name": "api-keys"
    },
    {
      "description": "Health and summary information",
      "name": "system"
    },
    {
      "description": "Hypervisor hosts",
      "name": "hosts"
    },
    {
      "description": "Groups of hosts (M3's first slice: grouping only)",
      "name": "clusters"
    },
    {
      "description": "Where disks and images live",
      "name": "datastores"
    },
    {
      "description": "What a VM interface attaches to",
      "name": "networks"
    },
    {
      "description": "L3 between ovn networks",
      "name": "routers"
    },
    {
      "description": "Registered disk images",
      "name": "images"
    },
    {
      "description": "Virtual machines",
      "name": "vms"
    },
    {
      "description": "Asynchronous operations",
      "name": "tasks"
    },
    {
      "description": "The audit trail",
      "name": "events"
    },
    {
      "description": "The live notification stream",
      "name": "notifications"
    }
  ]
}
