VorticPanel

API and billing

API overview

Everything the panel does goes through the same REST API, so a billing panel, a script or your own tools can do it too. Every permission is checked on every call, whether it comes from the panel or a token.

Base URL

https://panel.example.com/api/v1

Requests and responses are JSON. Request bodies must be sent as Content-Type: application/json, up to 1 MB.

curl https://panel.example.com/api/v1/servers \
  -H "Authorization: Bearer pnl_…"

See Authentication and tokens for getting a token.

Who can call what

The same API serves three kinds of account. Each sees only what it owns:

Account Sees
Customer Their own servers, and servers shared with them through team access
Reseller Their own servers and their customers’
Staff Everything their role allows

Another account’s server answers 404, never 403, so IDs can’t be probed.

The panel also has a built-in API reference: /api-reference for customers and resellers, and /admin/api/reference for staff. It lists every call the person can use, with its parameters, a TypeScript signature and a ready-to-copy curl command. It’s generated from the API client, so it can’t drift from the real API.

Successful responses

  • 200 with a JSON body, or 204 with no body.
  • Long-running actions (create, power, reinstall, move…) answer straight away with a job. Follow it with GET /jobs/{jobId} until its state is succeeded or failed.
{ "jobId": "job_…", "serverId": "srv_abc123", "ipv4": "203.0.113.10" }

Errors

Errors have one shape:

{
  "error": {
    "code": "missing_scope",
    "message": "This token needs one of these permissions: servers:power.",
    "details": { "scopes": ["servers:power"] }
  }
}

details is only there when it has something in it. Every response carries an X-Request-Id header; quote it when reporting a problem.

Status Codes
400 invalid_json, invalid_request, invalid_idempotency_key
401 invalid_token, not_authenticated
403 forbidden, missing_scope, scope_not_allowed, token_not_allowed, ip_not_allowed, csrf_failed, bad_origin, support_session
404 not_found, server_not_found, customer_not_found, …
405 method_not_allowed
409 hostname_taken, no_capacity, no_addresses, location_closed, account_locked, name_taken, email_taken, external_id_taken, reseller_suspended, quota_exceeded, snapshot_limit
413 too_large
415 unsupported_media_type
422 validation_failed, limit_reached, disk_too_small
429 rate_limited
500 internal_error
503 restarting

Idempotency

Send an Idempotency-Key header (1–200 characters) on every POST, and reuse it when you retry. If the first request succeeded, the retry gets the same answer instead of doing the work twice, for 24 hours.

curl -X POST https://panel.example.com/api/v1/admin/servers \
  -H "Authorization: Bearer pnl_…" \
  -H "Idempotency-Key: order-10482" \
  -H "Content-Type: application/json" \
  -d '{ … }'
  • Use a new key for each distinct operation, for example the order or invoice number. Keys are remembered per account and per endpoint, and the stored answer is returned without comparing request bodies.
  • Failed requests aren’t remembered, so they can be retried with the same key.
  • It’s honoured by the calls that create or change something you wouldn’t want twice: creating servers and customers, power, reinstall, snapshots, backups and restores, rescue, password resets, package changes, addresses, terminate, moves and imports.

Rate limits

What Limit
API calls 1200 a minute, per token (or per account, or per address)
Sign-in calls 20 a minute per address

Over the limit, the API answers 429 rate_limited with a Retry-After header in seconds. While the controller restarts, changes get 503 restarting with Retry-After: 60; reads keep working.

Pagination

Lists that can be long return a page:

{ "data": [ … ], "nextCursor": "NTA=", "total": 312 }

Pass cursor (the previous page’s nextCursor) and limit. nextCursor is null on the last page. Cursors are opaque.

List Default Max
Servers (staff) 50 100
Customers, reseller customers, transfers 50 200
IP pool addresses 64 256
Audit log 50 1000
Webhook deliveries 25 100

Next

Every word has to appear. ↑ ↓ to move, Enter to open.