Canadian KVM VPS in Montréal from 8.50 CAD/month

API

Errors, limits and replay

The shape of a refusal, the stable codes, the rate buckets and idempotency.

A refusal is a response like any other: it has a stable shape, a code your program can read, and an identifier support can trace. Nothing useful hides in the message text.

The shape of a refusal

bash
{
  "error": {
    "code": "insufficient_credit",
    "message": "This order needs 0.44 CAD of credit; the balance is 0.12 CAD.",
    "details": { "required": "0.44", "balance": "0.12", "missing": "0.32", "currency": "CAD" },
    "request_id": "req_8fh2kq4m1x0a"
  }
}

Branch on code, never on message: the message is English, meant for a log, and may be rephrased. The code is part of the contract. details carries what you need to act — here, the exact amount to top up.

Every response, successful or not, carries an X-Request-Id header. Quote it when you write to us and we find the call.

The codes

New codes may appear. Treat an unknown one as an ordinary failure of its HTTP class.

CodeHTTP
missing_token401
invalid_api_key401
key_disabled401
key_expired401
insufficient_scope403
ip_not_allowed403
email_not_verified403
account_suspended403
forbidden403
validation_failed400
not_found404
request_refused422
insufficient_credit402
budget_cap_reached402
payment_required402
quota_reached409
service_suspended409
action_conflict409
idempotency_conflict409
hostname_taken409
order_in_progress409
too_many_pending_invoices409
coupon_not_found422
coupon_max_uses422
plan_unavailable_in_region422
image_unavailable_in_region422
image_incompatible_with_plan422
out_of_stock422
billing_mode_unavailable422
provisioning_failed500
rate_limited429
backend_unavailable503
internal_error500

Rate buckets

Per key: 120 reads a minute, 20 actions a minute, and 10 creations or reinstalls an hour. A reinstall destroys a disk and rebuilds a machine, so it falls in the same bucket as a creation.

Every response carries X-RateLimit-Limit and X-RateLimit-Remaining for the bucket the call fell in; a refusal answers 429 with Retry-After. A wider guard also applies per calling address, before authentication: a normal client never meets it, a script guessing tokens does.

Replaying without duplicating

POST /v1/vms requires an Idempotency-Key header: a value of your choosing, unique per order. If your request times out you do not know whether the machine was created — replaying with the same key returns the first response, as it was, with Idempotency-Replayed: true.

Three cases, one of them a replay: same key and same body, we serve it again; same key, different body, that is a mistake on your side and we refuse (409 idempotency_conflict); same key while the first is still running, we refuse rather than run both. Keys are kept for 24 hours.

Asynchronous actions

Provisioning, reinstalling, powering on: none of it is instant. Those calls answer 202 and an action object, which you poll until its status leaves running.

bash
{ "id": 90112, "type": "create", "status": "error", "vm_id": 4312,
  "source": "api",
  "error": { "code": "provisioning_failed",
             "message": "No IPv4 address left in the pool for this region." } }

An action does not stay open forever: after six hours without a conclusion — a lost task, a machine deleted before anyone read its fate — it turns to error with the code internal_error. A polling loop therefore always ends.

A machine's history is on GET /v1/vms/{id}/actions, and it is complete: a click in the console shows up there too, with source: console.

Support & discussions

Technical questions, incident reports, or infrastructure discussions, the team is reachable on Discord, Telegram, X, and IRC.