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 for this and the organization's other instances",
"balance_micro": 0,
"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 |
| 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, the instance cap is reached |
| 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 the seconds in 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 with a balance that 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), even a positive one | 402 | INSUFFICIENT_BALANCE | An admin adds credits in the console. The body carries balance_micro and required_micro. API keys cannot top up. |
The organization already has 25 instances that are not destroyed — stopped and error ones count | 409 | INSTANCE_LIMIT_REACHED | The body carries limit. Destroy instances you no longer need, including any in error, then deploy. |
| 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. Top-up orders, 发票 details, 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 |
| A template marked coming soon (its image is not published yet) | 422 | TEMPLATE_COMING_SOON | Pick another template. GET /v1/templates lists these, but they cannot be deployed. |
A template that pins an older CUDA stack (cuda_max) on a machine with a newer CUDA | 422 | TEMPLATE_CUDA_TOO_NEW | Pick a machine whose cuda_version is within the template's ceiling, or another template |
| A template that declares an architecture other than x86_64 | 422 | TEMPLATE_ARCH_UNSUPPORTED | Superheat hosts are x86_64. Use a template with an x86_64 image. |
| Another organization's template that pulls from a private registry with credentials | 422 | TEMPLATE_REQUIRES_OWNER | Only the owning organization can deploy it. Duplicate it into your organization and enter your own registry credentials (if you can pull that image). Nothing was created. |
| The offer's machine is not registered with the fleet's control plane | 409 | OFFER_NOT_PINNABLE | Listings never show such an offer; this comes from a stale offer_id. Re-list and pick another. Nothing was created. |
| Starting an instance whose GPUs were released, when those GPUs are now in use by someone else or out of service on that machine | 409 | CAPACITY_UNAVAILABLE | The instance is still stopped, with its disk intact, but the data on the disk cannot be reached until a start succeeds. Retry later; storage keeps billing meanwhile. If it keeps failing, the only way forward is to destroy the instance and deploy again — destroying deletes the disk and its data. |
| 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. |
| 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 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 |
| 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 |
Every deploy error is returned before anything is kept: a refused deploy leaves no instance behind.
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 INSTANCE_LIMIT_REACHED | Only after destroying at least one instance. |
409 OFFER_NOT_PINNABLE | Yes, with a different offer — the same one never succeeds. |
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.