pve-agents
Reference

HTTP API

For scripting the controller from something that is not a browser.

Everything the web UI does goes through server functions rather than these routes. The HTTP API exists for scripting: requesting a workspace from a CI job, checking the fleet from a terminal, watching an agent from something that is not a browser.

Authentication

Send x-api-key: <key>. Create a key with:

pnpm apikey:create

Every route below requires it, except GET /api/health. With CONTROLLER_AUTH_SECRET unset the controller has no authentication at all and answers anyone who can reach it, which is only survivable on a trusted LAN. GET /api/health reports whether auth is configured, so that is the one call worth making before trusting the rest.

Routes

GET /api/healthController health, whether auth is configured, and whether provisioning is on. The only unauthenticated route.
GET /api/workspacesEvery workspace with its timeline.
POST /api/workspacesRequest one.
GET /api/workspaces/:idOne workspace, with placement, failure detail and its whole timeline.
DELETE /api/workspaces/:idQueue destruction. Returns once the operation is queued, not once the container is gone.
POST /api/workspaces/:id/retryQueue a fresh provision. Only for a workspace in failed.
GET /api/workspaces/:id/agentServer-sent events: the agent's transcript, live.
GET /api/infrastructure/probeCheck the controller can reach Proxmox.

Requesting a workspace

curl -X POST https://pve-agents.example.internal/api/workspaces \
  -H "x-api-key: $PVE_AGENTS_KEY" \
  -H "content-type: application/json" \
  -d '{
        "idempotencyKey": "ci-run-8813",
        "repository": "github.com/you/your-repo",
        "ref": "main",
        "purpose": "Work out why the nightly job leaves orphans behind"
      }'
Field
idempotencyKeyrequired1-255 chars. Replaying the same key returns the workspace the first call made, rather than building a second container. Replaying it with a different body is a conflict, not a new workspace.
repositoryrequired1-2000 chars.
refoptional1-255 chars, defaults to main.
purposeoptional1-500 chars. What the agent is told to do. Without it the workspace comes up ready and idle, waiting for a prompt.

The response is the persisted workspace. It has been requested, not built: the worker picks it up on its next pass, and the container appears some tens of seconds later. Poll GET /api/workspaces/:id or watch the timeline to see how far it got.

With PROVISIONING_ENABLED=false the request is accepted and queued and nothing is ever built. That is the intended way to exercise this API without touching Proxmox.

Watching an agent

GET /api/workspaces/:id/agent is an EventSource stream, not a plain response.

A reader gets the runner's whole snapshot first and the live events after it, because a transcript replay is not the same for a late arrival as it is for someone who was there from the start. One SSH attachment is made per reader rather than one shared between them.

The stream sends a comment line every 20 seconds. A thinking agent can say nothing for minutes, which a proxy or a browser otherwise treats as a dead connection.

If you put a proxy in front of the controller, exclude this path from buffering and from compression. Both hold the stream and deliver it in bursts, or not at all. See the reverse proxy section for the two rules that matter.

A workspace with nothing to watch is refused rather than streamed as empty, so a caller can tell the difference between "no agent here" and "an agent that has not spoken yet".

Last updated on

On this page