Skip to main content

Write Tokens

A token that carries write scopes lets scripts, CI pipelines and infrastructure-as-code tooling change the fleet through ProxCenter. This page covers how write scopes differ from read scopes, who can mint such a token, how it is confined, the endpoints it opens, how to follow its tasks, examples, and the rights checked on sensitive actions. Writes are paced by their own quota, see Quotas.

Read vs write scopes​

The *:read scopes listed in Scopes bundle read permissions only. Thirteen write scopes are separate: each lists its RBAC permission keys explicitly, none is granted until its domain is set to Write, and a scope is a domain barrier, so vms:power never lets a token delete or reconfigure a guest. Every write scope also carries the matching .view keys and connection.view, so a write token can read what it changes and poll its own tasks.

ScopeAllowsPermission keys
vms:powerStart, stop, shutdown, reboot, reset, suspend and resume a guestvm.view, connection.view, vm.start, vm.stop, vm.restart, vm.suspend
vms:writeCreate, clone, convert to template, reconfigure, resize or move a disk, unlock, edit notesvm.view, connection.view, vm.create, vm.clone, vm.config
vms:snapshotCreate, roll back, edit and delete guest snapshotsvm.view, connection.view, vm.snapshot
vms:migrateMigrate a guest, including remote migration (check, prepare, run)vm.view, connection.view, vm.migrate
vms:deleteDestroy a guestvm.view, connection.view, vm.delete
backups:writeBackup jobs (create, edit, delete, run), one-off vzdump, PBS prune, sync, verify and tape jobs, snapshot verifybackup.view, connection.view, backup.job.view, backup.job.create, backup.job.edit, backup.job.delete, backup.job.run, vm.backup
backups:restoreRestore a backup to a guest, and guest file restorebackup.view, connection.view, vm.backup, backup.restore
backups:deleteDelete a PBS snapshotbackup.view, connection.view, backup.delete
storage:writeDelete a volume, download an ISO or template from a URL, refresh appliance templates, attach or detach a PBS storagestorage.view, storage.content, storage.upload, storage.delete, connection.view, connection.manage, backup.view
ha:writeHA resources, groups, affinity rules and auto-HAnode.view, connection.view, node.manage, connection.manage
pools:writeCreate, edit and delete resource poolsconnection.view, connection.manage
network:writeNode network interfaces, SDN apply, vDC VNetsnode.view, connection.view, node.network, connection.manage, sdn.vnet.view, sdn.vnet.create, sdn.vnet.edit, sdn.vnet.delete
firewall:writeCluster, node and guest firewall: rules, aliases, groups, IP sets, NIC firewall, micro-segmentationnode.view, connection.view, node.manage

vms:delete and backups:delete are the destructive scopes. They sit in red in the creation dialog, and what they remove cannot be brought back.

Map of the token creation dialog rows to the scope ids they grant. Each domain row is set to None, Read or Write, and Read grants vms:read, backups:read, storage:read or nodes:read. On Write, VMs and containers shows the toggles Power, Edit, Snapshots, Migrate and Delete, granting vms:power, vms:write, vms:snapshot, vms:migrate and vms:delete. Backups shows Write jobs, Restore and Delete, granting backups:write, backups:restore and backups:delete. Storage has a single write scope, storage:write, with no toggle. Nodes and cluster shows HA, Pools, Network and Firewall, granting ha:write, pools:write, network:write and firewall:write. Write preselects every toggle except the two Delete toggles, drawn in red as destructive scopes to select yourself, and includes the row's read scope. Automation, Alerts and Reports are read only, and no endpoint requires their read scopes yet.Map of the token creation dialog rows to the scope ids they grant. Each domain row is set to None, Read or Write, and Read grants vms:read, backups:read, storage:read or nodes:read. On Write, VMs and containers shows the toggles Power, Edit, Snapshots, Migrate and Delete, granting vms:power, vms:write, vms:snapshot, vms:migrate and vms:delete. Backups shows Write jobs, Restore and Delete, granting backups:write, backups:restore and backups:delete. Storage has a single write scope, storage:write, with no toggle. Nodes and cluster shows HA, Pools, Network and Firewall, granting ha:write, pools:write, network:write and firewall:write. Write preselects every toggle except the two Delete toggles, drawn in red as destructive scopes to select yourself, and includes the row's read scope. Automation, Alerts and Reports are read only, and no endpoint requires their read scopes yet.
What each toggle of the dialog grants. Setting a row to Write selects every toggle except Delete, which grants a destructive scope and must be selected on purpose.
Sensitive NIC settings stay out

vm.config.nic.mac and vm.config.nic.vlan are in no scope. A vDC tenant token that changes a MAC address or a VLAN tag is refused, exactly like a tenant user without those rights. Console, upload by chunks, node reboot and shutdown, Ceph and connection management are not reachable by a token at all.

Who can mint a write token​

A token never holds a right its creator does not hold. When you tick a write scope, the account creating the token must hold every permission of that scope, either tenant-wide or on each connection you chose (tenant-wide when the token covers all connections). A super_admin passes. Otherwise creation is refused with HTTP 403 naming the scope, and the dialog greys such scopes out.

Only tenant-wide grants and grants on the chosen connection count. A right held on a node, a VM, a tag, a pool or a storage does not, and neither does anything granted through a vDC: a token covers a whole connection, so it would be wider than its creator. A user whose rights live only inside a vDC therefore cannot mint a write token.

The check is repeated on every write, not only at creation. The creator must still exist, be enabled, still belong to the token's tenant and still hold the rights of the scope the endpoint needs. The verdict is cached for 60 seconds, so a token loses its write ability within 60 seconds when its creator is:

  • demoted, or has the role removed
  • disabled
  • removed from the tenant
  • deleted

The refused write answers HTTP 403 Token creator no longer holds this right. Reads keep working. This is the difference with the behaviour described in Offboarding a user: a token's reads outlive its creator, its writes do not. If a handler checks a permission beyond the scope's own keys, the creator must hold that one too.

warning

A token whose creator was deleted can still read and can no longer write. Delete it, or mint a replacement from an account that holds the rights.

Confinement​

Every write is checked before the handler runs, against the token's connection perimeter, and again by the usual RBAC checks inside the handler. The handler guards a session meets (quotas, VMID ranges, allowed drives, sensitive NIC keys, forced pools, migration restrictions for vDC tenants) apply to a token unchanged.

Diagram of what a token can reach. The token of the vDC tenant Acme Corp acts on web01 and db01, the guests of its own vDC pool on the provider connection pve-lab-a, for power, config, snapshots and the other guest routes. The guest app01 of the Initech vDC, on the same connection, answers 403 Guest outside your vDC scope. The cluster-level objects of that connection, HA, pools, cluster and node firewall, node network interfaces, SDN apply, PBS jobs and storage attach or detach, answer 403 Cluster-level object not available to a vDC tenant token. The token of the MSP tenant Globex, which owns the connection pve-lab-c, keeps full access to its guests and to the whole cluster. For any token, a connection outside its perimeter answers 403 Connection not in token scope. Every refusal happens before the handler, so nothing reaches Proxmox.Diagram of what a token can reach. The token of the vDC tenant Acme Corp acts on web01 and db01, the guests of its own vDC pool on the provider connection pve-lab-a, for power, config, snapshots and the other guest routes. The guest app01 of the Initech vDC, on the same connection, answers 403 Guest outside your vDC scope. The cluster-level objects of that connection, HA, pools, cluster and node firewall, node network interfaces, SDN apply, PBS jobs and storage attach or detach, answer 403 Cluster-level object not available to a vDC tenant token. The token of the MSP tenant Globex, which owns the connection pve-lab-c, keeps full access to its guests and to the whole cluster. For any token, a connection outside its perimeter answers 403 Connection not in token scope. Every refusal happens before the handler, so nothing reaches Proxmox.
A vDC tenant's token stays inside its vDC: its own guests yes, other tenants' guests and cluster-wide objects no. A tenant that owns its connection keeps full access.
  • Connection perimeter. A connection id is checked wherever it appears: in the path, in the request body, in nested body fields, and in the second connection of a call that names two (the target of a remote migration, the PBS source of a restore, the PBS of a storage attach). A connection outside the perimeter answers 403 Connection not in token scope, and nothing reaches Proxmox. A path parameter that is still percent-escaped after one decoding is refused with the generic 401.
  • vDC tenant tokens act only on their own guests. A token of a vDC tenant on a provider connection must name a guest that belongs to its vDC pools and nodes, for every route that targets an existing guest (power, config, clone, migrate, disks, snapshots, delete, notes, backups, restore, guest firewall, isolation). Anything else answers 403 Guest outside your vDC scope.
  • vDC tenant tokens never touch cluster-level objects of a connection they do not own: HA, pools, cluster and node firewall (and aliases, groups, IP sets), node network interfaces, SDN apply, PBS jobs, attaching or detaching a storage. Answer: 403 Cluster-level object not available to a vDC tenant token. A tenant that owns its connection keeps full access.
  • download-url. POST .../storage/{storage}/download-url makes the Proxmox node fetch a URL, so it is refused to vDC tenant tokens (403 Download URL not available to a vDC tenant token). Every other token may use it with http and https URLs only.
  • Isolation. The micro-segmentation isolate route works on a vDC token's own guest, but with apply_base_sgs it would create cluster-level security groups, so that option is refused to vDC tenant tokens (403 Cluster-level object not available to a vDC tenant token). The provider can pre-create the base groups.

Write endpoints​

Each domain below is a set of routes in the OpenAPI document and in Settings > API reference, with its body schema, its response and the required scope on every operation. The reference states plainly that Try it really acts on your infrastructure.

DomainScopeRoutes (prefix /api/v1)
Guest lifecyclevms:power, vms:write, vms:migrate, vms:deletePOST /connections/{id}/guests/{type}/{node} (create), POST .../{vmid}/{action}, PUT .../{vmid}/config, .../clone, .../template, .../disk/resize, .../disk/move, .../unlock, .../migrate, .../remote-migrate (with /check and /prepare), DELETE .../{vmid}, PUT /guests/{vmid}/notes
Snapshotsvms:snapshotPOST and DELETE /guests/{vmid}/snapshots, POST (rollback) and PUT /guests/{vmid}/snapshots/{snapname}
Backupsbackups:write, backups:restore, backups:delete/connections/{id}/backup-jobs (and /{jobId}), .../nodes/{node}/vzdump, .../nodes/{node}/restore, /pbs/{id}/jobs/{kind} (and /{jobId}, /{jobId}/run), /pbs/{id}/backups/{backupId} (and /verify), /guest-file-restore/*
Storagestorage:writeDELETE .../storage/{storage}/content/{volid}, POST .../storage/{storage}/download-url, POST .../aplinfo, POST and DELETE /connections/{id}/storage
HAha:write/connections/{id}/ha/{sid}, ha/groups, ha/affinity-rules, ha/auto-ha (and /sync)
Poolspools:writePOST, PUT, DELETE /connections/{id}/pools
Networknetwork:write/connections/{id}/nodes/{node}/network (and /{iface}), POST /connections/{id}/sdn/apply, /vdcs/{id}/vnets (and /{pveName})
Firewallfirewall:write/firewall/aliases, cluster, groups, ipsets, nodes, vms and microseg, each followed by the connection id
Tasksany write scopeGET /tasks/{connectionId}/{node}/{upid}

A guest in a snapshot or notes route is addressed by its composite key connId:type:node:vmid, URL-encoded.

Because the write routes are opened, the GET endpoints that sit next to them (for example the guest config, the HA groups or the backup-job list) answer to a read token as well as to a write token.

Tasks​

Most writes are asynchronous. The answer carries the Proxmox UPID of the task, and a 200 means the task was accepted, not that it succeeded. Poll

GET /api/v1/tasks/{connectionId}/{node}/{upid}

until status is stopped, then read exitstatus (OK on success) and failureReason. Any write scope opens this route, the connection must be in the perimeter, and the UPID must be URL-encoded because it contains colons.

Sequence diagram between your script, ProxCenter and the Proxmox node. The script sends POST …/guests/qemu/NODE/VMID/start with its Bearer token; ProxCenter starts the task on the node, gets its UPID and answers 200 with the UPID in data, which means the task was accepted, not that it succeeded. In a loop until status is stopped, the script sends GET /api/v1/tasks/CONN/NODE/UPID with the UPID URL-encoded, ProxCenter reads the task status and log on the node and answers running or stopped. Once stopped, the script checks exitstatus: OK means the change is done, otherwise it reads failureReason. Any write scope opens the task route with the connection in the perimeter, and a vDC tenant token reads only the tasks of its own guests and those it started.Sequence diagram between your script, ProxCenter and the Proxmox node. The script sends POST …/guests/qemu/NODE/VMID/start with its Bearer token; ProxCenter starts the task on the node, gets its UPID and answers 200 with the UPID in data, which means the task was accepted, not that it succeeded. In a loop until status is stopped, the script sends GET /api/v1/tasks/CONN/NODE/UPID with the UPID URL-encoded, ProxCenter reads the task status and log on the node and answers running or stopped. Once stopped, the script checks exitstatus: OK means the change is done, otherwise it reads failureReason. Any write scope opens the task route with the connection in the perimeter, and a vDC tenant token reads only the tasks of its own guests and those it started.
A 200 only means Proxmox accepted the task. Poll the task with its UPID until it is stopped, then check exitstatus before treating the change as done.

A vDC tenant token can poll the tasks of its own guests, and the tasks it started itself (so it can read the outcome of a delete, or of a create that failed). The UPID must name the node in the path. A task outside its scope answers 403 Task outside your vDC scope.

Examples​

Placeholders only. Set once:

BASE=https://proxcenter.example.com
TOKEN=pxc_REPLACE_WITH_YOUR_TOKEN
CONN=CONNECTION_ID
NODE=NODE_NAME

Start a VM (vms:power):

curl -fsS -X POST \
-H "Authorization: Bearer $TOKEN" \
"$BASE/api/v1/connections/$CONN/guests/qemu/$NODE/VMID/start"

Create a snapshot (vms:snapshot). The guest is CONN:qemu:NODE:VMID, with the colons encoded:

curl -fsS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name": "before-upgrade", "description": "Taken by CI", "vmstate": false}' \
"$BASE/api/v1/guests/$CONN%3Aqemu%3A$NODE%3AVMID/snapshots"

Poll the task with the UPID returned by either call (any write scope):

curl -fsS \
-H "Authorization: Bearer $TOKEN" \
"$BASE/api/v1/tasks/$CONN/$NODE/UPID_URL_ENCODED"

Delete a VM and remove it from backup jobs (vms:delete). The guest must be stopped and unlocked:

curl -fsS -X DELETE \
-H "Authorization: Bearer $TOKEN" \
"$BASE/api/v1/connections/$CONN/guests/qemu/$NODE/VMID?purge=1"

destroy-unreferenced-disks=1 can be added to the same call to remove disks the guest config no longer references.

Rights checked on sensitive actions​

These checks apply to tokens and sessions alike. A few of them are easy to miss on a custom role:

  • Deleting a PBS snapshot needs backup.delete.
  • Verifying a PBS snapshot needs vm.backup on the PBS connection.
  • Restoring a backup needs backup.restore, and vm.delete as well when the restore overwrites an existing guest. A role without vm.delete, such as Operator, cannot restore over an existing guest.
  • A remote migration also checks vm.migrate on the target connection.
  • Changing or applying the Auto-HA settings of a cluster needs node.manage on the connection; a right on a single node does not suffice, and the card is read only in that case.
  • Download from URL is refused to vDC tenants, who upload files instead: the Proxmox node fetches the URL itself, so ProxCenter cannot vet where it really goes.
  • A tenant user listing the VNets of another tenant's vDC gets the same 404 as for a vDC that does not exist.
  • Reading a task status is bound to the task's connection, with a fallback on the right over the guest or the node the task runs on, so a user scoped to a few VMs still follows their own tasks. A task covering several guests (a backup job run) needs a right over the whole connection.

Read tokens also reach the GET endpoints of the write routes, as described under Write endpoints.