Skip to main content
Every error — all of them, on every endpoint — comes back in one shape:
string
required
The broad class. Six values; see below. Branch on this for retry policy.
string
required
Stable and machine-readable. Branch on this for specific handling.
string
required
Human-readable. Written for a developer reading a log — safe to show a user, but not localised.
string
required
Deep link to this page, anchored at the code.
string
required
Quote it to support and we can find the exact request.
string
The offending parameter, when one is identifiable.
Some codes add fields — required_scope, retry_after_seconds, limit_cents, committed_cents, resets_at. They are always inside the error object, never beside it, so a parser that reads body.error gets everything.

Branch on type first

billing_error is the one worth special-casing. It looks transient — the request was valid, nothing was malformed — but retrying it unchanged will fail forever. A retry loop that treats 402 like 429 will hammer us until its own timeout and still not start a machine.

Every code

Authentication — 401

invalid_api_key

401 · authentication_error No usable API key was provided. Send it as Authorization: Bearer gpk_…. Check for the usual suspects: a missing Bearer prefix, a truncated paste, an environment variable that did not survive into the container, or a trailing newline from $(cat key.txt).

api_key_revoked

401 · authentication_error The key was revoked in the dashboard. Revocation is immediate and final — create a new key. If you did not revoke it, someone with dashboard access did. Check with your team before minting a replacement.

api_key_expired

401 · authentication_error The key passed its expiry date. Create a new one.

Permission — 403

insufficient_scope

403 · permission_error · carries required_scope The key is valid but lacks the scope this endpoint needs. Scopes are fixed at creation — mint a new key. See Scopes.

test_mode_not_supported

403 · permission_error A test key tried to create or stop a rental. Test keys authenticate like live keys and read the same real catalog, but there is no sandbox behind this API — a rental they created would be a real, billed machine. Use a live key.

account_suspended

403 · permission_error · code banned The account is suspended. Contact help@gpuoutlet.ai.

Request — 400, 404, 409

invalid_request

400 · invalid_request_error · often carries param One or more parameters are invalid. param names the first offender when we can identify one.

invalid_cursor

400 · invalid_request_error The cursor does not match the current filters or sort order. Cursors are valid only for the exact query that produced them — see Pagination.

pagination_limit_exceeded

400 · invalid_request_error The result set is too deep to page through. Narrow the filters rather than paging further.

not_found

404 · invalid_request_error No such resource, or it is not visible to you. The two are reported identically on purpose: distinguishing them would confirm that another account’s resource exists.

offer_not_found

404 · invalid_request_error No such offer, or it is no longer visible. Offers are transient — a supplier can withdraw one at any time. Re-read /offers and pick another.

offer_unavailable

409 · invalid_request_error The offer exists but cannot be rented right now: no free capacity at the size you asked for, or the seller withdrew it. Specific to this offer — pick a different one rather than retrying this one. Contrast with capacity_unavailable (503), which is not offer-specific.

max_active_rentals_reached

409 · invalid_request_error The account is at its concurrent-rental ceiling. Stop one first — and remember that a rental in stopping still occupies a slot until it reaches stopped. See Concurrency.

invalid_template

400 · invalid_request_error No such template. Omit template_id to boot the default image.

Idempotency — 400, 409

idempotency_key_required

400 · invalid_request_error POST /rentals spends money, so Idempotency-Key is mandatory. Generate one per intent — a UUID is fine. See Idempotency.

idempotency_key_in_flight

409 · invalid_request_error · carries retry_after_seconds A request with this key is still being processed. This is the correct answer to a client that retried before the first attempt finished — it stops you creating two machines. Wait retry_after_seconds and retry with the same key.

idempotency_key_reused

409 · invalid_request_error This key was already used with a different request body. We refuse rather than silently serving the first rental or overwriting it, because both would be wrong. Use a new key for a new rental.

Billing — 402

All three are billing_error. None of them is retryable as-is.

insufficient_balance

402 · billing_error The balance does not cover the hold this rental requires. Top up, then retry.

account_past_due

402 · billing_error There is an unpaid balance. Settle it before starting new rentals.

key_spend_limit_exceeded

402 · billing_error · carries limit_cents, committed_cents, resets_at This key’s daily spend cap would be crossed. Raise the limit, use another key, or wait until resets_at. See Spend limits.

Rate limits — 429

rate_limited

429 · rate_limit_error · Retry-After header Too many requests. Back off for the stated interval. See Rate limits.

daily_quota_exceeded

429 · rate_limit_error The daily request quota for this key is spent. It resets at 00:00 UTC. Slowing down will not help before then.

Ours — 5xx

capacity_unavailable

503 · api_error Capacity we expected to be there was not, and no rental was created. Temporary and not specific to the offer you asked for. Retry with backoff; if it persists across several offers, check status.gpuoutlet.ai.

api_error

5xx · api_error Our fault. Retry with exponential backoff and jitter. If it persists, send us the request_id.

A retry policy that works

Three things this gets right: it branches on type rather than on the status code alone, it prefers the server’s Retry-After over its own guess, and it adds jitter so a fleet of workers does not retry in lockstep.