Proxmox VE provides a full-featured REST API that exposes every operation — from creating a VM to managing the entire cluster. It's the foundation for integrating with Terraform, Ansible, CI/CD, and your own scripts.
Authentication: API Token vs. Cookie
| Method | Use Case | Lifetime |
|---|---|---|
| API Token | Scripts, automation, CI/CD | Unlimited (until revoked) |
| Session Cookie | Web interface, one-off requests | 2 hours |
Creating an API Token
# Create a user for automation
pveum user add automation@pve --password AutoPass123
# Assign permissions
pveum aclmod / --users automation@pve --roles PVEVMAdmin
# Create an API token (without superuser privileges)
pveum user token add automation@pve mytoken --privsep 0
# Output: automation@pve!mytoken = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Example Requests via curl
>TOKEN="automation@pve!mytoken=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
HOST="https://192.168.1.100:8006/api2/json"
# List cluster nodes
curl -s -k -H "Authorization: PVEAPIToken=$TOKEN" "$HOST/nodes" | python3 -m json.tool
# List VMs on node pve
curl -s -k -H "Authorization: PVEAPIToken=$TOKEN" "$HOST/nodes/pve/qemu"
# Start VM 100
curl -s -k -X POST -H "Authorization: PVEAPIToken=$TOKEN" "$HOST/nodes/pve/qemu/100/status/start"
# Stop the VM
curl -s -k -X POST -H "Authorization: PVEAPIToken=$TOKEN" "$HOST/nodes/pve/qemu/100/status/stop"
# Create a snapshot
curl -s -k -X POST -H "Authorization: PVEAPIToken=$TOKEN" -d "snapname=api-test&description=Created+via+API" "$HOST/nodes/pve/qemu/100/snapshot"
Python SDK for Proxmox
>from proxmoxer import ProxmoxAPI
proxmox = ProxmoxAPI(
'192.168.1.100',
user='automation@pve',
token_name='mytoken',
token_value='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
verify_ssl=False
)
# List all VMs
for vm in proxmox.nodes('pve').qemu.get():
print(f"VM {vm['vmid']}: {vm['name']} - {vm['status']}")
# Create a snapshot
proxmox.nodes('pve').qemu(100).snapshot.post(
snapname='python-snap',
description='Created via Python SDK'
)
# Start the VM
proxmox.nodes('pve').qemu(100).status.start.post()
pip install proxmoxer requests. Official API documentation is available on your server: https://YOUR-PVE:8006/pve-docs/api-viewer/
Terraform Provider for Proxmox
>terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "~> 0.50"
}
}
}
provider "proxmox" {
endpoint = "https://192.168.1.100:8006/"
api_token = "automation@pve!mytoken=xxxxxxxx..."
insecure = true
}
resource "proxmox_virtual_environment_vm" "ubuntu" {
name = "tf-ubuntu"
node_name = "pve"
vm_id = 300
clone {
vm_id = 100 # Template ID
full = false
}
cpu { cores = 2 }
memory { dedicated = 2048 }
network_device {
bridge = "vmbr0"
model = "virtio"
}
}Common Errors When Working with the Proxmox API
401 Unauthorized: permission denied - invalid PVE ticket
596 Connection reset by peer / SSL_ERROR
Parameter verification failed: vmid: value does not look like a valid VM ID
403 Forbidden: Permission check failed (/vms/100, VM.PowerMgmt)| Message | Cause | Fix |
|---|---|---|
| 401 Unauthorized: invalid PVE ticket | The API token is wrong or revoked, or the PVEAPIToken= prefix is missing from the header | Check the header format: Authorization: PVEAPIToken=user@realm!tokenid=UUID |
| 596 Connection reset / SSL_ERROR | The client is using HTTP instead of HTTPS, or a self-signed certificate is being rejected without verify_ssl=False | Use https:// and the -k flag in curl, or verify_ssl=False in proxmoxer, for a test setup |
| Parameter verification failed: vmid | The VM ID was passed as a string with invalid characters, or is out of range | Pass vmid as a plain number within the valid range, unquoted in the JSON request body |
| 403 Forbidden: Permission check failed | The API token's user has no role granting the needed permission (such as VM.PowerMgmt) on that path | Assign a role via pveum aclmod on the path /vms/VMID or the root /, not just create the token |
API Token vs. the Terraform Provider: Which to Choose
| Criterion | Direct API token (curl/Python) | Terraform provider |
|---|---|---|
| One-off operations and scripts | Fast, minimal dependencies | Overkill for a single command |
| Reproducible infrastructure as code | You write idempotency by hand | Built in — the state file tracks changes |
| Rolling back to a previous configuration | Manual, through separate API calls | terraform apply with the previous state |
| Team learning curve | Lower — a regular HTTP client | Higher — requires knowing HCL and the Terraform lifecycle |
When API Automation Doesn't Fit
- A one-off manual task that's simpler to do through the web interface in a minute — wrapping it in an authenticated script doesn't pay off
- No one on the team maintains the resulting scripts after the author leaves — unmaintained automation is riskier than none at all, especially around
status/startandstatus/stop - You need a visual check of cluster state before every action (neighboring nodes, free space) — the API doesn't surface that as clearly as the web interface
- The infrastructure changes so rarely that Terraform state would drift from reality between runs — in that case manual operations are more reliable than unwatched automation
Verifying the Automation Works Correctly
# The token is valid and can see the cluster
curl -s -k -H "Authorization: PVEAPIToken=$TOKEN" "$HOST/version"
# The token's permissions are limited to the expected set
pveum user permissions automation@pve
# List of tasks run via the API, to compare against the script's log
pvesh get /nodes/pve/tasks --limit 10- Query
/versionwithout targeting a specific VM — if it returns 200, the token works at the basic level - Compare the token's actual permissions with
pveum user permissionsagainst what was expected at creation time — extra permissions are easiest to remove right away - After running the script, check the task list under
/nodes/NODE/tasks— every API action should show up there with a final status of OK - For Terraform, run
terraform planwithout applying and confirm it shows no unexpected changes to already-existing resources
Restricting token permissions by roles and ACLs is covered in users, roles, and ACLs in Proxmox, and basic protection for the panel's own web interface is covered in two-factor protection for the Proxmox panel.
Frequently asked questions
Does an API token bypass two-factor authentication enabled for the Proxmox web interface login?
An API token and the web interface login are separate authentication mechanisms: the token carries its own secret and is checked independently of any browser session, so it does not technically bypass two-factor protection, it simply uses a different login path that protection was never applied to. That is exactly why a token's permissions and lifetime deserve extra-careful restriction.
Which is safer: giving an automation token superuser rights or a restricted role?
A restricted role is almost always safer: a token is created for one specific job and should get only the permissions that script needs, assigned on a specific path rather than the root of the resource tree. Superuser rights turn the leak of a single token into a compromise of the cluster, while a narrow role keeps the damage contained.
Do you need to send the user's password with every request when using an API token?
No, that is the whole point of a token: once created, its value sits in the request header and keeps working without a password being entered, until it is explicitly revoked. A password is only needed for the cookie-based session login, while a token exists for scripts and automation, where typing a password each time would be inconvenient and unsafe.
Is it worth switching to Terraform if direct API calls through curl or Python already work?
If the current scripts handle one-off tasks and someone actively maintains them, switching to Terraform is not mandatory. The switch pays off once infrastructure needs to be reproducible from code, with rollback to a previous configuration and state tracking — that is what Terraform's state file takes over instead of being built by hand on top of direct API calls.