Errors
GPU Forge uses standard HTTP status codes. Every refusal says why, and most say what to do next. A refusal of an action that costs money means nothing was rented and nothing was charged, unless the answer explicitly says otherwise (see 502 below).
Error format
Most errors have a detail field. It is either a sentence:
{"detail": "no job 'job_000000000000'"}or an object with more fields:
{
"detail": {
"error": "another rental for this capability is already running",
"capability_id": "cap_ab12cd34",
"nothing_rented": true,
"help": "A scale is renting for this capability right now. ... Try again in a moment."
}
}Fields you may find in an object detail:
| Field | Meaning |
|---|---|
error | What went wrong |
help | What to do next |
nothing_rented | true: no machine was rented, nothing is billing |
balance_empty | The refusal is because your balance is $0 |
daily_budget_reached | The capability's daily budget is spent |
capability_inactive | The capability is switched off |
quote_usd, max_price_usd | A job's quote and your cap |
indeterminate, machine_exists, pod_id | See 502 |
Invalid request bodies (422) use a different shape that names each problem:
{
"error": "the request body was not accepted",
"problems": [
{"field": "spec.min_vram_gb", "problem": "Input should be greater than or equal to 0", "you_sent": -1},
{"field": "spec.min_vram_gbb", "problem": "Extra inputs are not permitted", "you_sent": 48,
"expected": "Unknown field — check the spelling. GET /v1/help lists every accepted field."}
],
"help": "GET /v1/help for every field, its units, and examples."
}Values that may carry a secret (anything under secrets, SSH keys) are never echoed back in you_sent.
A few refusals are returned as a top-level {"error": "...", ...} object without detail (for example a balance refusal raised at the moment of renting). Read detail first, then error.
Status codes
| Status | Meaning | What to do |
|---|---|---|
200, 201, 202 | Success. 201 = created, 202 = accepted and running in the background (jobs) | |
400 | The request cannot work as sent: an expired or hand-made offer_id, no max_price_per_hour on an automatic deploy, min_pods above desired_pods | Read the message. For an offer, search again and copy the new offer_id exactly |
401 | No bearer token | Send Authorization: Bearer <key> |
402 | Payment needed: balance empty or too low to start, a job's quote above your max_price_usd, a capability's daily budget reached, or a card declined on top-up | Top up (console or POST /v1/billing/top-up), raise the cap or budget, or wait for 00:00 UTC |
403 | Not allowed: invalid or revoked key, an action only the person signed in on the console can do (cards, automatic top-up, the daily cap, API keys), a suspended account, a field you may not change on a ready program, or (on POST /v1/deploy) a balance too low to cover the machine | Use the console, or a valid key |
404 | Not found: the id does not exist, or belongs to another account | Check the id with a list call (GET /v1/fleet, /v1/capabilities, /v1/jobs, /v1/catalog) |
409 | Conflict with the current state: an offer above your price limit, another rental for the same capability in progress, the capability switched off, a machine that cannot be parked, a capability with machines still running (on delete), a program already added, no saved card, or the daily cap | Read the message. For "in progress", try again in a moment |
410 | A CLI login code expired or was already used | Run gpuforge login again |
422 | The body or a parameter failed validation, a route that is not a job route, or update-capability on files without sha256 | Fix the field named in problems or the message |
429 | Too many CLI logins waiting for approval | Try again in a few minutes |
502 | A provider failed or did not confirm | See below |
503 | A part of the service is temporarily unavailable | Retry after a short wait |
502 and machines that may be billing
A 502 from a call that rents or ends a machine needs care:
- On a rental (
deploy,scale,POST /v1/deploy): if the body has"indeterminate": true, a machine may have been created and may be billing;"machine_exists": truemeans one certainly was. Do not retry: a retry can rent a second machine. Find it by thepod_idin the body withGET /v1/fleet/{pod_id}, and destroy it if you do not want it. Withoutindeterminate, the provider refused and nothing was rented. - On a destroy: the provider has not yet confirmed the machine is gone. The request is saved and retried automatically, and the machine stays in your fleet until absence is confirmed. You can call destroy again safely.
- On a search: a
502means every provider failed. If only some failed, you get200with their errors inprovider_errors.
Errors from ready programs
A job reports the program's own errors inside the job, not on POST /v1/jobs:
{"status": "failed", "status_code": 422, "error": "the program answered 422", "price_usd": 0.0,
"result": {"error": "..."}}A failed job is never charged. Common program answers:
| Status | Meaning |
|---|---|
400 | A field is invalid; the message names it |
401 | Missing or wrong program key (own machine only) |
422 | The request was refused; the message says why (for example an image of a child, or a prompt that cannot be drawn) |
503 | The machine is busy (see Retry-After) or its program key is not set |
500 | The render failed; try again, perhaps with another seed |
A job that could not get a machine within 20 minutes fails with "error": "no machine of this program could be started in time; nothing was charged".
Retrying safely
| Call | Safe to retry? |
|---|---|
Any GET | Yes |
POST /v1/capabilities/{id}/scale | Yes: it is a target, not an increment |
POST /v1/fleet/{pod_id}/destroy, /release, /park | Yes |
POST /v1/jobs | Each call is a new job, billed separately. Retry only after the first one failed |
POST /v1/capabilities/{id}/deploy, POST /v1/deploy | Only after a clear refusal (4xx, or 5xx without indeterminate). Never after indeterminate |
POST /v1/billing/top-up | Check GET /v1/billing/top-ups first: a pending top-up may still succeed |
This page as Markdown, for tools and agents: /docs/errors.md