API reference

Everything the dashboard does, from code. Base URL https://api.portveil.com. Send Authorization: Bearer <token> on every request, and a User-Agent header (requests without one are refused). Your account ID (acct_…) and API tokens are in the dashboard.

Locations · Devices · Moving devices · Scheduled rotation · API tokens · Activity

# move a device to Finland, then follow the command
API=https://api.portveil.com/v1/accounts/$ACCT
curl -X POST "$API/devices/$DEV/commands" \
  -H "Authorization: Bearer $TOKEN" -H "User-Agent: my-agent" \
  -H "Content-Type: application/json" \
  -d '{"type":"switch_server","server_id":"srv-eu-1"}'

Prefer an AI assistant? The Portveil MCP server wraps these calls for Claude, Cursor and other MCP clients.

Locations

GET/v1/serversread

The exit locations a device can use.

Returns: {"servers": [{"id": "srv-eu-1", "name", "region", …}]}

Devices

GET/v1/accounts/{account_id}/devicesread

Every device with its real protection state. exit_confirmed is true only when the exit server sees that device's tunnel; quality is good, fair, poor, unverified or offline. Also shows rotation and, for temporary devices, expires_at.

POST/v1/accounts/{account_id}/devicesadmin

Register a device with its WireGuard public key. Add ttl_minutes (5–43200) for a temporary device: Portveil deletes it when the time is up, revoking its access and freeing its plan slot. Made for short-lived agents.

{"name": "scraper-42", "platform": "linux",
 "peer_pubkey": "<base64 WireGuard key>",
 "ttl_minutes": 60}

Returns: the device id, its device token (shown once), its tunnel addresses and expires_at.

PATCH/v1/accounts/{account_id}/devices/{device_id}admin

Rename a device or turn remote control on or off.

{"name": "Scraper box", "allow_remote": true}
DELETE/v1/accounts/{account_id}/devices/{device_id}admin

Delete a device. Its tunnel access and any tokens limited to it stop working immediately.

Moving devices

POST/v1/accounts/{account_id}/devices/{device_id}/commandscontrol

Send a command: switch_server (with server_id), reconnect or disconnect. The device picks it up within about 10 seconds. Returns 409 if the device has remote control turned off.

{"type": "switch_server", "server_id": "srv-eu-1"}

Returns: {"command_id", "status": "queued", "expires_at"}

GET/v1/accounts/{account_id}/commands/{command_id}read

Follow a command: queued → delivered → acked (or failed, rejected, expired). After a switch is acked, the device list shows it exiting in the new location once that exit confirms it.

Scheduled rotation

PUT/v1/accounts/{account_id}/devices/{device_id}/rotationcontrol

Move a device to the next location automatically every N minutes (5–10080). Limit it to some locations with servers, or leave that out to use all of them. Portveil runs the schedule; nothing on your side has to stay running. If the last move hasn't landed yet, the next one waits. Send {"every_minutes": null} to stop.

{"every_minutes": 15, "servers": ["srv-us-1", "srv-eu-1"]}

Returns: {"rotation": {"every_minutes", "servers"}, "next_rotation_at"}

GET/v1/accounts/{account_id}/devices/{device_id}/rotationread

The device's rotation schedule, if any.

API tokens

POST/v1/accounts/{account_id}/api-tokensaccount key

Create a token for an agent. Scopes: read (look), control (move, reconnect, disconnect, rotation) or admin (also add, rename and delete devices). Add device_id for a device-limited token: it can only see and control that one device, and is refused anywhere else. Only your account key can create tokens; tokens can't create other tokens.

{"name": "scraper-42 agent", "scopes": ["control"],
 "device_id": "dev_…"}

Returns: {"token_id", "token"}. The token is shown once.

GET/v1/accounts/{account_id}/api-tokensaccount key

List tokens (never the secrets).

DELETE/v1/accounts/{account_id}/api-tokens/{token_id}account key

Revoke a token immediately.

Activity

GET/v1/accounts/{account_id}/audit-log?limit=100read

Recent actions, newest first, each with who did it: account, api-token:tok_…, rotation-policy or system.

Errors and limits

401 the token is missing or wrong · 403 the token is valid but its scope (or its device limit) doesn't allow this · 404 not found, including anything on another account · 409 the device has remote control turned off · 429 too many requests; wait the number of seconds in Retry-After. Error bodies are JSON with a detail field written for people.