Quickstart
Discover the catalog, provision a VM and retrieve its initial password securely.
First VM
In CorePanel → Reseller create a scoped rk_ API key; store the value securely (shown once). Ask the operator to enable your account and grant capacity first. Discover actual catalog values; examples are illustrative.
export BASE='https://panel.expanse.host/api/reseller/v1'
export RESELLER_KEY='rk_REPLACE_WITH_YOUR_KEY'
curl -sS "$BASE/account" -H "Authorization: Bearer $RESELLER_KEY"
curl -sS "$BASE/packages" -H "Authorization: Bearer $RESELLER_KEY"
curl -sS "$BASE/packages/small/os-templates" -H "Authorization: Bearer $RESELLER_KEY"
curl -sS -X POST "$BASE/vms" -H "Authorization: Bearer $RESELLER_KEY" -H 'Content-Type: application/json' -d '{"name":"customer-vps-1","packageKey":"small","osTemplateId":14,"idempotencyKey":"order-20261024-0042","endUser":{"ref":"customer-42","email":"customer@example.com","name":"Example Customer"}}'
Save data.id; poll GET /vms/{id} with backoff until active; stop on failed. When initialPasswordAvailable is true fetch the password once; store it securely immediately and never log it. SSH-key VMs have none.
export VM_ID='REPLACE_WITH_DATA_ID'
curl -sS "$BASE/vms/$VM_ID" -H "Authorization: Bearer $RESELLER_KEY"
curl -sS "$BASE/vms/$VM_ID/initial-password" -H "Authorization: Bearer $RESELLER_KEY"
Polling and credentials
Power (start/stop/restart/poweroff), suspend, unsuspend, rebuild and IPv4 actions
return immediately after provider acceptance: 202 { "data": { "accepted": true, "taskId": 123 } }.
taskId is null if the provider does not supply a queue id. Acceptance does not
mean completion. Read the VM again for live state. Suspend/unsuspend update local
status and suspension reasons optimistically. IPv4 remains reserved until reconciled.
A provider lock (HTTP 423) maps to 409 vm_busy with Retry-After: 30; other provider failures
map to 502 provider_error. Reset-password returns expectedPassword from the
accept response without polling.
New VMs belong to one internal platform owner per reseller. End-customer
names and emails remain in CorePanel only. Provider mail is disabled.
POST /vms accepts optional hostname: a lowercase RFC 1123 FQDN, at most 253
characters, labels at most 63 characters. The default is the VM name slug plus
.local. VM responses include hostname and initialPasswordAvailable.
New VMs remain building until a detail read or hourly sweep confirms commissioning.
The post-build reset uses sendMail: false; its expectedPassword is encrypted at
rest. Failed resets retry on later reads/sweeps. No password is passed to build.
GET /vms/{id}/initial-password requires reseller.vms.write and returns
{ "data": { "password": "..." } } exactly once, clearing the stored ciphertext.
While building it returns 409 vm_busy; unavailable, consumed or expired passwords
return 404 not_available. Other accounts receive 404 not_found. Unfetched
passwords expire after seven days and are purged by the hourly traffic cron.
Retry safely
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.