Expanse API
Sign up
Reseller API

Reseller API

Provision and manage wholesale VMs through a scoped, versioned API.

Base URL and access

https://panel.expanse.host/api/reseller/v1

Ask Expanse to enable your account and grant capacity. Create a scoped rk_ key in CorePanel → Reseller; store it securely (shown once). Account sk_ keys and browser sessions are not accepted. Discover real catalog values; examples are illustrative.

Integration contract

Send Authorization: Bearer rk_... on all operations except openapi.json. Write does not imply read. Foreign resources return not_found.

ScopeOperations
reseller.vms.readAccount, limits, catalog, locations, VM reads, stats, rDNS/rescue/backups GET, webhooks GET
reseller.vms.writeVM mutations, initial-password GET, rDNS PUT, rescue/backup/webhook mutations
reseller.usage.readUsage 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.

Customer ownership 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.