REST API
Authenticating, conventions, errors, and the interactive specification.
Everything the console does, it does through this API. There is nothing the console can do that the API cannot.
The specification
The deployment serves its own OpenAPI specification, which is always accurate for the version you are running:
https://<vip>/v3/api-docs the specification
https://<vip>/swagger-ui.html interactive browser
That is the exhaustive reference — every endpoint, every parameter. This page covers the things you need to know that a specification does not tell you.
Base URL
Always the virtual IP or the console name:
https://vs.example.com/api/...
Warning — A standby controller answers reads but returns HTTP 409 to a write, naming the active controller. Client code should either always use the VIP, or handle a 409 by following the address it returns. Pointing a script at a node’s own address works until that node stops being active.
Authentication
Two ways in.
Sign in
curl -X POST https://vs.example.com/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"operator","password":"..."}'
{
"access_token": "...",
"refresh_token": "...",
"token_type": "Bearer",
"expires_in": 86400,
"user": { "username": "operator", "role": "SUPER_ADMIN", "domains": { ... } }
}
Present the access token as a bearer credential:
curl https://vs.example.com/api/vms \
-H "Authorization: Bearer $TOKEN"
Access tokens last 24 hours, refresh tokens 7 days by default.
Where the account has two-factor enabled, the sign-in flow asks for a code.
API keys
For automation, use an API key rather than a person’s password. Keys are scoped, expiring and revocable, and actions taken with one are attributed to it in the audit log.
Errors
Errors are RFC 7807 problem documents:
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"detail": "No endpoint GET /api/nope.",
"instance": "/api/nope",
"timestamp": "2026-09-22T07:36:56.570860295Z"
}
detail is written to be read. Log it.
| Status | Meaning |
|---|---|
| 400 | Malformed request |
| 401 | Not signed in, or the session expired |
| 403 | Signed in, not permitted — recorded in the audit log |
| 404 | No such object or endpoint |
| 409 | Conflict — including a write sent to a standby |
| 429 | Rate limited |
| 500 | Server error — carries an errorId to quote to support |
Paging
Collections that can grow are paged:
GET /api/audit?page=0&size=50
{
"content": [ ... ],
"page": 0,
"totalPages": 491,
"totalElements": 981
}
Smaller collections — hosts, uplinks, templates — return a plain array.
Rate limits
| Sign-in | 10 attempts per minute |
| API | 1000 requests per minute |
Exceeding either returns 429. Account lockout is separate: 5 consecutive failed sign-ins lock an account for 15 minutes.
Asynchronous work
Anything slow returns a task rather than blocking. Poll it, or subscribe over the WebSocket:
POST /api/vms → task created
GET /api/tasks/{id} → status and progress
A 200 from a create call means accepted, not finished. See Tasks.
Authorization
Every endpoint is authorized server-side against your role and the domain it belongs to — virtualization, networking, storage, hosts or the control plane itself. A key or account scoped to storage is refused on networking endpoints.
See Users and roles.
Shapes worth knowing
Identifiers are UUIDs. Names are for people; do not key automation on them.
Per-host resources are nested. Networking and storage configuration are per host:
/api/hosts/{hostId}/networks/interfaces
/api/hosts/{hostId}/storage/pools
Secrets are write-only. Passwords, CHAP secrets, BGP passwords and keys can be set and never read back. An integration that needs a secret must hold it itself.
WebSockets
Live updates — task progress, metrics, events — arrive over a WebSocket. A browser cannot put a credential on a raw socket, so the client asks the API for a short-lived, single-use ticket and connects with that. VM consoles work the same way.
A worked example
BASE=https://vs.example.com
TOKEN=$(curl -sk -X POST $BASE/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"automation","password":"..."}' \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')
# Fleet state
curl -sk $BASE/api/hosts -H "Authorization: Bearer $TOKEN"
curl -sk $BASE/api/vms -H "Authorization: Bearer $TOKEN"
# Start a VM, then follow the task
curl -sk -X POST $BASE/api/vms/$VM_ID/start -H "Authorization: Bearer $TOKEN"
curl -sk $BASE/api/tasks -H "Authorization: Bearer $TOKEN"
Versioning
The API belongs to the version of the platform serving it. Read the release notes before upgrading anything that automates against it, and re-read the specification from the deployment afterwards rather than a cached copy.