Skip to main content

Error codes

An error response from https://lium.io/api has this shape (the routes listed under Routes with the older body are the exception):

{
"success": false,
"error": {
"code": "node_unavailable",
"message": "Node is not available.",
"hint": "The node was rented or went offline between listing and renting; pick another (`lium ls`) or retry in a minute.",
"request_id": "0fd17bdf43884d48af88bca99c884936"
},
"message": "Node is not available.",
"status_code": 400,
"error_id": null,
"request_id": "0fd17bdf43884d48af88bca99c884936",
"timestamp": "2026-09-07T01:48:42.756086",
"api_context": null
}
  • error.code is stable: key on it, not on the message text. The message may be reworded; the code will not.
  • error.hint says what to do next in one sentence.
  • error.request_id identifies this request in the platform's logs. The same value comes back in the X-Request-Id response header on every response, success or failure, and is the error_id of a 5xx. Quote it when you ask for help (support@lium.io, Discord #support): it points at every log line and the exception of that exact request.
  • You can send your own X-Request-Id (8–64 characters of A-Z a-z 0-9 . _ : -) and the API will keep it, so an agent's run id can be found in the platform logs.
  • The flat fields (message, status_code, error_id, validation_errors, details) are the older shape and stay for compatibility.

The codes​

error.codeHTTPWhenWhat to do
unauthorized401No key or session, or the key is unknownSend a valid key in X-API-Key (API keys); lium config get api.api_key shows which key the CLI uses, masked
api_key_inactive401The key was deactivated or has expiredCreate a new key
insufficient_balance403Balance is zero or negative (renting needs a positive balance covering 15 minutes of the node)Top up at lium.io/billing
account_balance_invalid403The account's balance could not be readContact support with the request_id; topping up does not clear it
email_not_verified403The account's e-mail is not verifiedVerify it, then retry
forbidden403The resource belongs to another account, or this key may not do thatCheck which key the CLI uses with lium config get api.api_key
pod_not_found404No such pod on this accountlium ps lists yours; a deleted pod's entries stay in the account's event log, GET /users/me/events (API key or signed-in session)
node_not_found404The node left the marketplace or the id is wrongPick another from lium ls (UUID or the huid it prints)
template_not_found404No such template, or a private one of another accountlium templates
not_found404Anything elseCheck the id
node_rent_in_progress400/409Another rental is starting on this nodeRetry in ~30 s or pick another node
node_not_verified400The validator has not passed this node yetChoose a node lium ls marks available
node_unavailable400The node was rented or went offline between listing and renting, or has no free GPUPick another node or retry in a minute
node_paused_by_provider409The provider paused new rentals on this nodePick another node
provider_banned409The provider is bannedPick a node from another provider
gpu_count_invalid400The requested GPU count is not available, is below the node's minimum, or a whole-node rental is requiredlium ls --format json shows gpu_count per node
gpu_split_not_allowed400This node cannot be splitRent all of its GPUs or pick a node that allows splitting
template_invalid400The template is not yours, or a one-time/temporary template is being reusedUse a template you own or an official one
ttl_invalid400Termination outside 1–720 hoursFix the value
pod_state_conflict400The pod is not in a state that allows this (already deleting, not running, stuck pending)lium ps shows its status; wait or delete it
bad_request400Refused for the reason in messageFix the input
validation_error422A field failed validationFix the fields listed in validation_errors
idempotency_key_invalid422POST /executors/{executor_uuid}/rent with an Idempotency-Key header that is not 1–255 letters, digits, -, _, . or :Send a well-formed key (a UUID is fine) or omit the header
idempotency_key_reused422The same Idempotency-Key was sent within 24 h with a different request bodyUse a new key for a new request; repeat the original request unchanged to get its first answer again (Idempotent-Replayed: true)
idempotency_in_progress409The first request with this Idempotency-Key is still runningRetry after the Retry-After seconds (1) with the same key to receive its answer
conflict409The resource changed under youRe-read and retry
no_executor_matches_spec409POST /executors/rent-by-spec: no node satisfies the spec; the body names the first constraint that left no candidate and the best value the remaining nodes offered for itRelax that constraint (or raise max_price_per_gpu_hour); lium ls shows what is available now
method_not_allowed405The path exists, but not for this HTTP methodGET /api/openapi.json lists the methods per path
rate_limited429Too many requests from this IPRetry after a minute
client_errorother 4xxRefused for the reason in messageFix the request; status_code says which rule
upstream_unavailable502–504A dependency (validator, payment provider) did not answerRetry in a minute; quote the request_id if it persists
internal_error500Unhandled errorRetry once; quote the request_id to support

A few endpoints send their own, upper-case codes in the same field (for example BACKUP_STORAGE_BUSY, POD_NOT_FOUND on the Discord support routes); they are documented on those endpoints.

Routes with the older body​

The /volumes, /bookmark and /machine-requests handlers catch their own errors and answer {"success": false, "message": "…"} with the HTTP status and no error object. Key on the status there. /backup-configs and /backup-logs re-raise their 4xx (so those carry the error object) and answer the flat body only on an unexpected 500. volume_not_found is sent by GET /volumes/{volume_id}/current-stats (404, through the global handler); the five CRUD handlers in routes/volume.py answer the flat body.

In the SDK and the CLI​

Coming with lium#190 (not in CLI 0.0.45, which prints the message only): every SDK exception carries the three fields:

from lium.sdk import Lium, LiumError

try:
Lium().up(executor_id="…")
except LiumError as e:
if e.code == "insufficient_balance":
... # top up and retry
elif e.code in ("node_unavailable", "node_rent_in_progress"):
... # pick another node
else:
raise # e.hint says what to do; e.request_id is what to quote

The CLI prints the hint and the request id under the error and exits non-zero:

$ lium up brave-wolf-26
Node brave-wolf-26 could not be rented: API error 400: Node is not available. Run 'lium ps' to check whether a pod was created. Run 'lium ls --format json' for the nodes rentable now.
The node was rented or went offline between listing and renting; pick another (`lium ls`) or retry in a minute.
request_id: 0fd17bdf43884d48af88bca99c884936

On the commands that take --json (lium exec, lium balance, lium describe, …; lium up has no --json) the envelope on stderr carries the server's code: {"ok": false, "error": {"code": "node_unavailable", "message": "…", "hint": "…", "exit_code": 3}, "data": {"request_id": "…"}}.