Endpoints
Every v1 operation with scope, request and response examples.
Before calling
Set BASE=https://panel.expanse.host/api/reseller/v1 and RESELLER_KEY to your secret rk_ key. Replace IDs and catalog values. Full bounds and schemas.
Send Authorization: Bearer rk_... on all operations except openapi.json. Write does not imply read. Foreign resources return not_found.
| Scope | Operations |
|---|---|
| reseller.vms.read | Account, limits, catalog, locations, VM reads, stats, rDNS/rescue/backups GET, webhooks GET |
| reseller.vms.write | VM mutations, initial-password GET, rDNS PUT, rescue/backup/webhook mutations |
| reseller.usage.read | Usage and spend |
Each key allows 120 requests per 60 seconds. Webhook mutations additionally share 10 per 60 seconds per account. Limiters fail closed. Honor Retry-After on 429; vm_busy returns Retry-After: 30.
Create uses JSON idempotencyKey, NOT an Idempotency-Key header. Reuse within the account returns existing VM (200); new create is 201. Persist one key per VM/order and retry same payload/key after transport failure. No general idempotency facility on other mutations.
Actions return 202 {data:{accepted:true,taskId:123}}. taskId can be null; no public task lookup exists. Poll VM detail, rescue GET or backups GET as appropriate. Accepted is not completed. Legacy jobId, vm and credential fields remain. Pending DELETE retains VM fields with accepted/taskId. Create stays 201/200; reset-password/rDNS PUT stay 200.
Statuses: creating, building, active, failed, suspended, delete_requested, deleted. Power/buildState are nullable provider strings. RAM is MiB, disk GiB, dates UTC ISO 8601; backup timestamps Unix seconds. Usage bytes and spend micros/cents are decimal strings; stats counters numbers or null. One USD is 1000000 micros. All authenticated responses have Cache-Control: no-store; spec alone has public, max-age=300.
Endpoint reference
Full bounds and schemas are in the OpenAPI download. Replace IDs/catalog values. No body needed unless shown. Responses illustrate one possible outcome.
GET /account
Scope: reseller.vms.read.
curl -sS "$BASE/account" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": {
"limits": {
"maxVms": 1,
"maxVcpu": 1,
"maxRamMiB": 1,
"maxDiskGiB": 1,
"maxIpv4": 1
},
"used": {
"vms": 1,
"vcpu": 2,
"ramMiB": 2048,
"diskGiB": 40,
"ipv4": 1
},
"features": {
"rescue": false,
"ipv6": [],
"backups": false
},
"units": {
"ram": "MiB",
"disk": "GiB"
}
}
}
GET /limits
Scope: reseller.vms.read.
curl -sS "$BASE/limits" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": {
"vcpu": {
"min": 1,
"max": 1
},
"ramMiB": {
"min": 1,
"max": 1,
"step": 1
},
"diskGiB": {
"min": 1,
"max": 1
},
"ipv4": {
"min": 1,
"max": 1
},
"units": {
"ram": "MiB",
"disk": "GiB"
}
}
}
GET /locations
Scope: reseller.vms.read.
curl -sS "$BASE/locations" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": [
{
"key": "small",
"name": "customer-vps-1"
}
]
}
GET /os-templates
Scope: reseller.vms.read.
curl -sS "$BASE/os-templates" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": [
{
"id": "cmvmexample001",
"name": "customer-vps-1",
"version": "12",
"group": "Debian",
"label": "Debian 12"
}
]
}
GET /packages/{key}/os-templates
Scope: reseller.vms.read.
curl -sS "$BASE/packages/small/os-templates" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": [
{
"id": "cmvmexample001",
"name": "customer-vps-1",
"version": "12",
"group": "Debian",
"label": "Debian 12"
}
]
}
GET /packages
Scope: reseller.vms.read.
curl -sS "$BASE/packages" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": [
{
"key": "small",
"vcpu": 2,
"ramMiB": 2048,
"diskGiB": 40,
"units": {
"ram": "MiB",
"disk": "GiB"
}
}
]
}
GET /spend
Scope: reseller.usage.read.
curl -sS "$BASE/spend" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": {
"available": false,
"reason": "Billing has not started"
}
}
GET /usage
Scope: reseller.usage.read.
curl -sS "$BASE/usage?month=2026-10" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": {
"month": "2026-10",
"unit": "hour",
"timezone": "UTC",
"vms": [
{
"id": "cmvmexample001",
"vmId": "example",
"name": "customer-vps-1",
"package": "example",
"packageKey": "small",
"location": "default",
"vcpu": 2,
"ramMiB": 2048,
"diskGiB": 40,
"ipv4": 1,
"billableHours": 1,
"hours": 1,
"vcpuHours": 1,
"ramMiBHours": 1,
"diskGiBHours": 1,
"rxBytes": "1048576",
"txBytes": "1048576"
}
]
}
}
POST /vms/{id}/backups/{backupId}/restore
Scope: reseller.vms.write.
curl -sS -X POST "$BASE/vms/cmvmexample001/backups/1-3-1700000000/restore" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 202:
{
"data": {
"accepted": true,
"taskId": 123,
"jobId": 123
}
}
GET /vms/{id}/backups
Scope: reseller.vms.read.
curl -sS "$BASE/vms/cmvmexample001/backups" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": {
"backups": [
{
"id": "cmvmexample001",
"disk": 1,
"timestamp": 1,
"type": "example",
"size": 1
}
],
"page": 1,
"lastPage": 1
}
}
POST /vms/{id}/backups
Scope: reseller.vms.write.
curl -sS -X POST "$BASE/vms/cmvmexample001/backups" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 202:
{
"data": {
"accepted": true,
"taskId": 123,
"jobId": 123
}
}
GET /vms/{id}/initial-password
Scope: reseller.vms.write.
curl -sS "$BASE/vms/cmvmexample001/initial-password" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": {
"password": "example-secret"
}
}
POST /vms/{id}/ipv4
Scope: reseller.vms.write.
curl -sS -X POST "$BASE/vms/cmvmexample001/ipv4" -H "Authorization: Bearer $RESELLER_KEY" -H 'Content-Type: application/json' -d '{"quantity":1}'
HTTP 202:
{
"data": {
"accepted": true,
"taskId": 123
}
}
POST /vms/{id}/power
Scope: reseller.vms.write.
curl -sS -X POST "$BASE/vms/cmvmexample001/power" -H "Authorization: Bearer $RESELLER_KEY" -H 'Content-Type: application/json' -d '{"action":"start"}'
HTTP 202:
{
"data": {
"accepted": true,
"taskId": 123
}
}
GET /vms/{id}/rdns
Scope: reseller.vms.read.
curl -sS "$BASE/vms/cmvmexample001/rdns" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": [
{
"ip": "203.0.113.10",
"hostname": "vps1.example.com",
"status": "none"
}
]
}
PUT /vms/{id}/rdns
Scope: reseller.vms.write.
curl -sS -X PUT "$BASE/vms/cmvmexample001/rdns" -H "Authorization: Bearer $RESELLER_KEY" -H 'Content-Type: application/json' -d '{"ip":"203.0.113.10","hostname":"vps1.example.com"}'
HTTP 200:
{
"data": {
"ip": "203.0.113.10",
"hostname": "vps1.example.com",
"status": "pending"
}
}
POST /vms/{id}/rebuild
Scope: reseller.vms.write.
curl -sS -X POST "$BASE/vms/cmvmexample001/rebuild" -H "Authorization: Bearer $RESELLER_KEY" -H 'Content-Type: application/json' -d '{"osTemplateId":14}'
HTTP 202:
{
"data": {
"accepted": true,
"taskId": 123
}
}
POST /vms/{id}/rescue
Scope: reseller.vms.write.
curl -sS -X POST "$BASE/vms/cmvmexample001/rescue" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 202:
{
"data": {
"accepted": true,
"taskId": 123,
"password": "example-secret"
}
}
DELETE /vms/{id}/rescue
Scope: reseller.vms.write.
curl -sS -X DELETE "$BASE/vms/cmvmexample001/rescue" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 202:
{
"data": {
"accepted": true,
"taskId": 123
}
}
GET /vms/{id}/rescue
Scope: reseller.vms.read.
curl -sS "$BASE/vms/cmvmexample001/rescue" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": {
"enabled": false
}
}
POST /vms/{id}/reset-password
Scope: reseller.vms.write.
curl -sS -X POST "$BASE/vms/cmvmexample001/reset-password" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": {
"password": "example-secret"
}
}
POST /vms/{id}/resize
Scope: reseller.vms.write.
curl -sS -X POST "$BASE/vms/cmvmexample001/resize" -H "Authorization: Bearer $RESELLER_KEY" -H 'Content-Type: application/json' -d '{"vcpu":4,"restart":true}'
HTTP 202:
{
"data": {
"accepted": true,
"taskId": 123,
"vm": {
"id": "cmvmexample001",
"name": "customer-vps-1",
"hostname": "vps1.example.com",
"initialPasswordAvailable": false,
"loginMethod": "password",
"packageKey": "small",
"location": "default",
"vcpu": 2,
"ramMiB": 2048,
"diskGiB": 40,
"pendingIpv4": 1,
"reconcileNeeded": false,
"ipv4Count": 1,
"status": "active",
"endUserRef": "example",
"ips": [
"203.0.113.10"
],
"ipv6": [],
"power": "running",
"buildState": "complete",
"createdAt": "2026-10-24T10:00:00.000Z",
"activeAt": "2026-10-24T10:00:00.000Z",
"deletedAt": null
}
}
}
GET /vms/{id}
Scope: reseller.vms.read.
curl -sS "$BASE/vms/cmvmexample001" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": {
"id": "cmvmexample001",
"name": "customer-vps-1",
"hostname": "vps1.example.com",
"initialPasswordAvailable": false,
"loginMethod": "password",
"packageKey": "small",
"location": "default",
"vcpu": 2,
"ramMiB": 2048,
"diskGiB": 40,
"pendingIpv4": 1,
"reconcileNeeded": false,
"ipv4Count": 1,
"status": "active",
"endUserRef": "example",
"ips": [
"203.0.113.10"
],
"ipv6": [],
"power": "running",
"buildState": "complete",
"createdAt": "2026-10-24T10:00:00.000Z",
"activeAt": "2026-10-24T10:00:00.000Z",
"deletedAt": null
}
}
DELETE /vms/{id}
Scope: reseller.vms.write.
curl -sS -X DELETE "$BASE/vms/cmvmexample001" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": {
"id": "cmvmexample001",
"name": "customer-vps-1",
"hostname": "vps1.example.com",
"initialPasswordAvailable": false,
"loginMethod": "password",
"packageKey": "small",
"location": "default",
"vcpu": 2,
"ramMiB": 2048,
"diskGiB": 40,
"pendingIpv4": 1,
"reconcileNeeded": false,
"ipv4Count": 1,
"status": "active",
"endUserRef": "example",
"ips": [
"203.0.113.10"
],
"ipv6": [],
"power": "running",
"buildState": "complete",
"createdAt": "2026-10-24T10:00:00.000Z",
"activeAt": "2026-10-24T10:00:00.000Z",
"deletedAt": null
}
}
GET /vms/{id}/stats
Scope: reseller.vms.read.
curl -sS "$BASE/vms/cmvmexample001/stats" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": {
"power": "running",
"cpuPercent": 1,
"memory": {
"totalMiB": 1,
"usedMiB": 1
},
"traffic": {
"periodStart": "example",
"periodEnd": "example",
"rxBytes": 1,
"txBytes": 1
}
}
}
POST /vms/{id}/suspend
Scope: reseller.vms.write.
curl -sS -X POST "$BASE/vms/cmvmexample001/suspend" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 202:
{
"data": {
"accepted": true,
"taskId": 123
}
}
POST /vms/{id}/unsuspend
Scope: reseller.vms.write.
curl -sS -X POST "$BASE/vms/cmvmexample001/unsuspend" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 202:
{
"data": {
"accepted": true,
"taskId": 123
}
}
GET /vms
Scope: reseller.vms.read.
curl -sS "$BASE/vms" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": [
{
"id": "cmvmexample001",
"name": "customer-vps-1",
"hostname": "vps1.example.com",
"initialPasswordAvailable": false,
"loginMethod": "password",
"packageKey": "small",
"location": "default",
"vcpu": 2,
"ramMiB": 2048,
"diskGiB": 40,
"pendingIpv4": 1,
"reconcileNeeded": false,
"ipv4Count": 1,
"status": "active",
"endUserRef": "example",
"ips": [
"203.0.113.10"
],
"ipv6": [],
"power": "running",
"buildState": "complete",
"createdAt": "2026-10-24T10:00:00.000Z",
"activeAt": "2026-10-24T10:00:00.000Z",
"deletedAt": null
}
]
}
POST /vms
Scope: reseller.vms.write.
curl -sS -X POST "$BASE/vms" -H "Authorization: Bearer $RESELLER_KEY" -H 'Content-Type: application/json' -d '{"name":"customer-vps-1","hostname":"vps1.example.com","location":"default","packageKey":"small","osTemplateId":14,"idempotencyKey":"order-20261024-0042","ipv4":1,"endUser":{"ref":"customer-42","email":"customer@example.com","name":"Example Customer"}}'
HTTP 201:
{
"data": {
"id": "cmvmexample001",
"name": "customer-vps-1",
"hostname": "vps1.example.com",
"initialPasswordAvailable": false,
"loginMethod": "password",
"packageKey": "small",
"location": "default",
"vcpu": 2,
"ramMiB": 2048,
"diskGiB": 40,
"pendingIpv4": 1,
"reconcileNeeded": false,
"ipv4Count": 1,
"status": "building",
"endUserRef": "example",
"ips": [
"203.0.113.10"
],
"ipv6": [],
"power": "running",
"buildState": "complete",
"createdAt": "2026-10-24T10:00:00.000Z",
"activeAt": "2026-10-24T10:00:00.000Z",
"deletedAt": null
}
}
DELETE /webhooks/{id}
Scope: reseller.vms.write.
curl -sS -X DELETE "$BASE/webhooks/cmvmexample001" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": {
"deleted": true
}
}
GET /webhooks
Scope: reseller.vms.read.
curl -sS "$BASE/webhooks" -H "Authorization: Bearer $RESELLER_KEY"
HTTP 200:
{
"data": [
{
"id": "cmvmexample001",
"url": "https://hooks.example.com/corepanel",
"events": [
"vm.active"
],
"enabled": false,
"createdAt": "2026-10-24T10:00:00.000Z",
"deliveries": [
{
"id": "cmvmexample001",
"event": "example",
"status": "pending",
"attempts": 1,
"lastStatusCode": 1,
"createdAt": "2026-10-24T10:00:00.000Z",
"deliveredAt": "2026-10-24T10:00:00.000Z"
}
]
}
]
}
POST /webhooks
Scope: reseller.vms.write.
curl -sS -X POST "$BASE/webhooks" -H "Authorization: Bearer $RESELLER_KEY" -H 'Content-Type: application/json' -d '{"url":"https://hooks.example.com/corepanel","events":["vm.active"]}'
HTTP 201:
{
"data": {
"id": "cmvmexample001",
"url": "https://hooks.example.com/corepanel",
"events": [
"vm.active"
],
"enabled": false,
"createdAt": "2026-10-24T10:00:00.000Z",
"deliveries": [
{
"id": "cmvmexample001",
"event": "example",
"status": "pending",
"attempts": 1,
"lastStatusCode": 1,
"createdAt": "2026-10-24T10:00:00.000Z",
"deliveredAt": "2026-10-24T10:00:00.000Z"
}
],
"secret": "example-hook-secret"
}
}
GET /openapi.json
curl -sS "$BASE/openapi.json"
HTTP 200: complete unwrapped OpenAPI document.
Create and query constraints
Create with either packageKey or all of vcpu, ramMiB, diskGiB, never both. Custom bounds: vCPU 1–32, RAM 1024–262144 MiB in steps of 1024, disk 10–1024 GiB. ipv4 is 1–8 (default 1); omit ipv6 for the location default. sshKeys allows up to five trimmed, deduplicated OpenSSH public keys (ed25519, RSA or ECDSA), at most 8192 UTF-8 bytes per key. SSH-key VMs have no initial password.
GET /vms accepts endUserRef; backup lists accept positive integer page (default 1) and return page/lastPage. Usage requires month=YYYY-MM (UTC). Power accepts start, stop, restart, poweroff. See VM lifecycle, webhooks and errors for operation semantics.