Errors
Any response outside the 2xx range carries a JSON body. Branch on code, never on message — messages are written for humans and can be reworded.
The envelope
{
"detail": {
"code": "INSUFFICIENT_BALANCE",
"message": "Add credits before deploying an instance",
"balance_microusd": 0,
"required_microusd": 2290000
}
}
| Key | Type | Meaning |
|---|---|---|
detail.code | string | Stable, uppercase, safe to branch on |
detail.message | string | One-line human explanation |
| Extra keys | varies | Context for that specific failure |
Two failures use a different shape. Request validation errors from the framework put a list under detail, one entry per bad field:
{"detail": [{"type": "greater_than_equal", "loc": ["body", "disk_gb"], "msg": "Input should be greater than or equal to 10"}]}
And an unexpected server-side failure puts a plain string there, such as {"detail": "Internal Server Error"}. Handle all three forms if you are writing a client library.
HTTP statuses
| Status | Meaning |
|---|---|
| 200 | The request succeeded |
| 201 | Something was created — an instance, a template copy, an API key |
| 204 | Succeeded with no body: revoking a key, deleting an SSH key |
| 400 | The request is malformed, such as an X-Org-Id that is not an id |
| 401 | The bearer token is missing, invalid, expired, revoked, or an API key on an account endpoint |
| 402 | The organization has no credit left |
| 403 | You are authenticated but not allowed: admin-only action, wrong organization, or an immutable object |
| 404 | Not found, or not visible to your organization — the two are deliberately indistinguishable |
| 409 | The state conflicts: the offer is taken, the instance is in the wrong status, the name is used |
| 422 | The values are wrong: a field out of range, a disk larger than the machine, an unknown enum value |
What a renter actually hits
| Condition | Status | Code | What to do |
|---|---|---|---|
| Deploying or starting with an empty balance | 402 | INSUFFICIENT_BALANCE | An admin adds credits in the console. The body carries balance_microusd and required_microusd. API keys cannot top up. |
| The offer was rented between your list call and your deploy | 409 | OFFER_UNAVAILABLE | Take the next offer from your filtered list and retry. Do not retry the same offer_id. |
| The machine is booked offline sooner than an instance can be started on it | 409 | MACHINE_IN_MAINTENANCE | The body carries next_maintenance. Deploy elsewhere, or wait for the window to pass. This is not a capacity problem — the machine has free GPUs — so retrying the same offer works once the window is over. |
disk_gb outside 10–20000 | 422 | validation list | Send a value inside the range |
disk_gb larger than the host's disk | 422 | DISK_TOO_LARGE | The body carries max_disk_gb. Retry at or below it, or pick a bigger machine. |
| Admin-only action, or any admin action attempted with an API key | 403 | ADMIN_REQUIRED | Use a session token belonging to an admin. Checkout, invites and member management are never available to keys. See Roles and permissions. |
| Revoking a key that is unknown, already revoked, or in another organization | 404 | API_KEY_NOT_FOUND | Re-list GET /v1/api-keys and use a current id |
| Stopping, starting or destroying from a status that forbids it | 409 | INVALID_STATE | Read status first. The message names the current one, for example Cannot start an instance while it is running. |
A vm template on a machine that is not VM-capable | 422 | OFFER_NOT_VM_CAPABLE | Filter for offers whose vm_capable is true |
An ssh_key_id that is not on your account | 404 | SSH_KEY_NOT_FOUND | List GET /v1/ssh-keys with a session token and use one of those ids |
| An instance, offer or template your organization cannot see | 404 | INSTANCE_NOT_FOUND, OFFER_NOT_FOUND, TEMPLATE_NOT_FOUND | Check the X-Org-Id you sent, and that the object still exists |
Authentication and organization failures — TOKEN_INVALID, TOKEN_EXPIRED, NOT_A_MEMBER, ORG_MISMATCH, INVALID_ORG_ID — are covered in Authentication. The complete list of codes is in Error codes.
Retrying
| Response | Retry? |
|---|---|
| 402 | No. Nothing changes until credits arrive. |
409 OFFER_UNAVAILABLE | Yes, with a different offer. |
409 MACHINE_IN_MAINTENANCE | Yes, with a different machine — or with the same one after next_maintenance. |
409 INVALID_STATE | Only after re-reading the instance and confirming the transition is now legal. |
| 422 | No. Fix the request first. |
401 TOKEN_EXPIRED | Yes, once, with a fresh session token. An shk_ key does not expire, so a 401 on a key means it was revoked. |
POST /v1/instances takes no idempotency key, so a timeout leaves you unsure whether the rental happened. Resolve it by looking, not by retrying: GET /v1/instances and match on your label.
Retrying the same offer_id is not safe. An offer is a shape, not a single slot — a machine with GPUs left answers the same offer again and you get a second instance, billed separately, from one intended deploy. It only 409s once that machine's units are gone, which is exactly the case where you were not double-charged anyway.