Report a hypervisor-agent heartbeat (agent-only).
curl --request PUT \ --url https://stratum.example.com/v1/compute/nodes/example/heartbeat \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "hostname": "example", "role": "hybrid", "region": "example", "availabilityZone": "example", "architecture": "amd64", "capacity": { "totalVCPUs": 1, "reservedVCPUs": 1, "vcpuOvercommitRatio": 1, "committedVCPUs": 1, "totalMemoryBytes": 1, "reservedMemoryBytes": 1, "memoryOvercommitRatio": 1, "committedMemoryBytes": 1, "totalDiskBytes": 1, "hasGPU": true, "hasNestedVirt": true, "hasSecureBoot": true, "hasTPM": true, "architecture": "example", "kvmVersion": "example", "sampledAt": "2026-04-15T12:00:00Z" }, "capabilities": { "additionalProperty": "example" } }'Called by the per-node hypervisor-agent every 60s. Idempotent same payload repeated produces the same row. First heartbeat registers the node (state=ready); subsequent heartbeats refresh capacity + last_seen_at. Maintenance state is preserved across heartbeats.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Request Bodyrequired
Section titled “Request Bodyrequired”object
Defaults to the install’s default region when omitted.
Defaults to the install’s default AZ when omitted.
Per-heartbeat capacity snapshot the hypervisor-agent reports.
object
object
Responses
Section titled “Responses”Heartbeat accepted.
Operator-facing inventory record Appendix A.
widened the role enum from the original VM-hosting
subset (hybrid, hypervisor) to all four master-plan D5
roles. Control-plane and storage nodes self-register so they
appear in the Nodes page + capacity dashboards; the scheduler
filters by role and never places instances on them.
object
operator-friendly per-node display label (e.g.
“stratum-01”). Independent of id: id is the canonical
n-node_name metric label.
Settable via PATCH /v1/compute/nodes/{nodeId}; sticky
across redeploys.
/ D5 node role. hybrid and hypervisor run VMs;
control-plane and storage appear here for inventory +
capacity visibility but never accept placement.
VCPUs the node keeps for itself (kernel, control plane, substrate),
taken off the total before the overcommit ratio. On a single-node
install computed as max(2, 6% of the threads) unless an operator set
it; see reservationSource.
Memory the node keeps for itself, taken off the total before the overcommit ratio. On a single-node install computed as max(6 GiB, 10% of RAM) unless an operator set it, and enforced there: system.slice gets MemoryMin/MemoryLow equal to it.
Who wrote the reservation. reported: the heartbeat, on
every heartbeat (every clustered node). computed: the single-node
default, kept in step with the node’s size. operator: set through
PATCH /v1/compute/nodes/{nodeId}, or chosen at install (node.yaml
host_reservation,, written when the node first registers);
nothing else changes it.
VCPUs the node can have committed: floor((total - reserved) x overcommit), the scheduler’s own formula. Derived, never stored.
Memory the node can have committed: floor((total - reserved) x overcommit), the scheduler’s own formula. Derived, never stored.
The node’s label map, all string-valued. Two key namespaces are reserved and defined by the platform; any other key is the operator’s own and is never interpreted here.
topology.* — where the machine physically sits. topology.az is
published from the node’s declared availability zone; topology.rack and
topology.power are declared by the operator in the cluster topology.
An undeclared level simply does not exist for that cluster.
capability.* — what the hardware can do, detected by the node on every
heartbeat so a hot-installed device is picked up without restarting it:
capability.cpu.model, capability.cpu.sockets, capability.gpu.present,
capability.mem.ecc, capability.sec.tpm, capability.virt.nested, and
the rest of the detected set.
Each key has a kind, declared by the platform and never inferred from
the value. Descriptive keys are matched by equality and
consumed by nothing. Boolean keys are true or false. Countable
keys carry a whole number and are consumed on placement:
capability.net.sriov_vfs (virtual functions, each attaching to a single
guest) and capability.mem.hugepages_1g. The accelerator namespaces
capability.gpu.<vendor-model> and capability.accel.<vendor-model> are
countable and open — a new model is data, not a schema change — and are
declared by an operator until device enumeration ships.
capability.cpu.cores and capability.cpu.threads are descriptive, not
countable: vCPU is accounted separately and with an overcommit ratio, so
counting them here would account the same resource twice under two
different rules.
A countable key that is absent is not zero. Absent means the node did not report it — the control plane runs in a container that often cannot read host sysfs — while zero means the node genuinely has none.
These keys are operational rather than physical:
capability.migration.transport—listeningordown; whether this node can RECEIVE a live migration.capability.host.binaries_commit— the git commit the node’s/usr/local/binbinaries were built from,mixedwhen they disagree with each other, orunknownwhen they are too old to report it.capability.host.binaries_stale—truewhen those binaries are not the build the node should run: on a mutable node the deployed control plane’s (a control-plane deploy replaces a container image and does NOT touch host binaries, so they drift from the day a node is built), on an image-mode node the host binaries of the release the cluster runs, as theHostBinariesStalereadiness finding judges them.capability.host.binaries_unknownnames the ones that could not report at all — those are the binaries most likely to be missing a subcommand a runbook names.capability.host.binaries_source— what fixes a stale node.control-plane-imageon a mutable node: the host-helper installs the control plane’s copies on its next pass.os-imageon an image-mode (bootc) node: the binaries are part of the read-only stratum-os image and nothing on the node changes them, so stale there lasts until an upgrade changes them.overlayon an image-mode node whose binaries were switched in live from a release: the next upgrade switches them again.capability.host.dev_mode—truewhen the node’s control plane runs in dev mode (node.yamldev_mode,): the substrate’s web UIs on loopback, the OpenBao UI, and credentials inline in the admin substrate-tools response. Reported by the control plane’s own heartbeat, so a node without a control plane has no key. Readiness lists each such node asDevModeOn.
Where a declared label and detection disagree the declared value wins and the override is logged.
Absent keys mean “not reported”, never “false” — a node whose host-helper
has not run a pass publishes no capability.host.binaries_* keys at all.
renamed every key under these prefixes; the previous unprefixed
names (hasGpu, cpuModel, migrationTransport,…) are no longer
published.
object
How many units of each countable capability are currently allocated
on this node — capability.net.sriov_vfs,
capability.mem.hugepages_1g, capability.gpu.<vendor-model> and the
rest of the countable set.
Deliberately separate from capabilities: that map is what the node
SAYS about itself, this is what the platform has handed out. Mixing them
would make a node’s self-report depend on scheduling decisions it never
made.
Free units are capabilities[key] - committedCapabilities[key]. An
absent key means none are allocated, and a node holding nothing omits
the object entirely.
A count here can exceed the declared total if an operator shrinks a declared capability below what is already allocated. That is a supported state, not a corruption: existing guests keep what they hold and nothing new is placed until reality catches up. Clamp at zero when computing free rather than rendering negative capacity.
object
The node’s latest shutdown or reboot through the API, absent
when it never had one. In progress while state is shutting-down,
rebooting or off.
object
shutting-down— recorded; the node powers off atnotBefore.rebooting— recorded; the node reboots atnotBefore, and staysrebootinguntil it has booted again.off— a shutdown whose node stopped heartbeating. It staysoffuntil someone powers it on (seepowerOn).done— the node booted again after the action.failed— the host did not power off or reboot;messagesays why.
Bumped by every new action on the node.
When the host acts: a short delay after the request, so the 202 is answered before the node (and, on a single node, the API) goes down.
When a shutdown’s node was seen gone.
What the action is doing or waiting for, or why it failed.
True when this node is the only control-plane node up (a single-node install, or the last node of a cluster): the console and the API go down with it, and nothing answers until it is back.
How the node comes back after a shutdown: its power button, and Wake-on-LAN to its out-of-band NIC’s MAC when the node reported one.
Example
{ "role": "hybrid", "architecture": "amd64", "state": "ready", "reservationSource": "reported", "powerAction": { "action": "shutdown", "state": "shutting-down" }}Bad request.
object
The findings of a rejected IAM policy document (custom and resource policies).
One finding of a rejected IAM policy document. A rejection lists every finding, so the author fixes the document in one round trip.
object
The finding, e.g. INVALID_DOCUMENT, UNKNOWN_ACTION, ACTION_NOT_APPLICABLE.
Index of the statement at fault; -1 for the document as a whole.
The offending action, where one is at fault (ACTION_NOT_APPLICABLE).
Field errors, for codes that carry them: each problem at its field
(see FieldError). Documented on the responses that set it, e.g.
approveNodeJoin’s INVALID_INPUT and JOIN_APPROVAL_REFUSED.
Absent otherwise.
One field’s problem, the stage-2 installer API’s shape.
field is the field’s path, message what is wrong with it.
object
The field’s path, e.g. links[0].interface, disks[1].confirm,
mgmt.address or role; empty for the whole request.
Structured facts about the refusal, for codes that carry them. The
shape is fixed per code and documented on the response that answers
it, e.g. IMAGE_POOL_MISMATCH (see ImagePoolMismatchDetails) and
IMAGE_IN_USE. Absent for codes that carry none.
object
Example
{ "issues": [ { "code": "ACTION_NOT_APPLICABLE", "action": "compute:CreateVpc" } ]}