Skip to content

Hosts API

List Hosts

Responses

StatusDescriptionSchema
200Successful Responseany

Create Host

Request body (required), application/json, HostIn

FieldTypeRequiredDescription
addressstringyes
namestringyes
ssh_consentbooleanno
ssh_enrollbooleanno
tls_fingerprintstring | nullno
token_idstringyes
token_secretstringyes
verify_tlsbooleanno

Responses

StatusDescriptionSchema
201Successful Responseany
422Validation ErrorHTTPValidationError

Remove Host

RBAC: owner. No entitlement flag.

Forget a host and everything Proxploy cached about it.

Owner-only (authz matrix), and gated on typing the host name back: this drops every app row, VM cache row and stored credential for the host in one call, and the SSH key it deletes cannot be recovered, only re-enrolled.

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes

Request body, application/json, HostRemoveIn

FieldTypeRequiredDescription
confirmstring | nullno
forget_appsbooleanno

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError
404host not found
409host_has_apps (pass forget_apps to proceed), or confirm_required

Host Detail

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Patch Host

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes

Request body (required), application/json, HostPatchIn

FieldTypeRequiredDescription
addressstring | nullno
namestring | nullno
node_shell_enabledboolean | nullno
ssh_host_key_fingerprintstring | nullno
team_idinteger | nullno
tls_fingerprintstring | nullno

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Rotate Credentials

RBAC: owner. No entitlement flag.

Replace a host’s stored API token and/or SSH key.

Owner-only. The new API token is verified against the node BEFORE it replaces the old one: a rotation that stores an unusable credential would take the host offline with no way back except editing the database.

When rotate_ssh is set, the response carries a public_key the operator must install on the node.

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes

Request body (required), application/json, CredentialRotateIn

FieldTypeRequiredDescription
capabilitystring | nullno
rotate_sshbooleanno
token_idstring | nullno
token_secretstring | nullno

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError
404host not found
422token_id/token_secret given without the other, or nothing to rotate
502token_rejected: the new token failed verification, the old one is still in place

DELETE /api/v1/hosts/{host_id}/credentials/{kind}

Section titled “DELETE /api/v1/hosts/{host_id}/credentials/{kind}”

Delete Credential

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes
kindpathstringyes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

GET /api/v1/hosts/{host_id}/nodes/{node}/hardware

Section titled “GET /api/v1/hosts/{host_id}/nodes/{node}/hardware”

Node Hardware

Everything the node will say about itself that is not already on the Overview strip: disks, network interfaces, PCI devices, systemd services, and the subscription/DNS/time facts.

Gathered INDEPENDENTLY, on purpose. Each of these is separately refusable on a real node — a token with a narrow privilege set answers some and rejects others, and a PVE without a given path 501s — so one refusal returns that section as null and names it in unreadable rather than costing the tab its other six sections. The 502 is reserved for the case where nothing at all could be read, which is the node being down.

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes
nodepathstringyes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

POST /api/v1/hosts/{host_id}/nodes/{node}/power

Section titled “POST /api/v1/hosts/{host_id}/nodes/{node}/power”

Power Node

Reboot or power off a Proxmox NODE, not a guest (doc 02 §9, doc 08 §1 and §9 row 14).

Owner-gated, same severity class as host.remove/host.credentials: this can take the whole node, and every guest it hosts, down. Always requires typing the node’s name back, self or not — GET …/status’s is_self field lets the confirm dialog say so explicitly BEFORE the operator types anything, but the server enforces the same gate regardless of what the client already showed, since detection can miss.

The actual PVE call runs as a job (services/guestjobs.py::run_host_power), the same reasoning as every other destructive PVE action: a synchronous 200 with a bare UPID left this with no transcript in job_events and nothing to show in the bell popover (GET /jobs), unlike every other action in the product. The confirmation gate above still runs BEFORE anything is enqueued and is unchanged by the move.

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes
nodepathstringyes

Request body (required), application/json, NodePowerIn

FieldTypeRequiredDescription
commandstringyes
confirmstring | nullno

Responses

StatusDescriptionSchema
202Successful Responseany
422Validation ErrorHTTPValidationError

GET /api/v1/hosts/{host_id}/nodes/{node}/status

Section titled “GET /api/v1/hosts/{host_id}/nodes/{node}/status”

Node Status

The node’s own view of itself, for the host page.

On demand, never from the poll loop: doc 02 §3 caps a cycle at O(nodes), and model/cores/kernel/boot mode do not change between polls. The volatile figures here (load, wait, memory) are already recorded as metric samples every cycle, so polling this would buy nothing and cost a call per node.

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes
nodepathstringyes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

List Peers

The other nodes of this host’s cluster, and whether each can be added.

Read only: nothing here writes a host, a credential or an audit row. It reveals node names, addresses and fingerprints, which is the same class of information POST /hosts/probe already returns to an admin.

Every peer is probed before this answers, so the caller never renders a row whose reachability is still unknown. A failure against one peer is recorded on that peer’s row and never raised: one dead node must not hide the live ones.

Every outbound connection still goes through resolve_target(), inside tls_fingerprint_sha256 and ProxmoxClient._connect. That guard matters more here than anywhere else, because the peer address comes from the node rather than from the operator, and it is why no new guard is needed.

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Enrol Peers

Add the named nodes of this host’s cluster as hosts of their own, each with its own copy of every API token this host holds.

The write half of GET /{host_id}/peers above, and owner-scoped rather than admin for that reason: copying stored secrets into new rows is the same severity class as rotating them, which is why it sits next to rotate_credentials rather than next to discovery.

One result row per requested node, always 200. The flow is inherently partial and a 502 for the whole request would throw away the record of the peers that did work, so a failure is a row saying what happened to that node, exactly as the frontend already treats one rejected capability token as that capability’s failure and not the enrolment’s.

Never copied: the ssh_key credential, install consent, and the node shell opt-in. The SSH key is a root shell on the node, a different trust decision from an API token, and keeping them separate is the whole reason this route was allowed to exist.

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes

Request body (required), application/json, PeerEnrolIn

FieldTypeRequiredDescription
nodesstring[]yes
tls_fingerprintsobjectno

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Verify Ssh

Prove the enrolled key actually opens a root shell on the node.

The wizard used to take the operator’s word for it, so a mis-pasted authorized_keys line surfaced at the first app install instead of here, far from its cause. true is the whole command: this asks one question does the key authenticate and can we run anything, and nothing else.

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Sync Host

RBAC: operator. No entitlement flag.

Poll this host now instead of waiting out the interval.

Runs the poller’s own cycle rather than a parallel implementation, so a forced sync and a scheduled one cannot disagree about what they ingest. Operator-level: it changes no configuration; it only refreshes cache.

Despite the name, this is SYNCHRONOUS: it runs the poller cycle inline and can be slow.

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError
404host not found
502pve_error
504timeout

Host Tasks

RBAC: viewer. No entitlement flag.

The node’s own task list, including work Proxploy did not start.

Read-level on purpose: this is the same information the Proxmox UI shows anyone who can log in, and an operator debugging “why did my container restart at 3am” needs the tasks Proxploy did not cause.

A passthrough read of PVE’s own task list.

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes
limitqueryintegerno

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError
404host not found
422limit must be between 1 and 500
502pve_error

GET /api/v1/hosts/{host_id}/tasks/{upid}/log

Section titled “GET /api/v1/hosts/{host_id}/tasks/{upid}/log”

Host Task Log

RBAC: viewer. No entitlement flag.

Passthrough of one PVE task log, the missing half of the task feature.

Proxploy already archives the logs of tasks IT started, in job_events. This is for the ones it did not.

A passthrough read of PVE’s own task log.

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes
upidpathstringyes
startqueryintegerno
limitqueryintegerno

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError
404host not found
502pve_error

Test Host

Parameters

NameInTypeRequiredDescription
host_idpathintegeryes

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

List Capabilities

The static catalogue of optional capabilities the setup script can grant (key, label, why it matters, whether it is required), for the frontend to tell an operator what they give up by unticking one.

Registered ABOVE the /{host_id} wildcard below: Starlette matches in registration order, and out of order this literal path would be swallowed by GET /{host_id} with host_id=“capabilities” (same WARNING as api/vms.py’s /{vm_id}/{action} ordering hazard). Confirmed by test_capabilities_route_is_not_shadowed_by_the_host_id_wildcard.

Derived straight from CAPABILITIES, list not dict, so declaration order (monitoring first) survives into the response, and a capability added there needs no edit here. privileges/role/token are deliberately left off: the UI only needs why a capability matters, not the PVE privilege names or the identifiers that build the script.

Responses

StatusDescriptionSchema
200Successful Responseany

Probe

Request body (required), application/json, ProbeIn

FieldTypeRequiredDescription
addressstringyes
namestring | nullno
tls_fingerprintstring | nullno
token_idstringyes
token_secretstringyes
verify_tlsbooleanno

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Set Self Host

Which enrolled host, if any, Proxploy itself runs on (PXP-33): selfguard.is_self_host_node()‘s Host-record narrowing, and the second condition inside is_self().

A dedicated route rather than a hole in PATCH /settings’s allowlist (api/settings.py, PXP-36 note): that route takes free-form values, and self.host_id must name an actually-enrolled host or nothing at all, never an arbitrary string. host_id: null is “none of these”, the honest answer when Proxploy is not running on any host it manages; set_setting still writes the row (value None), so the onboarding wizard and the settings screen can tell “answered none” apart from “never asked”. Every selfguard read already treats an absent key and a None value the same way (fail open), so recording “none” changes nothing about detection, only whether the question gets asked again.

Request body (required), application/json, SelfHostIn

FieldTypeRequiredDescription
host_idinteger | nullno

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError

Token Script

The copy-paste pveum script from doc 08 §2.

POST rather than GET for the structured body, following /probe: it reads nothing and changes nothing on this side. The operator runs the result in a node shell they already own, which is the whole point: Proxploy never asks for root credentials, even transiently.

Request body (required), application/json, TokenScriptIn

FieldTypeRequiredDescription
capabilitiesstring[]no
node_powerbooleanno
node_shellbooleanno
pathstringno

Responses

StatusDescriptionSchema
200Successful Responseany
422Validation ErrorHTTPValidationError