REST errors
Every HTTP status and code the looot REST API answers with, and the fix for each.
Every REST error is a JSON body {error: {code, message, requestId}} plus an HTTP status. Some
routes add fields next to error; those are noted below. See Errors for the shape and
Idempotency and Fallback for background on a few of
these.
validation_error
400. The request body or query failed schema validation. Retryable: no. Fix: read the message,
which names the field; check it against the endpoint’s schema with inspect or
GET /v1/operations/{id}. See inspect.
unauthorized
401. The bearer token is missing or invalid. Retryable: no. Fix: sign in again
(looot login, or set LOOOT_TOKEN). See Sign in.
token_revoked
401. This token was revoked, or its login approval was denied. Retryable: no. Fix: run
looot login again, or an MCP client should reopen its sign-in flow. See Sign in.
forbidden
403. The token lacks the scope this route requires. Retryable: no. Fix: looot whoami (or the
MCP client’s own sign-in) lists its scopes; an owner or admin can issue a token, or approve a
sign-in, with the scope you need. See Access.
insufficient_balance
402. POST /v1/runs when the reservation would exceed the workspace’s available balance. Body
adds balanceMicros, estimatedCostMicros, topUpUrl, and topUp: {minimumUsd, suggestedUsd, checkoutUrl, dashboardUrl, message}. This is a pre-admission refusal: no hold is placed and
nothing is charged. Retryable: no. Fix: show topUp.checkoutUrl (Stripe’s hosted page) or
topUp.dashboardUrl to whoever holds the account, then retry with a new idempotencyKey once
paid. See Before your first run and Top up.
not_found
404. Unknown runId, connectionId, or catalog item. This is not what you get for an unknown
endpointId on POST /v1/runs, though: that answers 201 with status: "failed" and a
populated error instead. See Errors: failed runs. Retryable: no. See runs_list.
route_not_found
404. There’s no route at this method and path at all (a typo in the path, or a route this
gateway doesn’t serve). Retryable: no. Fix: check the path against the
API reference.
method_not_allowed
405. The route exists, but not for this HTTP method. Retryable: no. Fix: check the method
against the API reference.
idempotency_conflict
409. The idempotencyKey was already used with a different endpointId or input than the
first call that used it. Retryable: no. Fix: send the exact same input to replay the original
run, or use a new key for a new one. See Idempotency.
rows_mode_unsupported
422. The request needs a fleet capability this gateway’s storage mode doesn’t provide right now
(for example output.mode: "canonical", which production doesn’t serve). Body adds reason.
Retryable: no. Fix: drop the unsupported option, or use output.mode: "raw". See inspect.
too_many_inflight_runs
429. POST /v1/runs when this workspace already has limit runs in flight (8 by default). Body
adds inflight, limit, retryAfterSeconds, and a retry-after header. Retryable: yes. Fix:
wait for some runs to finish, then retry with the same idempotency key. See Idempotency.
rate_limit_exceeded
429. A token-bucket limiter rejected the request (60 burst, 10 per second per tenant by
default). Headers: x-ratelimit-limit, x-ratelimit-remaining, retry-after. Retryable: yes.
Fix: wait the retry-after time, then retry.
runs_cursor_invalid
400. The cursor sent to GET /v1/runs (or runs_list) is invalid or stale. Retryable: no.
Fix: start paging again from runs_list/GET /v1/runs with no cursor. See runs_list.
unknown_category
404. GET /v1/catalog/overview?category=... (or MCP catalog_overview, or
looot catalog overview --category) named a category id that doesn’t exist. Body adds field and
categories, the valid ids. Retryable: no. See catalog_overview.
unknown_platform
404. GET /v1/catalog/overview?platform=... named a platform id that doesn’t exist, or one
outside the given category. Body adds field. Retryable: no. See catalog_overview.
internal_error
500. An unhandled error on looot’s side. Retryable: usually yes. Fix: retry once; if it
persists, report it with the requestId. See Support.
catalog_loading
503. The catalog hasn’t finished loading in this gateway process yet. Retry with the
retry-after header. Retryable: yes.
storage_busy
503. The storage backend is briefly overloaded. Body adds retryAfterSeconds, and a
retry-after header is sent too. Retryable: yes. Fix: wait a few seconds and retry with the same
idempotency key. See Idempotency.
storage_uncertain
503. Service storage couldn’t confirm the last write. Retryable: yes. Fix: wait briefly, then
retry with the same idempotency key; this never means the write actually failed, only that this
node can’t yet confirm it landed. See Idempotency.
billing_unavailable
503. A billing route was called, but billing isn’t enabled on this gateway. Retryable: no. See Support.
runtime_bridge_timeout
503. The storage bridge didn’t respond in time. Body adds retryAfterSeconds, and a
retry-after header is sent too. Retryable: yes.
Settlement outcomes, not HTTP errors
Once a run is admitted (past every refusal above), its money outcome is never a blanket “the HTTP
call failed”: a definitive failure settles at $0 or the provider’s evidenced cost with the hold
released, and an uncertain outcome parks at status: "reconciliation_pending" with the hold kept
until it is reviewed. Check status and error on the run, not the HTTP status, to see which
applies; see Errors: failed runs and Run errors.