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
/v1/serversreadThe exit locations a device can use.
Returns: {"servers": [{"id": "srv-eu-1", "name", "region", …}]}
Devices
/v1/accounts/{account_id}/devicesreadEvery 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.
/v1/accounts/{account_id}/devicesadminRegister 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.
/v1/accounts/{account_id}/devices/{device_id}adminRename a device or turn remote control on or off.
{"name": "Scraper box", "allow_remote": true}/v1/accounts/{account_id}/devices/{device_id}adminDelete a device. Its tunnel access and any tokens limited to it stop working immediately.
Moving devices
/v1/accounts/{account_id}/devices/{device_id}/commandscontrolSend 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"}
/v1/accounts/{account_id}/commands/{command_id}readFollow 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
/v1/accounts/{account_id}/devices/{device_id}/rotationcontrolMove 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"}
/v1/accounts/{account_id}/devices/{device_id}/rotationreadThe device's rotation schedule, if any.
API tokens
/v1/accounts/{account_id}/api-tokensaccount keyCreate 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.
/v1/accounts/{account_id}/api-tokensaccount keyList tokens (never the secrets).
/v1/accounts/{account_id}/api-tokens/{token_id}account keyRevoke a token immediately.
Activity
/v1/accounts/{account_id}/audit-log?limit=100readRecent 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.