GPU Forge

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:

JSON
{"detail": "no job 'job_000000000000'"}

or an object with more fields:

JSON
{
  "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:

FieldMeaning
errorWhat went wrong
helpWhat to do next
nothing_rentedtrue: no machine was rented, nothing is billing
balance_emptyThe refusal is because your balance is $0
daily_budget_reachedThe capability's daily budget is spent
capability_inactiveThe capability is switched off
quote_usd, max_price_usdA job's quote and your cap
indeterminate, machine_exists, pod_idSee 502

Invalid request bodies (422) use a different shape that names each problem:

JSON
{
  "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

StatusMeaningWhat to do
200, 201, 202Success. 201 = created, 202 = accepted and running in the background (jobs)
400The 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_podsRead the message. For an offer, search again and copy the new offer_id exactly
401No bearer tokenSend Authorization: Bearer <key>
402Payment 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-upTop up (console or POST /v1/billing/top-up), raise the cap or budget, or wait for 00:00 UTC
403Not 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 machineUse the console, or a valid key
404Not found: the id does not exist, or belongs to another accountCheck the id with a list call (GET /v1/fleet, /v1/capabilities, /v1/jobs, /v1/catalog)
409Conflict 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 capRead the message. For "in progress", try again in a moment
410A CLI login code expired or was already usedRun gpuforge login again
422The body or a parameter failed validation, a route that is not a job route, or update-capability on files without sha256Fix the field named in problems or the message
429Too many CLI logins waiting for approvalTry again in a few minutes
502A provider failed or did not confirmSee below
503A part of the service is temporarily unavailableRetry 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": true means one certainly was. Do not retry: a retry can rent a second machine. Find it by the pod_id in the body with GET /v1/fleet/{pod_id}, and destroy it if you do not want it. Without indeterminate, 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 502 means every provider failed. If only some failed, you get 200 with their errors in provider_errors.

Errors from ready programs

A job reports the program's own errors inside the job, not on POST /v1/jobs:

JSON
{"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:

StatusMeaning
400A field is invalid; the message names it
401Missing or wrong program key (own machine only)
422The request was refused; the message says why (for example an image of a child, or a prompt that cannot be drawn)
503The machine is busy (see Retry-After) or its program key is not set
500The 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

CallSafe to retry?
Any GETYes
POST /v1/capabilities/{id}/scaleYes: it is a target, not an increment
POST /v1/fleet/{pod_id}/destroy, /release, /parkYes
POST /v1/jobsEach call is a new job, billed separately. Retry only after the first one failed
POST /v1/capabilities/{id}/deploy, POST /v1/deployOnly after a clear refusal (4xx, or 5xx without indeterminate). Never after indeterminate
POST /v1/billing/top-upCheck GET /v1/billing/top-ups first: a pending top-up may still succeed

This page as Markdown, for tools and agents: /docs/errors.md