Register a machine to join the cluster (join listener only).
curl --request POST \ --url https://stratum.example.com/v1/join/registrations \ --header 'Content-Type: application/json' \ --data '{ "pake": { "session": "pk-4f7k2q9mabcdefgh", "ke3": "example" }, "release": "0.5.0", "selfUpgrade": true, "hostname": "example", "systemSerial": "example", "inventory": { "uefi": true, "disks": [ { "name": "nvme1n1", "path": "/dev/nvme1n1", "byId": [ "example" ], "model": "example", "serial": "example", "wwn": "example", "transport": "example", "sizeBytes": 1, "rotational": true, "removable": true, "readOnly": true, "mountpoints": [ "example" ], "holders": [ { "kind": "example", "device": "example", "detail": "example" } ], "partitions": [ { "number": 1, "name": "example", "path": "example", "sizeBytes": 1, "fsType": "example", "label": "example", "partLabel": "example" } ], "stack": [ { "name": "example", "path": "example", "type": "example" } ] } ], "nics": [ { "name": "enp87s0", "mac": "example", "permanentMac": "example", "operState": "example", "link": "example", "speedMbps": 1, "mtu": 1, "driver": "example", "addresses": [ "example" ], "wireless": true, "routes": [ "example" ] } ], "cpu": { "model": "example", "sockets": 1, "cores": 1, "threads": 1, "virtualization": "example" }, "memoryBytes": 1, "osDiskLayout": "compute-tail" } }'. Served only on the join listener
(TLS, port 9444, open while a join code is live), never on the API.
No session: the proof of the code (the KE3 of a POST /v1/join/pake
exchange on this connection) authenticates the call, and the TLS client
certificate is the machine’s key, which every later call is pinned to.
Refused: a code not proven (401 JOIN_CODE_INVALID), an expired or
revoked code (401, with the reason); a proof for no waiting exchange of
this key on this connection (409 JOIN_PAKE_RESTART); a release other
than the cluster’s (409 RELEASE_MISMATCH, naming the
cluster’s release and its ISO path); a key whose registration was
rejected (409); more than 16 registrations waiting, or too many from one
address (429); an upgrade in progress (503, retry later). The same key
registering again gets its registration back. Nothing secret is
returned: the pairing code is derived from the key and the PAKE session.
Request Bodyrequired
Section titled “Request Bodyrequired”A joiner’s registration. Sent over TLS 1.3 to the join
listener with the joiner’s own client certificate, on the connection the
joiner proved the code on (POST /v1/join/pake), and once the listener
has proven it holds the code. The registration is pinned to that
certificate’s key.
object
The joiner’s proof of its join code: the OPAQUE KE3 for the exchange the listener answered. Sent on the same TLS connection as that exchange, with the same client certificate; a session is used once.
object
The exchange, from the POST /v1/join/pake answer.
Example
pk-4f7k2q9mabcdefghThe OPAQUE KE3 message (RFC 9807), base64.
The release the joiner was installed from. Another release than the
cluster’s is refused (RELEASE_MISMATCH), unless the joiner sent
selfUpgrade and the release is older than the cluster’s and within
its upgrade rules: it is then registered as needs-upgrade.
Example
0.5.0The joiner can upgrade itself to the cluster’s release before it registers. Absent or false: exactly the cluster’s release or a refusal, as before.
A joining machine’s hardware, as it reported it at registration (the inventory). The approval’s assignment is checked against it.
object
One whole disk of a joining machine, as its discovery reports it.
object
Example
nvme1n1Example
/dev/nvme1n1Live mounts on the disk or anything stacked on it. A mounted disk is the system disk.
What already holds data on the disk. Empty means blank.
object
The disk’s partitions. On an OS disk they say what stage 1 left free at its end (the compute tail, or nothing with the whole-disk layout).
object
Every md array and device-mapper device built on the disk, deepest first.
object
One network interface of a joining machine.
object
Example
enp87s0up with a carrier, down without one, unknown.
The IPv4 destinations the main routing table sends out of this NIC (CIDRs; a host route as /32), without the default route.
object
vmx, svm, or empty.
How stage 1 laid out the OS disks, from the joiner’s install
record: compute-tail keeps the end of each OS disk for the compute
pool, whole-disk gives the disks whole to the system, so compute is a
data disk (a compute pool, or Ceph on a disk marked ceph-osd). Absent
on a machine with no record; the founder then reads it off the disks’
partitions.
Responses
Section titled “Responses”This key’s existing registration.
A joiner’s view of its registration.
object
needs-upgrade: the joiner runs an older release and is upgrading
itself to the cluster’s (upgrade says to which). It is not approvable
until it registers again on the cluster’s release, when it becomes
pending. Only a joiner that sent selfUpgrade is ever in it.
Why it was rejected.
The upgrade a joiner on an older release makes before it can register: from the release it was installed with to
the release the cluster runs. Only a joiner that sent selfUpgrade: true
is ever offered one; any other joiner on another release is refused with
RELEASE_MISMATCH.
object
The release the joiner reported.
The release the cluster runs, which the joiner upgrades to.
Where the joiner fetches the release from (the release mirror’s URL of the release directory). Every file is checked against the release’s signed manifest. Empty when the founder names none.
The founder gives the release itself, when no release mirror can (a lab,
an air gap): fetch it from GET /v1/join/registrations/{joinId}/release/{path}
on the join listener, the release layout a mirror publishes
(payload/manifest.json, os/blobs/..., sysext/...). source is then
empty. Served only to this registration while it is needs-upgrade,
read only, a few files at once (503 RELEASE_BUSY: retry); ranges are
accepted so a download resumes. The joiner verifies the manifest’s
signature against the release keys of its own OS and every file against
the manifest, as it does from a mirror.
What an approved joiner receives when its turn comes, only over the channel pinned to its key: its node.yaml, built from the assignment, and the join bundle, with its transport leaf issued for its node id.
object
object
The cluster’s id (e.g. cl-3vin6yzz). The joiner MUST adopt it as its own so every node’s Patroni scope matches — otherwise each node manages a separate single-node cluster and the replica never finds the leader (M1).
The role this node joins as (from the redeemed token).
The cluster members to peer with: every member in joining or active, this node included (admitted as joining when this bundle was issued), in join order.
object
Availability zone; empty on single-AZ installs.
Management-plane address.
The node’s traffic-class bindings, keyed by class
(storage, overlay). Carried in the join bundle so a joiner’s
ClusterConfig matches the founder’s.
Without it a joined node has no networks entry, so ClassAddr falls
through to mgmt_ip and EVERY traffic class silently collapses onto the
management plane. That put Ceph’s public_network — and therefore all
client I/O — on the 1G mgmt NIC while only OSD replication used the 10G
storage VLAN, with the cluster reporting HEALTH_OK throughout.
object
One traffic class’s binding on a node (-TOPO-1).
object
The address this class uses on the node (a bare IP, not a CIDR).
Host interface carrying the class, when one is declared.
The node’s DECLARED label map (topology.rack,
topology.power, and any operator-namespace key). Carried in the join
bundle so a joiner’s ClusterConfig matches the founder’s.
Without it a joined node has NO declared labels at all, and every consumer degrades to “the founder has a rack, nobody else does” — silently. Rack spread sees one value and two empties, so the anti-affinity guarantee reports success and places anywhere; a CRUSH hierarchy built from labels finds one rack and is refused as a half-declared topology; a rack failure domain can never be satisfied; and a taint declared on a joiner repels nothing.
topology.az is NOT carried here — the AZ rides its own az field, and
two records of one fact is the bug each cost a
session.
This is the THIRD field to go missing from this schema the same way (networks in the bundle, networks in the status report). A field added to config.ClusterNode does not reach a joiner unless someone remembers this file.
object
Per-node boot progress, probed from the node’s /startupz.
ready = serving; starting = still booting (see bootPhase);
unreachable = the node didn’t answer (down, early-boot before the
listener, or wedged). Lets the UI tell “booting / stuck / down”
apart from a vague “unknown”. Omitted when the boot-health probe
isn’t wired (single-node).
The in-flight startup phase when bootState=starting (e.g. migrations).
How long the current boot phase has run (ms); climbs while a phase is stuck.
The node’s last boot duration (ms), when bootState=ready.
The Stratum-owned domain the cluster serves its API, console and S3 endpoints under. The joiner ADOPTS the founder’s value rather than restating it in its own manifest, so the cluster cannot end up with two nodes disagreeing about its domain. Load-bearing since the parent domain lost its built-in default: a joining node’s control plane refuses to start without one, and a joiner manifest legitimately omits the field.
The cluster’s region. Adopted by the joiner for the same reason as parentDomain: it is cluster identity, and a node that decides it locally can disagree with the cluster it just joined. The region is baked into every ARN the cluster mints and into every delegated zone’s served FQDN, so two nodes disagreeing is not a cosmetic split. A joiner’s node.yaml legitimately omits it, and the built-in fallback is the test-region-1 placeholder — so before this field a joiner on a cluster with a real region silently ran in a different one. Empty on a pre- founder, where the joiner keeps its previous local behaviour.
The resolvers the cluster’s VPC recursors forward every name they do not serve to: the founder’s node.yaml dns.upstream_servers, as its control plane loaded them (network.default_dns_servers). Adopted by the joiner for the same reason as parentDomain. Every control plane runs the recursor reconciler, which writes this list into the one shared “.” forward zone, so a node holding another list (or none) would flip that zone on every tick. An EMPTY list is a value, not an omission: the cluster resolves from the root servers, and the joiner adopts that too. Absent only from a pre- founder, where the joiner keeps the list its own manifest names.
PEM cluster CA bundle the node trusts (HA installs with an internal PKI).
PEM transport leaf issued to this node by the cluster CA (mTLS identity).
PEM private key for nodeCert. Sensitive — the node persists it mode 0600.
The cluster’s Postgres superuser/replication password (M1). Sensitive — a joining node uses it so its Patroni replica authenticates streaming replication against the primary instead of minting a fresh password the cluster wouldn’t accept. The node persists it only as the podman secret (mode 0600). Replaced by cert-based replication auth in a later hardening.
The cluster’s bootstrap IAM admin email: the email of the admin the founder’s control plane found at start, not that node’s configured one. Adopted by the joiner for the same reason as adminPassword: the admin identity lives once in the cluster Postgres. Before this field a joiner always wrote the built-in default email, and control planes then ensured the admin by email, so on a cluster whose admin had another email its control plane created a second bootstrap admin under the default name. Since a control plane finds the admin by its stamp whatever its env names. Empty on a pre- founder, where the joiner uses that founder’s default, admin@stratum.local.
The cluster’s bootstrap IAM admin password. Sensitive — the admin identity is shared (one iam.users row + argon2 hash in the cluster Postgres), so a joining node MUST adopt the founder’s admin password rather than generate its own; otherwise each node’s controlplane.env carries a different STRATUM_IAM_ADMIN_PASSWORD and only the founder’s actually authenticates against the shared hash. The node persists it only in controlplane.env + bootstrap-credentials.txt (mode 0600). Empty on a Postgres-only replica (which runs no control plane).
The cluster’s OpenBao token. Sensitive — a full-stack joiner runs a control plane that needs OpenBao access, but increment 1 keeps OpenBao a single instance behind the.242 VIP (raft is the separate EPIC). Rather than init its own, the joiner adopts this token and points STRATUM_OPENBAO_ADDR at the VIP. The node persists it only in controlplane.env (mode 0600). Empty on a Postgres-only replica (which runs no control plane). a scoped, periodic token (policy stratum-cp), never the root token; a founder still on root refuses the bundle until its token converge has run.
A token minted for this joiner’s host tools (policy stratum-node: the raft doctor’s peer list, removing a failed member from the OpenBao raft, the unseal key in the cluster KV). Sensitive. The node persists it only in /etc/stratum/secrets/openbao-node.token (mode 0600) and its host-helper renews it. Absent from a founder that predates.
The cluster’s axdnsd ENCRYPTION_KEY, 64 hex characters: the AES-256 key every node’s authoritative DNS seals and opens the DNSSEC private keys and TSIG secrets in the shared database with. Sensitive. The serving control plane reads it from its own node’s axdnsd, because nothing can re-derive it: the founder derived it from the Postgres password it had at install, and a joiner that derived it from postgresPassword after a rotation got a key the cluster’s rows were never sealed with. The joiner persists it only in axdnsd.env (mode 0600). Absent from a pre- founder, where the joiner still derives the key from postgresPassword.
The cluster’s OpenBao Shamir unseal key. Sensitive — when OpenBao runs as a 3-node integrated- storage raft every member shares the founder’s single unseal key, so a joiner needs it to unseal its local voter right after operator raft join. The joiner persists it NOT in plaintext: it TPM-seals the key to its own TPM immediately on receipt (the boot-time stratum-openbao-unseal.service then re-unseals on every reboot with no human). Transits the same already-authenticated join channel that carries openBaoToken. Empty on increment-1 single-instance clusters (the joiner used the.242 VIP, ran no local OpenBao) and on a Postgres-only replica.
The membership generation this node was admitted at. Its join is done when every member in joining or active reports at least this generation as applied (GET /v1/admin/cluster/members).
The cluster’s keepalived service VIPs (HA installs,). firstboot renders the joiner’s keepalived from these so the API + Postgres VIPs match the rest of the cluster.
object
Control-plane API VIP.
Postgres VIP (follows the Patroni primary, M1.2 /).
OpenBao VIP (WS-B).
OVN northbound DB VIP (WS-C).
OVN southbound DB VIP (WS-C).
Vmselect read VIP for the VictoriaMetrics cluster. The controlplane’s PromQL read path follows it to a healthy vmselect.
Routed-host floating-IP next hop: the LAN router routes the floating ranges to it, and it follows the node whose chassis holds the VPC gateways. Absent on a bridged cluster.
Example
{ "state": "needs-upgrade", "upgrade": { "from": "0.5.0", "to": "0.6.0", "source": "https://mirror.example/releases/stratum/0.6.0/bundle" }, "delivery": { "bundle": { "clusterId": "cl-3vin6yzz", "role": "control-plane", "nodes": [ { "nodeId": "n-abc12def", "role": "control-plane", "networks": { "additionalProperty": { "ip": "192.0.2.111", "interface": "vlan90" } }, "bootState": "ready", "bootPhase": "migrations" } ], "parentDomain": "stratum.example.com", "region": "lab-1", "dnsUpstreamServers": [ "192.0.2.53" ], "adminEmail": "admin@cloud.internal" } }}Registered; waiting for approval.
A joiner’s view of its registration.
object
needs-upgrade: the joiner runs an older release and is upgrading
itself to the cluster’s (upgrade says to which). It is not approvable
until it registers again on the cluster’s release, when it becomes
pending. Only a joiner that sent selfUpgrade is ever in it.
Why it was rejected.
The upgrade a joiner on an older release makes before it can register: from the release it was installed with to
the release the cluster runs. Only a joiner that sent selfUpgrade: true
is ever offered one; any other joiner on another release is refused with
RELEASE_MISMATCH.
object
The release the joiner reported.
The release the cluster runs, which the joiner upgrades to.
Where the joiner fetches the release from (the release mirror’s URL of the release directory). Every file is checked against the release’s signed manifest. Empty when the founder names none.
The founder gives the release itself, when no release mirror can (a lab,
an air gap): fetch it from GET /v1/join/registrations/{joinId}/release/{path}
on the join listener, the release layout a mirror publishes
(payload/manifest.json, os/blobs/..., sysext/...). source is then
empty. Served only to this registration while it is needs-upgrade,
read only, a few files at once (503 RELEASE_BUSY: retry); ranges are
accepted so a download resumes. The joiner verifies the manifest’s
signature against the release keys of its own OS and every file against
the manifest, as it does from a mirror.
What an approved joiner receives when its turn comes, only over the channel pinned to its key: its node.yaml, built from the assignment, and the join bundle, with its transport leaf issued for its node id.
object
object
The cluster’s id (e.g. cl-3vin6yzz). The joiner MUST adopt it as its own so every node’s Patroni scope matches — otherwise each node manages a separate single-node cluster and the replica never finds the leader (M1).
The role this node joins as (from the redeemed token).
The cluster members to peer with: every member in joining or active, this node included (admitted as joining when this bundle was issued), in join order.
object
Availability zone; empty on single-AZ installs.
Management-plane address.
The node’s traffic-class bindings, keyed by class
(storage, overlay). Carried in the join bundle so a joiner’s
ClusterConfig matches the founder’s.
Without it a joined node has no networks entry, so ClassAddr falls
through to mgmt_ip and EVERY traffic class silently collapses onto the
management plane. That put Ceph’s public_network — and therefore all
client I/O — on the 1G mgmt NIC while only OSD replication used the 10G
storage VLAN, with the cluster reporting HEALTH_OK throughout.
object
One traffic class’s binding on a node (-TOPO-1).
object
The address this class uses on the node (a bare IP, not a CIDR).
Host interface carrying the class, when one is declared.
The node’s DECLARED label map (topology.rack,
topology.power, and any operator-namespace key). Carried in the join
bundle so a joiner’s ClusterConfig matches the founder’s.
Without it a joined node has NO declared labels at all, and every consumer degrades to “the founder has a rack, nobody else does” — silently. Rack spread sees one value and two empties, so the anti-affinity guarantee reports success and places anywhere; a CRUSH hierarchy built from labels finds one rack and is refused as a half-declared topology; a rack failure domain can never be satisfied; and a taint declared on a joiner repels nothing.
topology.az is NOT carried here — the AZ rides its own az field, and
two records of one fact is the bug each cost a
session.
This is the THIRD field to go missing from this schema the same way (networks in the bundle, networks in the status report). A field added to config.ClusterNode does not reach a joiner unless someone remembers this file.
object
Per-node boot progress, probed from the node’s /startupz.
ready = serving; starting = still booting (see bootPhase);
unreachable = the node didn’t answer (down, early-boot before the
listener, or wedged). Lets the UI tell “booting / stuck / down”
apart from a vague “unknown”. Omitted when the boot-health probe
isn’t wired (single-node).
The in-flight startup phase when bootState=starting (e.g. migrations).
How long the current boot phase has run (ms); climbs while a phase is stuck.
The node’s last boot duration (ms), when bootState=ready.
The Stratum-owned domain the cluster serves its API, console and S3 endpoints under. The joiner ADOPTS the founder’s value rather than restating it in its own manifest, so the cluster cannot end up with two nodes disagreeing about its domain. Load-bearing since the parent domain lost its built-in default: a joining node’s control plane refuses to start without one, and a joiner manifest legitimately omits the field.
The cluster’s region. Adopted by the joiner for the same reason as parentDomain: it is cluster identity, and a node that decides it locally can disagree with the cluster it just joined. The region is baked into every ARN the cluster mints and into every delegated zone’s served FQDN, so two nodes disagreeing is not a cosmetic split. A joiner’s node.yaml legitimately omits it, and the built-in fallback is the test-region-1 placeholder — so before this field a joiner on a cluster with a real region silently ran in a different one. Empty on a pre- founder, where the joiner keeps its previous local behaviour.
The resolvers the cluster’s VPC recursors forward every name they do not serve to: the founder’s node.yaml dns.upstream_servers, as its control plane loaded them (network.default_dns_servers). Adopted by the joiner for the same reason as parentDomain. Every control plane runs the recursor reconciler, which writes this list into the one shared “.” forward zone, so a node holding another list (or none) would flip that zone on every tick. An EMPTY list is a value, not an omission: the cluster resolves from the root servers, and the joiner adopts that too. Absent only from a pre- founder, where the joiner keeps the list its own manifest names.
PEM cluster CA bundle the node trusts (HA installs with an internal PKI).
PEM transport leaf issued to this node by the cluster CA (mTLS identity).
PEM private key for nodeCert. Sensitive — the node persists it mode 0600.
The cluster’s Postgres superuser/replication password (M1). Sensitive — a joining node uses it so its Patroni replica authenticates streaming replication against the primary instead of minting a fresh password the cluster wouldn’t accept. The node persists it only as the podman secret (mode 0600). Replaced by cert-based replication auth in a later hardening.
The cluster’s bootstrap IAM admin email: the email of the admin the founder’s control plane found at start, not that node’s configured one. Adopted by the joiner for the same reason as adminPassword: the admin identity lives once in the cluster Postgres. Before this field a joiner always wrote the built-in default email, and control planes then ensured the admin by email, so on a cluster whose admin had another email its control plane created a second bootstrap admin under the default name. Since a control plane finds the admin by its stamp whatever its env names. Empty on a pre- founder, where the joiner uses that founder’s default, admin@stratum.local.
The cluster’s bootstrap IAM admin password. Sensitive — the admin identity is shared (one iam.users row + argon2 hash in the cluster Postgres), so a joining node MUST adopt the founder’s admin password rather than generate its own; otherwise each node’s controlplane.env carries a different STRATUM_IAM_ADMIN_PASSWORD and only the founder’s actually authenticates against the shared hash. The node persists it only in controlplane.env + bootstrap-credentials.txt (mode 0600). Empty on a Postgres-only replica (which runs no control plane).
The cluster’s OpenBao token. Sensitive — a full-stack joiner runs a control plane that needs OpenBao access, but increment 1 keeps OpenBao a single instance behind the.242 VIP (raft is the separate EPIC). Rather than init its own, the joiner adopts this token and points STRATUM_OPENBAO_ADDR at the VIP. The node persists it only in controlplane.env (mode 0600). Empty on a Postgres-only replica (which runs no control plane). a scoped, periodic token (policy stratum-cp), never the root token; a founder still on root refuses the bundle until its token converge has run.
A token minted for this joiner’s host tools (policy stratum-node: the raft doctor’s peer list, removing a failed member from the OpenBao raft, the unseal key in the cluster KV). Sensitive. The node persists it only in /etc/stratum/secrets/openbao-node.token (mode 0600) and its host-helper renews it. Absent from a founder that predates.
The cluster’s axdnsd ENCRYPTION_KEY, 64 hex characters: the AES-256 key every node’s authoritative DNS seals and opens the DNSSEC private keys and TSIG secrets in the shared database with. Sensitive. The serving control plane reads it from its own node’s axdnsd, because nothing can re-derive it: the founder derived it from the Postgres password it had at install, and a joiner that derived it from postgresPassword after a rotation got a key the cluster’s rows were never sealed with. The joiner persists it only in axdnsd.env (mode 0600). Absent from a pre- founder, where the joiner still derives the key from postgresPassword.
The cluster’s OpenBao Shamir unseal key. Sensitive — when OpenBao runs as a 3-node integrated- storage raft every member shares the founder’s single unseal key, so a joiner needs it to unseal its local voter right after operator raft join. The joiner persists it NOT in plaintext: it TPM-seals the key to its own TPM immediately on receipt (the boot-time stratum-openbao-unseal.service then re-unseals on every reboot with no human). Transits the same already-authenticated join channel that carries openBaoToken. Empty on increment-1 single-instance clusters (the joiner used the.242 VIP, ran no local OpenBao) and on a Postgres-only replica.
The membership generation this node was admitted at. Its join is done when every member in joining or active reports at least this generation as applied (GET /v1/admin/cluster/members).
The cluster’s keepalived service VIPs (HA installs,). firstboot renders the joiner’s keepalived from these so the API + Postgres VIPs match the rest of the cluster.
object
Control-plane API VIP.
Postgres VIP (follows the Patroni primary, M1.2 /).
OpenBao VIP (WS-B).
OVN northbound DB VIP (WS-C).
OVN southbound DB VIP (WS-C).
Vmselect read VIP for the VictoriaMetrics cluster. The controlplane’s PromQL read path follows it to a healthy vmselect.
Routed-host floating-IP next hop: the LAN router routes the floating ranges to it, and it follows the node whose chassis holds the VPC gateways. Absent on a bridged cluster.
Example
{ "state": "needs-upgrade", "upgrade": { "from": "0.5.0", "to": "0.6.0", "source": "https://mirror.example/releases/stratum/0.6.0/bundle" }, "delivery": { "bundle": { "clusterId": "cl-3vin6yzz", "role": "control-plane", "nodes": [ { "nodeId": "n-abc12def", "role": "control-plane", "networks": { "additionalProperty": { "ip": "192.0.2.111", "interface": "vlan90" } }, "bootState": "ready", "bootPhase": "migrations" } ], "parentDomain": "stratum.example.com", "region": "lab-1", "dnsUpstreamServers": [ "192.0.2.53" ], "adminEmail": "admin@cloud.internal" } }}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" } ]}Missing or invalid token.
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" } ]}Headers
Section titled “Headers”Conflicting state.
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" } ]}Request rate limit exceeded. The API-boundary throttle caps request throughput per principal and per project; code is RATE_LIMITED (distinct from the quota engine’s QUOTA_EXCEEDED). The Retry-After header carries the seconds to wait. Applies to any authenticated endpoint — it is enforced by middleware, not per-path.
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" } ]}Headers
Section titled “Headers”Seconds to wait before retrying.
Upstream temporarily unavailable (driver / scheduler / dependency).
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" } ]}