Authentication
Dedicated keys, independent scopes, rate limits and IP restrictions.
Authentication and scopes
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.
Optional key IP allowlist
Create a key with allowedIps: ["203.0.113.10", "2001:db8::/48"] in the
reseller dashboard or admin reseller key form. Enter one address or network per
line. Empty lists allow any IP, including for keys created before this feature.
Lists accept at most 20 entries, deduplicate, and store canonical addresses.
IPv4 prefixes must be /8–/32; IPv6 prefixes /48–/128. CIDRs with host bits set
are rejected with the correct network form. Zone IDs and malformed entries are
rejected. IPv4-mapped IPv6 callers match IPv4 entries.
Session-authenticated owners/admins can update their own key with
PATCH /api/teams/{teamId}/reseller/keys/{keyId} and platform admins can use
PATCH /api/admin/resellers/{teamId}/keys/{keyId}. Body: { "allowedIps": [] }
clears restrictions; an omitted field preserves them; null is invalid. These
management endpoints are session-only and are not restricted by key IP lists,
so an owner can recover access from any IP. Cross-account keys return 404.
Revoked keys return 409 key_revoked; PATCH never restores them.
Authentication checks the secret/revocation/account, then per-key rate limit,
then scope, then IP, before updating last-use time. Restrictions and revocations
are read afresh for every request. A denied IP still consumes the rate limit.
Missing client IPs and malformed stored lists fail closed. IP denials return
403 ip_not_allowed with "This API key is not allowed from this IP address."
They do not disclose the allowlist or refresh last-use time. Input validation
returns 400 invalid_request with the bad entry and a correction when applicable.
Use the public egress address seen by the panel when configuring the allowlist.