Skip to content

Report a hypervisor-agent heartbeat (agent-only).

PUT
/v1/compute/nodes/{nodeId}/heartbeat
Code sample: Shell / cURL
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.

nodeId
required
string
/^n-[a-z0-9-]+$/
Media typeapplication/json
object
hostname
required
string
role
required
string
Allowed values: hybrid hypervisor control-plane storage
region

Defaults to the install’s default region when omitted.

string
availabilityZone

Defaults to the install’s default AZ when omitted.

string
architecture
required
string
Allowed values: amd64 arm64
capacity

Per-heartbeat capacity snapshot the hypervisor-agent reports.

object
totalVCPUs
integer
reservedVCPUs
integer
vcpuOvercommitRatio
number format: float
committedVCPUs
integer
totalMemoryBytes
integer format: int64
reservedMemoryBytes
integer format: int64
memoryOvercommitRatio
number format: float
committedMemoryBytes
integer format: int64
totalDiskBytes
integer format: int64
hasGPU
boolean
hasNestedVirt
boolean
hasSecureBoot
boolean
hasTPM
boolean
architecture
string
kvmVersion
string
sampledAt
string format: date-time
capabilities
object
key
additional properties
string

Heartbeat accepted.

Media typeapplication/json

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
id
required
string
/^n-[a-z0-9-]+$/
name
required

operator-friendly per-node display label (e.g. “stratum-01”). Independent of id: id is the canonical n- identity used for joins/counts/grouping; name is the cosmetic label surfaced on the Nodes page, log-stream selectors, and the vmagent node_name metric label. Settable via PATCH /v1/compute/nodes/{nodeId}; sticky across redeploys.

string
>= 2 characters <= 64 characters /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}[a-zA-Z0-9]$/
hostname
required
string
role
required

/ D5 node role. hybrid and hypervisor run VMs; control-plane and storage appear here for inventory + capacity visibility but never accept placement.

string
Allowed values: hybrid hypervisor control-plane storage
region
required
string
availabilityZone
required
string
architecture
required
string
Allowed values: amd64 arm64
state
required
string
Allowed values: ready draining cordoned maintenance unknown
totalVCPUs
integer
reservedVCPUs

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.

integer
vcpuOvercommitRatio
number format: float
committedVCPUs
integer
totalMemoryBytes
integer format: int64
reservedMemoryBytes

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.

integer format: int64
memoryOvercommitRatio
number format: float
committedMemoryBytes
integer format: int64
reservationSource

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.

string
Allowed values: reported computed operator
allocatableVCPUs

VCPUs the node can have committed: floor((total - reserved) x overcommit), the scheduler’s own formula. Derived, never stored.

integer format: int64
allocatableMemoryBytes

Memory the node can have committed: floor((total - reserved) x overcommit), the scheduler’s own formula. Derived, never stored.

integer format: int64
capabilities

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 — listening or down; whether this node can RECEIVE a live migration.
  • capability.host.binaries_commit — the git commit the node’s /usr/local/bin binaries were built from, mixed when they disagree with each other, or unknown when they are too old to report it.
  • capability.host.binaries_stale — true when 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 the HostBinariesStale readiness finding judges them. capability.host.binaries_unknown names 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-image on a mutable node: the host-helper installs the control plane’s copies on its next pass. os-image on 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. overlay on an image-mode node whose binaries were switched in live from a release: the next upgrade switches them again.
  • capability.host.dev_mode — true when the node’s control plane runs in dev mode (node.yaml dev_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 as DevModeOn.

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
key
additional properties
string
committedCapabilities

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
key
additional properties
integer
powerAction

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
nodeId
required
string
action
required
string
Allowed values: shutdown reboot
state
required
  • shutting-down — recorded; the node powers off at notBefore.
  • rebooting — recorded; the node reboots at notBefore, and stays rebooting until it has booted again.
  • off — a shutdown whose node stopped heartbeating. It stays off until someone powers it on (see powerOn).
  • done — the node booted again after the action.
  • failed — the host did not power off or reboot; message says why.
string
Allowed values: shutting-down rebooting off done failed
generation
required

Bumped by every new action on the node.

integer format: int64
reason
string
force
boolean
requestedBy
required
string
requestedAt
required
string format: date-time
notBefore
required

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.

string format: date-time
offAt

When a shutdown’s node was seen gone.

string format: date-time
nullable
finishedAt
string format: date-time
nullable
message
required

What the action is doing or waiting for, or why it failed.

string
controlPlaneOffline
required

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.

boolean
powerOn
required

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.

string
lastSeenAt
required
string format: date-time
createdAt
required
string format: date-time
updatedAt
required
string format: date-time

Example

{
"role": "hybrid",
"architecture": "amd64",
"state": "ready",
"reservationSource": "reported",
"powerAction": {
"action": "shutdown",
"state": "shutting-down"
}
}

Bad request.

Media typeapplication/problem+json
object
type
required
string format: uri
title
required
string
status
required
integer
>= 400 <= 599
detail
string
instance
string format: uri-reference
requestId
required
string
code
required
string
issues

The findings of a rejected IAM policy document (custom and resource policies).

Array<object>

One finding of a rejected IAM policy document. A rejection lists every finding, so the author fixes the document in one round trip.

object
code
required

The finding, e.g. INVALID_DOCUMENT, UNKNOWN_ACTION, ACTION_NOT_APPLICABLE.

string
statement
required

Index of the statement at fault; -1 for the document as a whole.

integer
>= -1
detail
required
string
action

The offending action, where one is at fault (ACTION_NOT_APPLICABLE).

string
errors

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.

Array<object>

One field’s problem, the stage-2 installer API’s shape. field is the field’s path, message what is wrong with it.

object
field
required

The field’s path, e.g. links[0].interface, disks[1].confirm, mgmt.address or role; empty for the whole request.

string
message
required
string
details

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
key
additional properties
any

Example

{
"issues": [
{
"code": "ACTION_NOT_APPLICABLE",
"action": "compute:CreateVpc"
}
]
}