REST API
There is exactly one API. The console uses it, the MCP server uses it, and so do you. There is no privileged internal path and therefore no capability gap to discover: if the panel can do something, an API call can, and the reverse.
Base URL and versioning
Section titled “Base URL and versioning”https://<your-installation>/api/v1The version is in the path. Within a version, fields are added but not removed or repurposed.
Conventions
Section titled “Conventions”Identifiers are prefixed and opaque. srv_…, lb_…, cert_…, s3s_…, job_….
Never construct one; always use what the API returned.
Reads are immediate, writes are jobs. A GET answers from the control plane database.
A POST, PATCH or DELETE that touches infrastructure validates, writes intent and a
job, and returns 202 Accepted with both the resource and the job. See
Asynchronous operations.
Everything is scoped to your organisation. A resource belonging to someone else
returns 404, not 403. Its existence is not disclosed.
Errors are typed.
| Status | Meaning |
|---|---|
422 | The request body is wrong. The response lists the offending fields |
409 | Well-formed, but a precondition failed. The message names it |
404 | Does not exist for you |
503 | An upstream (hypervisor, DNS provider) is temporarily unavailable. Retry with backoff |
The 409 case deserves attention because it is the one people retry blindly. It means the
world is not ready — a port is taken, a subnet has no egress, a resource is being deleted.
Retrying the same request produces the same conflict.
Resource families
Section titled “Resource families”| Path | Product |
|---|---|
/servers | Servers, power, resize, console, password reset |
/ssh-keys | Public keys for server creation |
/networks | Private networks and subnets |
/ip-pools, /public-ips | Public IPs — pool endpoints are operator-only |
/firewalls | Firewalls and attachments |
/gateways | Gateway |
/load-balancers | Load balancers and listeners |
/certificates | TLS certificates |
/k8s/clusters | Kubernetes, kubeconfig, console, verify |
/s3/stores | Object storage, buckets, keys |
/domains | Domains and DNS |
/catalog | Images, plans, regions |
/jobs | Job status |
/usage | Usage events |
The OpenAPI document
Section titled “The OpenAPI document”The specification is the contract, hand-written and kept current — the TypeScript client types are generated from it, which means a drifting spec breaks the build rather than silently misleading you.
Find it at docs/api/openapi.yaml in the source repository. Feed it to your generator of
choice.
A complete example
Section titled “A complete example”Z9=https://panel.example.com/api/v1AUTH="Authorization: Bearer $Z9_TOKEN"
# What can I create?curl -s -H "$AUTH" $Z9/catalog | jq '.images[].slug, .packages[].slug'
# Create a server — 202JOB=$(curl -s -X POST -H "$AUTH" -H 'Content-Type: application/json' \ -d '{"name":"web-1","region":"ist1","image":"ubuntu-24.04","package":"m"}' \ $Z9/servers | jq -r '.job.id')
# Wait properlyuntil [ "$(curl -s -H "$AUTH" $Z9/jobs/$JOB | jq -r .status)" \ = "succeeded" ]; do sleep 5; doneA production script must also stop on failed; see Asynchronous operations
for the full pattern.
Rate limits
Section titled “Rate limits”Be reasonable. Poll jobs no more than once every five seconds — a job is talking to a hypervisor and a tight loop achieves nothing but load.