---
title: Errors
description: The shapes an error takes in the CLI, REST API, MCP tools and failed runs, plus a lookup table for every code and its fix.
---

Every error carries a stable `code` you can match on, a plain `message`, and, where there is one
thing to do, a fix. Quote the `requestId` (or a run's `runId`) when you ask for help.

There are four places an error can show up, and each has its own shape.

## CLI stderr

On a terminal, `looot` prints one line, then the fix:

```txt
The workspace balance is too low to reserve this run's estimated cost. (request req_...)
Fix: Add credits at https://looot.ai/usage?top_up=1
```

Piped, or with `--format json` or `--format jsonl`, it prints one JSON object on stderr and exits
with code 1:

```json
{
  "code": "insufficient_balance",
  "message": "The workspace balance is too low to reserve this run's estimated cost.",
  "retryable": false,
  "status": 402,
  "requestId": "req_...",
  "fix": "Add credits at https://looot.ai/usage?top_up=1",
  "error": { "code": "insufficient_balance", "message": "..." }
}
```

| Field | Meaning |
| --- | --- |
| `code` | Stable, snake_case. The gateway's own code when the gateway answered. |
| `message` | One sentence, never a stack trace or an HTML page. |
| `retryable` | `true` when the exact same command may succeed on a retry (a dropped connection, a timeout, a 429, a 5xx). `false` when the request itself has to change. |
| `status` | The HTTP status, when the gateway answered. |
| `requestId` | The gateway's id for the request. |
| `fix` | What to do next. |
| `retryCommand` | `looot run` only: the exact command to retry with the same idempotency key. |

Nothing on stdout mixes with an error: stdout carries only a command's result. Ctrl+C prints
`Cancelled.` and exits with code 130. See [CLI command reference](/cli) and
[CLI errors](/errors/cli-errors) for the local, network and file errors that never reach the
gateway.

## REST errors

`GET`/`POST`/etc against `https://api.looot.ai` answer with an HTTP status and a JSON body:

```json
{ "error": { "code": "idempotency_conflict", "message": "...", "requestId": "req_..." } }
```

Some routes add fields next to `error`. `POST /v1/runs` at 402 adds `balanceMicros`,
`estimatedCostMicros` and a `topUp` object with a checkout link; `POST /v1/runs` at 429 adds
`inflight`, `limit` and `retryAfterSeconds`. See [REST errors](/errors/rest-errors).

A `201` from `POST /v1/runs` can still carry `"status": "failed"` or `"status": "blocked"`, for
example when the input fails the endpoint's own schema. Check `status` and `error` on the run
body, never the HTTP status code alone.

## MCP tool errors

A failed tool call returns `isError: true`, with the same object in both `content[0].text` (as
compact JSON) and `structuredContent.data`:

```json
{
  "code": "idempotency_conflict",
  "message": "This idempotencyKey was already used with a different body. Reuse the exact same body to replay the original, or pick a new idempotencyKey for a new request.",
  "retryable": false,
  "requestId": "req_...",
  "error": "idempotency_conflict"
}
```

`message` always says what to do next. `error` repeats `code` as a plain string, kept for a
caller written against the older `{ error: "<code>" }` shape. Arguments that don't match a tool's
schema come back as `validation_error` naming the field. Anything unexpected comes back as
`internal_error` with a `requestId` to report. See [MCP errors](/errors/mcp-errors).

## Failed runs

When a run itself fails, the run record carries an `error` object, whatever surface fetched it
([`runs_get`](/mcp-tools/runs-get), `runs_list`, the inline result of `run`, or `GET /v1/runs/{id}`):

```json
{
  "code": "provider_error",
  "providerStatus": 502,
  "message": "The provider returned an error response.",
  "requestId": "req_...",
  "whoseError": "provider",
  "retryable": true,
  "retryHint": "Retry the run; if it keeps failing, the provider itself is rejecting or down."
}
```

`whoseError` says whose side the problem is on: `provider`, `gateway` or `customer` (you). A
failed run settles at $0 (or the provider's evidenced partial cost) and any remaining hold is
released. A `job:<id>` run that never called a provider gets its own two codes,
`route_capped` and `route_no_fit`; see [Job refusals](/errors/job-refusals) for those and for the
`result.code` values a `job:` run's failed body carries. See [Run errors](/errors/run-errors) for
the full list.

## Every code

| Code | Where | Section | What fixes it |
| --- | --- | --- | --- |
| `validation_error` | REST 400, MCP, run, CLI | [REST](/errors/rest-errors#validation_error) | [`inspect`](/mcp-tools/inspect) |
| `unauthorized` | REST 401, CLI | [REST](/errors/rest-errors#unauthorized) | [Sign in](/get-started/sign-in) |
| `token_revoked` | REST 401, CLI | [REST](/errors/rest-errors#token_revoked) | [Sign in](/get-started/sign-in) |
| `forbidden` | REST 403, MCP, CLI | [REST](/errors/rest-errors#forbidden) | [Access](/concepts/access) |
| `not_found` | REST 404, MCP | [REST](/errors/rest-errors#not_found) | [`runs_list`](/mcp-tools/runs-list) |
| `route_not_found` | REST 404 | [REST](/errors/rest-errors#route_not_found) | [API reference](/reference) |
| `method_not_allowed` | REST 405 | [REST](/errors/rest-errors#method_not_allowed) | [API reference](/reference) |
| `idempotency_conflict` | REST 409, MCP, CLI | [REST](/errors/rest-errors#idempotency_conflict) | [Idempotency](/concepts/idempotency) |
| `rows_mode_unsupported` | REST 422, MCP | [REST](/errors/rest-errors#rows_mode_unsupported) | [`inspect`](/mcp-tools/inspect) |
| `too_many_inflight_runs` | REST 429, MCP, CLI | [REST](/errors/rest-errors#too_many_inflight_runs) | [Idempotency](/concepts/idempotency) |
| `rate_limit_exceeded` | REST 429, CLI | [REST](/errors/rest-errors#rate_limit_exceeded) | [Wait and retry](/errors/rest-errors#rate_limit_exceeded) |
| `runs_cursor_invalid` | REST 400, MCP | [REST](/errors/rest-errors#runs_cursor_invalid) | [`runs_list`](/mcp-tools/runs-list) |
| `unknown_category` | REST 404, MCP, CLI | [REST](/errors/rest-errors#unknown_category) | [`catalog_overview`](/mcp-tools/catalog-overview) |
| `unknown_platform` | REST 404, MCP | [REST](/errors/rest-errors#unknown_platform) | [`catalog_overview`](/mcp-tools/catalog-overview) |
| `internal_error` | REST 500, MCP | [REST](/errors/rest-errors#internal_error) | [Support](/support) |
| `catalog_loading` | REST 503, MCP, CLI | [REST](/errors/rest-errors#catalog_loading) | [Wait and retry](/errors/rest-errors#catalog_loading) |
| `storage_busy` | REST 503, MCP, CLI | [REST](/errors/rest-errors#storage_busy) | [Idempotency](/concepts/idempotency) |
| `storage_uncertain` | REST 503 | [REST](/errors/rest-errors#storage_uncertain) | [Idempotency](/concepts/idempotency) |
| `billing_unavailable` | REST 503 | [REST](/errors/rest-errors#billing_unavailable) | [Support](/support) |
| `runtime_bridge_timeout` | REST 503 | [REST](/errors/rest-errors#runtime_bridge_timeout) | [Wait and retry](/errors/rest-errors#runtime_bridge_timeout) |
| `insufficient_balance` | REST 402, run, MCP, CLI | [REST](/errors/rest-errors#insufficient_balance) | [Top up](/money/top-up) |
| `provider_error` | run, CLI | [Run errors](/errors/run-errors#provider_error) | [Fallback](/concepts/fallback) |
| `provider_reported_error` | run, CLI | [Run errors](/errors/run-errors#provider_reported_error) | [Fallback](/concepts/fallback) |
| `timeout` | run, CLI | [Run errors](/errors/run-errors#timeout) | [Fallback](/concepts/fallback) |
| `rate_limited` | run | [Run errors](/errors/run-errors#rate_limited) | [Idempotency](/concepts/idempotency) |
| `auth_error` | run | [Run errors](/errors/run-errors#auth_error) | [Bring your own provider key](/guides/bring-your-own-key) |
| `invalid_response` | run | [Run errors](/errors/run-errors#invalid_response) | [Support](/support) |
| `unsupported_response_type` | run | [Run errors](/errors/run-errors#unsupported_response_type) | [Support](/support) |
| `response_too_large` | run | [Run errors](/errors/run-errors#response_too_large) | [Fallback](/concepts/fallback) |
| `redirect_blocked` | run | [Run errors](/errors/run-errors#redirect_blocked) | [Fallback](/concepts/fallback) |
| `output_mapping_error` | run | [Run errors](/errors/run-errors#output_mapping_error) | [`run`](/mcp-tools/run) |
| `endpoint_not_found` | run, CLI | [Run errors](/errors/run-errors#endpoint_not_found) | [`search`](/mcp-tools/search) |
| `eligibility_blocked` | run | [Run errors](/errors/run-errors#eligibility_blocked) | [Access](/concepts/access) |
| `activation_blocked` | run | [Run errors](/errors/run-errors#activation_blocked) | [`search`](/mcp-tools/search) |
| `credential_denied` | run | [Run errors](/errors/run-errors#credential_denied) | [Bring your own provider key](/guides/bring-your-own-key) |
| `platform_key_missing` | run | [Run errors](/errors/run-errors#platform_key_missing) | [Bring your own provider key](/guides/bring-your-own-key) |
| `platform_auth_incompatible` | run | [Run errors](/errors/run-errors#platform_auth_incompatible) | [Bring your own provider key](/guides/bring-your-own-key) |
| `credential_placement_unsupported` | run | [Run errors](/errors/run-errors#credential_placement_unsupported) | [Support](/support) |
| `budget_exceeded` | run | [Run errors](/errors/run-errors#budget_exceeded) | [Cost cap](/money/cost-cap) |
| `provider_disabled` | run, CLI | [Run errors](/errors/run-errors#provider_disabled) | [`search`](/mcp-tools/search) |
| `provider_draining` | run | [Run errors](/errors/run-errors#provider_draining) | [Fallback](/concepts/fallback) |
| `connection_not_found` | run | [Run errors](/errors/run-errors#connection_not_found) | [Access](/concepts/access) |
| `connection_invalid` | run | [Run errors](/errors/run-errors#connection_invalid) | [Bring your own provider key](/guides/bring-your-own-key) |
| `provider_http_error` | run | [Run errors](/errors/run-errors#provider_http_error) | [Fallback](/concepts/fallback) |
| `fetch_failed` | run | [Run errors](/errors/run-errors#fetch_failed) | [Fallback](/concepts/fallback) |
| `connection_unusable` | run | [Run errors](/errors/run-errors#connection_unusable) | [Bring your own provider key](/guides/bring-your-own-key) |
| `contract_invalid` | run | [Run errors](/errors/run-errors#contract_invalid) | [Support](/support) |
| `route_denied` | run | [Run errors](/errors/run-errors#route_denied) | [Support](/support) |
| `no_route_candidate` | run | [Run errors](/errors/run-errors#no_route_candidate) | [Fallback](/concepts/fallback) |
| `capacity_exhausted` | run | [Run errors](/errors/run-errors#capacity_exhausted) | [Wait and retry](/errors/run-errors#capacity_exhausted) |
| `cancelled_before_dispatch` | run | [Run errors](/errors/run-errors#cancelled_before_dispatch) | [`runs_cancel`](/mcp-tools/runs-cancel) |
| `cancellation_requested_after_dispatch` | run | [Run errors](/errors/run-errors#cancellation_requested_after_dispatch) | [`runs_evidence`](/mcp-tools/runs-evidence) |
| `runtime_store_replaced_before_dispatch` | run | [Run errors](/errors/run-errors#runtime_store_replaced_before_dispatch) | [Idempotency](/concepts/idempotency) |
| `route_capped` | run | [Job refusals](/errors/job-refusals#route_capped) | [Cost cap](/money/cost-cap) |
| `route_no_fit` | run | [Job refusals](/errors/job-refusals#route_no_fit) | [Fallback](/concepts/fallback) |
| `unknown_job` | run result | [Job refusals](/errors/job-refusals#unknown_job) | [`search`](/mcp-tools/search) |
| `no_supply_for_job` | run result, search | [Job refusals](/errors/job-refusals#no_supply_for_job) | [`capability_request`](/mcp-tools/capability-request) |
| `needs_input` | run result | [Job refusals](/errors/job-refusals#needs_input) | [Job inputs](/concepts/job-inputs) |
| `no_runnable_provider` | run result | [Job refusals](/errors/job-refusals#no_runnable_provider) | [Bring your own provider key](/guides/bring-your-own-key) |
| `invalid_input` | run result | [Job refusals](/errors/job-refusals#invalid_input) | [Job inputs](/concepts/job-inputs) |
| `unknown_tool` | MCP | [MCP errors](/errors/mcp-errors#unknown_tool) | [MCP tools](/mcp-tools) |
| `workflow_not_found` | MCP | [MCP errors](/errors/mcp-errors#workflow_not_found) | [`run`](/mcp-tools/run) |
| `top_up_amount_out_of_range` | MCP, run | [MCP errors](/errors/mcp-errors#top_up_amount_out_of_range) | [Top up](/money/top-up) |
| `top_up_unavailable` | MCP, run | [MCP errors](/errors/mcp-errors#top_up_unavailable) | [Top up](/money/top-up) |
| Local CLI codes (`token_required`, `network_error`, `invalid_input_json`, ...) | CLI only | [CLI errors](/errors/cli-errors) | [CLI errors](/errors/cli-errors) |

See [Money: free calls and failures](/money/free-and-failures) for how a failed or refused run is
charged.

<Related />
