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": "Deploying needs about 1h of credit up front",
"balance_micro": 1500000,
"required_micro": 2305000
}
}
| 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's balance does not cover the action, or payments are frozen |
| 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 |
| 410 | Gone: an invite that was used, has expired, or was revoked |
| 413 | The request body is larger than the endpoint accepts |
| 422 | The values are wrong: a field out of range, a disk larger than the machine, an unknown enum value |
| 429 | Too many requests: wait for Retry-After |
| 503 | Temporarily unavailable: a start the host could not complete, or sign-in checks that cannot run right now. Retry shortly |
What a renter actually hits
| Condition | Status | Code | What to do |
|---|---|---|---|
| Deploying or starting when the balance does not cover one hour of the organization's burn with that instance running (its GPU and disk, plus the GPU and disk of instances running, starting or being created and the disk of stopped ones) — a positive balance is not enough | 402 | INSUFFICIENT_BALANCE | An admin adds credits in the console. The body carries balance_micro and required_micro. API keys cannot top up. |
| The offer was rented between your list call and your deploy, or while your deploy was being reserved | 409 | OFFER_UNAVAILABLE | Take the next offer from your filtered list and retry. Do not retry the same offer_id. Nothing was created. |
| 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 the credential may not attach: not your own, or — with a team key — not a current member's | 404 | SSH_KEY_NOT_FOUND | List GET /v1/ssh-keys with a session token and use one of those ids |
| The organization already holds 25 instances that are not destroyed | 409 | INSTANCE_LIMIT_REACHED | The body carries limit. Destroy one first — stopped and errored instances count, so stopping frees nothing. |
| A payment dispute is open on the organization | 402 | PAYMENTS_FROZEN | Deploys and starts are paused until the dispute closes. |
| A template that is not published yet, pins a CUDA older than the machine's, or needs non-x86_64 hardware | 422 | TEMPLATE_COMING_SOON, TEMPLATE_CUDA_TOO_NEW, TEMPLATE_ARCH_UNSUPPORTED | Before deploying, check the template's coming_soon and its constraints.cuda_max and constraints.arch against the offer's cuda_version. |
| Another organization's template that pulls from a private registry, or an offer whose machine the control plane does not know | 422, 409 | TEMPLATE_REQUIRES_OWNER, OFFER_NOT_PINNABLE | Nothing was created. Duplicate the template with your own registry credentials, or re-list offers. |
| Starting an instance whose host cannot take it | 409, 503 | HOST_UNREACHABLE, START_REFUSED, START_UNAVAILABLE | The instance is still stopped. Retry later — for START_UNAVAILABLE, shortly. A START_REFUSED whose message says the host no longer holds the workload will not succeed: destroy and redeploy. |
Starting an instance whose GPUs were released (gpus_released), while another renter uses one of them | 409 | CAPACITY_UNAVAILABLE | The instance is still stopped, with its disk. Retry later; a start has to get the same GPUs back, so no other machine or offer will do. |
| Too many requests from one caller | 429 | RATE_LIMITED | Wait the number of seconds in the Retry-After header (also in the body as retry_after_s), then retry. |
| A request body over the size limit | 413 | PAYLOAD_TOO_LARGE | The body carries limit_bytes. Send less: 1 MiB for most endpoints, 4 MiB for templates. |
| 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 — or, for PAYMENTS_FROZEN, until the dispute closes. |
409 INSTANCE_LIMIT_REACHED | Only after destroying an instance. |
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 — allowed_actions says what is. |
409 HOST_UNREACHABLE, START_REFUSED, CAPACITY_UNAVAILABLE | Later, with backoff. The instance stays stopped in the meantime. |
429 RATE_LIMITED | Yes, after Retry-After seconds. |
| 503 | Yes, after a short wait. |
| 422 | No. Fix the request first. For INVALID_CURSOR, start the listing again without a cursor. |
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.