Skip to main content

Errors and Rate Limits

Error format

All errors return a JSON body with a consistent shape:

{
"error": {
"code": "INVALID_PARAMETER",
"message": "Field 'cidr' is required",
"field": "cidr",
"requestId": "req_abc123def456"
}
}
  • code: stable machine-readable error code, safe to switch on.
  • message: human-readable description, may change between versions.
  • field: optional, present for validation errors.
  • requestId: include this in any support ticket.

HTTP status codes

StatusMeaningWhat to do
200SuccessProcess the response body.
201CreatedResource created, ID is in the response.
202AcceptedAsync job started, poll for completion.
400Bad requestFix the request payload. Check field in the error.
401UnauthorizedToken is missing, invalid, or expired. Re-authenticate.
403ForbiddenToken is valid but the action is not allowed for this user.
404Not foundResource ID does not exist or is in another account.
409ConflictResource state does not allow this action (e.g. delete on a running VM).
422Validation failedRequest shape is valid but business rules rejected it.
429Rate limitBack off and retry. See below.
500Server errorRetry with exponential backoff. Open a ticket if it persists.
503Service unavailablePlatform in maintenance or overloaded. Retry.

Common error codes

CodeMeaning
INVALID_PARAMETERA required parameter is missing or malformed.
INVALID_TOKENJWT is missing, expired, or signed incorrectly.
MFA_REQUIREDAccount has MFA and the token is pre-MFA only.
RESOURCE_NOT_FOUNDThe ID in the URL does not exist in your account.
RESOURCE_BUSYThe resource is locked by another operation. Retry shortly.
QUOTA_EXCEEDEDAccount quota would be exceeded by this operation.
INSUFFICIENT_CAPACITYThe platform cannot fulfil the request right now. Retry or try a different zone.
RATE_LIMITEDToo many requests. Slow down.
OPERATION_NOT_ALLOWEDResource state forbids this action (e.g. stop on a Stopped VM).

Async jobs

Many operations are asynchronous. The API returns 202 immediately with a job ID, and you poll for the result:

{
"jobId": "job_abc123",
"status": "pending"
}

Poll the job:

api GET "/jobs/$JOB_ID"

States:

  • pending: queued, not yet started.
  • running: in progress.
  • succeeded: finished, result field contains the created resource.
  • failed: finished, error field describes why.

A robust polling pattern:

wait_for_job() {
local job_id=$1
for i in $(seq 1 60); do
local job
job=$(api GET "/jobs/$job_id")
local status
status=$(echo "$job" | jq -r '.status')
case "$status" in
succeeded) echo "$job" | jq '.result'; return 0 ;;
failed) echo "$job" | jq '.error'; return 1 ;;
*) sleep 5 ;;
esac
done
echo "Timed out after 5 minutes" >&2
return 2
}

Operations that are always async:

  • Instance create, start, stop, reboot, destroy.
  • Volume create, attach, detach, resize.
  • VPC and tier create.
  • Kubernetes cluster create, scale, upgrade, delete.
  • Backup create and restore.

Operations that are always sync:

  • All GET calls.
  • Login and authentication.
  • Listing resources.

Rate limits

The API enforces rate limits per account to protect the platform:

TierRequests per minuteBurst
Standard30050
Higher tiersOn requestOn request

Limits apply across all endpoints and all users in your account combined.

When you exceed the limit, the API returns 429 with headers:

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1716624000
Retry-After: 12

Retry-After is the number of seconds to wait before retrying.

Avoiding rate limits

  • Cache lookup data: zone, offering, and template IDs change rarely. Fetch them once at the start of your script and reuse.
  • Use list endpoints: GET /instances returns many instances in one call. Do not loop GET /instances/{id} for known IDs.
  • Batch where possible: bulk endpoints exist for tagging, security group rules, and ACL rules.
  • Back off on 429: respect Retry-After. A simple exponential backoff with jitter prevents thundering herd.

Example backoff in bash:

call_with_retry() {
local max=5
for attempt in $(seq 1 $max); do
response=$(api "$@")
status=$?
if [ $status -eq 0 ]; then
echo "$response"
return 0
fi
if echo "$response" | grep -q "RATE_LIMITED"; then
sleep $((2 ** attempt))
continue
fi
return $status
done
return 1
}

Timeouts

  • Connect timeout: clients should use 10 seconds.
  • Read timeout: 60 seconds for sync calls, 30 seconds for polling.
  • Long-running async operations: do not hold the connection open, use job polling.

Idempotency

The API does not currently support idempotency keys. To make your scripts safe to re-run:

  1. Check whether the resource exists first by name or tag.
  2. If it exists, use the existing ID.
  3. If not, create it.

Pattern:

existing=$(api GET "/vpcs?name=my-prod-vpc" | jq -r '.vpcs[0].id // empty')
if [ -n "$existing" ]; then
VPC_ID="$existing"
else
VPC_ID=$(api POST "/vpcs" -d "..." | jq -r '.vpc.id')
fi

Idempotency key support is on our roadmap.

When to contact support

Open a ticket if you see:

  • Persistent 500 errors after retries.
  • Async jobs stuck in pending or running for more than 30 minutes.
  • 401 responses with a token that should be valid.
  • Rate limit errors when you believe you are well under the limit.

Always include:

  • The requestId from the error response.
  • The full request (URL, method, body with secrets redacted).
  • The full response including status code and body.
  • The time of the request in UTC.