Hosts API
GET /api/v1/hosts
Section titled “GET /api/v1/hosts”List Hosts
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
POST /api/v1/hosts
Section titled “POST /api/v1/hosts”Create Host
Request body (required), application/json, HostIn
| Field | Type | Required | Description |
|---|---|---|---|
address | string | yes | |
name | string | yes | |
ssh_consent | boolean | no | |
ssh_enroll | boolean | no | |
tls_fingerprint | string | null | no | |
token_id | string | yes | |
token_secret | string | yes | |
verify_tls | boolean | no |
Responses
| Status | Description | Schema |
|---|---|---|
| 201 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
DELETE /api/v1/hosts/{host_id}
Section titled “DELETE /api/v1/hosts/{host_id}”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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes |
Request body, application/json, HostRemoveIn
| Field | Type | Required | Description |
|---|---|---|---|
confirm | string | null | no | |
forget_apps | boolean | no |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
| 404 | host not found | |
| 409 | host_has_apps (pass forget_apps to proceed), or confirm_required |
GET /api/v1/hosts/{host_id}
Section titled “GET /api/v1/hosts/{host_id}”Host Detail
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
PATCH /api/v1/hosts/{host_id}
Section titled “PATCH /api/v1/hosts/{host_id}”Patch Host
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes |
Request body (required), application/json, HostPatchIn
| Field | Type | Required | Description |
|---|---|---|---|
address | string | null | no | |
name | string | null | no | |
node_shell_enabled | boolean | null | no | |
ssh_host_key_fingerprint | string | null | no | |
team_id | integer | null | no | |
tls_fingerprint | string | null | no |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
POST /api/v1/hosts/{host_id}/credentials
Section titled “POST /api/v1/hosts/{host_id}/credentials”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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes |
Request body (required), application/json, CredentialRotateIn
| Field | Type | Required | Description |
|---|---|---|---|
capability | string | null | no | |
rotate_ssh | boolean | no | |
token_id | string | null | no | |
token_secret | string | null | no |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
| 404 | host not found | |
| 422 | token_id/token_secret given without the other, or nothing to rotate | |
| 502 | token_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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes | |
kind | path | string | yes |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes | |
node | path | string | yes |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes | |
node | path | string | yes |
Request body (required), application/json, NodePowerIn
| Field | Type | Required | Description |
|---|---|---|---|
command | string | yes | |
confirm | string | null | no |
Responses
| Status | Description | Schema |
|---|---|---|
| 202 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes | |
node | path | string | yes |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
GET /api/v1/hosts/{host_id}/peers
Section titled “GET /api/v1/hosts/{host_id}/peers”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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
POST /api/v1/hosts/{host_id}/peers
Section titled “POST /api/v1/hosts/{host_id}/peers”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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes |
Request body (required), application/json, PeerEnrolIn
| Field | Type | Required | Description |
|---|---|---|---|
nodes | string[] | yes | |
tls_fingerprints | object | no |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
POST /api/v1/hosts/{host_id}/ssh/verify
Section titled “POST /api/v1/hosts/{host_id}/ssh/verify”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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
POST /api/v1/hosts/{host_id}/sync
Section titled “POST /api/v1/hosts/{host_id}/sync”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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
| 404 | host not found | |
| 502 | pve_error | |
| 504 | timeout |
GET /api/v1/hosts/{host_id}/tasks
Section titled “GET /api/v1/hosts/{host_id}/tasks”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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes | |
limit | query | integer | no |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
| 404 | host not found | |
| 422 | limit must be between 1 and 500 | |
| 502 | pve_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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes | |
upid | path | string | yes | |
start | query | integer | no | |
limit | query | integer | no |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
| 404 | host not found | |
| 502 | pve_error |
POST /api/v1/hosts/{host_id}/test
Section titled “POST /api/v1/hosts/{host_id}/test”Test Host
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
host_id | path | integer | yes |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
GET /api/v1/hosts/capabilities
Section titled “GET /api/v1/hosts/capabilities”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
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
POST /api/v1/hosts/probe
Section titled “POST /api/v1/hosts/probe”Probe
Request body (required), application/json, ProbeIn
| Field | Type | Required | Description |
|---|---|---|---|
address | string | yes | |
name | string | null | no | |
tls_fingerprint | string | null | no | |
token_id | string | yes | |
token_secret | string | yes | |
verify_tls | boolean | no |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
PUT /api/v1/hosts/self
Section titled “PUT /api/v1/hosts/self”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
| Field | Type | Required | Description |
|---|---|---|---|
host_id | integer | null | no |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |
POST /api/v1/hosts/token-script
Section titled “POST /api/v1/hosts/token-script”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
| Field | Type | Required | Description |
|---|---|---|---|
capabilities | string[] | no | |
node_power | boolean | no | |
node_shell | boolean | no | |
path | string | no |
Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Successful Response | any |
| 422 | Validation Error | HTTPValidationError |