Skip to main content
RunBook Academy

Proxmox VEXX · CLI & AutomationREST API automation

PVE REST API: tokens, authentication, and the API viewer

Foundation⏱ ~18 min🧪 Lab requiredcurljq

What you'll learn

  • Use the PVE REST API directly with curl
  • Create and manage API tokens with the right privileges
  • Navigate the API viewer to find endpoints
  • Handle tickets, CSRF prevention, and async operations

Prerequisites

Verified against Proxmox VE 9.2.4 · Proxmox Backup Server 4.2.5 · Ceph Squid / Tentacle · Debian 13 (Trixie) · Linux kernel 7.0 (PVE 9.2 default) · 2026-08-07

Not yet marked complete on this device.

PVE REST API: tokens, authentication, and the API viewer

pvesh is a CLI wrapper around the PVE HTTP API. Anything you can do with pvesh, you can do with curl. This lesson shows when and how to use the REST API directly.

When to use the API directly

pvesh is fine for ad-hoc commands and simple scripts. Use the REST API directly when:

  • You’re integrating with a non-shell language (Python, Go, Java)
  • You need streaming responses (large data sets, real-time updates)
  • You want fine-grained control over HTTP behaviour (retries, timeouts)
  • You’re building a library

Authentication: tickets vs API tokens

The PVE API supports two auth methods:

Tickets (session-based)

# Step 1: Get a ticket (session)
TICKET=$(curl -sk -X POST 'https://pve-01:8006/api2/json/access/ticket' \
  -H 'Content-Type: application/json' \
  -d '{"username":"root@pam","password":"secret"}' | \
  jq -r '.data.ticket')

# Step 2: Use the ticket in subsequent requests
curl -sk -X GET 'https://pve-01:8006/api2/json/nodes' \
  -H "Cookie: PVEAuthCookie=$TICKET"

# Tickets expire in 2 hours

Tickets are useful for browser sessions but expire, so they’re not ideal for automation.

# Step 1: Create a token (one-time, on the PVE host)
pvesh create /access/users/admin@pve/token/automation --privsep 0
# Returns the secret — save it!

# Step 2: Use the token in API requests
TOKEN="admin@pve!automation=12345678-1234-1234-1234-123456789abc"
curl -sk -X GET 'https://pve-01:8006/api2/json/nodes' \
  -H "Authorization: PVEAPIToken=$TOKEN"

API tokens don’t expire by default (configurable), can be revoked individually, and don’t require a session.

The API viewer

The PVE documentation includes a complete interactive API viewer at pve.proxmox.com/pve-docs/api-viewer.html. It shows every endpoint with parameters, expected output, and an executable example.

For local installation, the same viewer is at: https://pve-01:8006/pve-docs/api-viewer.html

Use the API viewer to discover endpoints and their parameters.

Common API patterns

List VMs

TOKEN="admin@pve!automation=..."

curl -sk 'https://pve-01:8006/api2/json/cluster/resources?type=vm' \
  -H "Authorization: PVEAPIToken=$TOKEN" | \
  jq '.data[] | {vmid, name, node, status}'

Create a VM

VMID=100
curl -sk -X POST "https://pve-01:8006/api2/json/nodes/pve-01/qemu/$VMID/clone" \
  -H "Authorization: PVEAPIToken=$TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "newid": 200,
    "name": "web-01",
    "full": false,
    "storage": "local-zfs"
  }'

Update VM config

curl -sk -X POST "https://pve-01:8006/api2/json/nodes/pve-01/qemu/100/config" \
  -H "Authorization: PVEAPIToken=$TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"memory": 4096, "cores": 4}'

Start, stop, shutdown

curl -sk -X POST "https://pve-01:8006/api2/json/nodes/pve-01/qemu/100/status/start" \
  -H "Authorization: PVEAPIToken=$TOKEN"

curl -sk -X POST "https://pve-01:8006/api2/json/nodes/pve-01/qemu/100/status/stop" \
  -H "Authorization: PVEAPIToken=$TOKEN"

Async tasks

Some operations return a task UPID instead of immediate results. The task runs in the background; you can poll for completion:

# Start an async task — returns a UPID
UPID=$(curl -sk -X POST "https://pve-01:8006/api2/json/nodes/pve-01/qemu/100/snapshot" \
  -H "Authorization: PVEAPIToken=$TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"snapname": "pre-upgrade"}' | jq -r '.data')

# Poll for completion
while true; do
  STATUS=$(curl -sk "https://pve-01:8006/api2/json/nodes/pve-01/tasks/$UPID/status" \
    -H "Authorization: PVEAPIToken=$TOKEN" | jq -r '.data.status')
  echo "Status: $STATUS"
  if [ "$STATUS" = "stopped" ]; then break; fi
  sleep 2
done

CSRF prevention

The PVE API requires a CSRF prevention token for all mutating requests (POST, PUT, DELETE) when using ticket-based auth:

# Get ticket + CSRF token together
RESP=$(curl -sk -X POST 'https://pve-01:8006/api2/json/access/ticket' \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin@pam","password":"secret"}')
TICKET=$(echo "$RESP" | jq -r '.data.ticket')
CSRF=$(echo "$RESP" | jq -r '.data.CSRFPreventionToken')

# Include CSRF token in mutating requests
curl -sk -X POST "https://pve-01:8006/api2/json/nodes/pve-01/qemu/100/status/start" \
  -H "Cookie: PVEAuthCookie=$TICKET" \
  -H "CSRFPreventionToken: $CSRF"

API tokens don’t need CSRF prevention — they have their own auth.

Error handling

The API returns standard HTTP status codes:

StatusMeaning
200Success
400Bad request — invalid parameters
401Unauthorized — bad credentials
403Forbidden — credentials valid but privileges insufficient
404Not found
500Server error — check the task log
596PVE-specific error (e.g., snapshot in use)

Errors include a JSON body with details:

{
  "errors": {
    "vmid": "VM 100 does not exist"
  },
  "data": null
}

Always parse the errors object for useful diagnostics.

Production considerations

  • Rate limiting. The API has soft limits; bulk operations should include sleep 0.1 between requests.
  • TLS verification. In production, set insecure=False and use proper certificates. Self-signed certs require explicit trust.
  • Timeouts. Always set curl timeout (--max-time 30) so a hung API call doesn’t block your script forever.
  • Retries. Transient errors (503, network) are common. Use exponential backoff: 1s, 2s, 4s, 8s. Up to 5 retries.

Common mistakes

  • Using the root user. Always create a dedicated user with the minimum required privileges.
  • Tickets in scripts. Tokens are better — they don’t expire during a long script run.
  • Forgetting CSRF. With ticket auth, mutating requests fail with 401 unless you include the CSRF token.
  • Not handling 5xx. Transient errors are common; a script that fails on the first 503 is fragile.

Key takeaways

  • Use API tokens for automation, tickets for browser sessions.
  • Use the API viewer to discover endpoints.
  • Handle async operations with task UPIDs and polling.
  • Add retries with exponential backoff for transient errors.

Knowledge check

Knowledge check · 4 questions

  1. Q1. Which HTTP header carries a PVE API token?

  2. Q2. A Proxmox API token stays valid indefinitely unless an expiry is set when it is created.

  3. Q3. Which of these are good practices for REST API automation? (Select all that apply)

  4. Q4. Name the API endpoint that lists cluster resources.

Passing score: 75%. Answers are checked in this browser.