> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tilion.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Errors are JSON. detail is the field to read, and it takes three shapes:

## The envelope

Errors are JSON. `detail` is the field to read, and it takes three shapes:

* A string, on most 401, 403, 404, 409 and 501 answers:

```json theme={"dark"}
{"detail": "No such session"}
```

* An object, on every answer a program should branch on. It always has `error` (a stable slug) and usually `message`; some add `retryable`, and context such as `limit`, `balance`, `tier`, `upgrade_url`, `retry_after_seconds`, `max_ttl`, `job_id`:

```json theme={"dark"}
{"detail": {"error": "stealth_always_on", "message": "sessions are stealth-hardened by construction; stealth cannot be disabled"}}
```

* A list, on 422 validation failures, one entry per problem with `type`, `loc`, `msg` and `input`:

```json theme={"dark"}
{"detail": [{"type": "missing", "loc": ["body", "url"], "msg": "Field required", "input": {"session_id": "sess_00000000000000000000000000000000"}}]}
```

Some errors use a top-level shape: 500 `{"status": "error", "detail": "internal error"}`, the 504 `{"error": "deadline_exceeded", ...}` and `{"error": "browser_timeout", ...}` answers, 413 `{"detail": "request body too large"}`, MCP errors, and credential-route errors such as `{"error": "credential not found", "detail": "credential not found"}`. A tolerant reader takes `detail.error` when `detail` is an object, `detail` when it is a string, joins `loc` and `msg` when it is a list, and falls back to a top-level `error`.

A wrong value answers the 422 list shape with the reason in `msg`; for example `POST /v1/session` with `{"region": "zz"}` answers `Value error, unknown region 'zz'; see GET /v1/regions`.

## Status codes

| Status | When | Retry |
| - | - | - |
| 400 | A request the route understands but refuses: a blocked or non-public URL, a bad selector, a bad promo code, `stealth: false`, a malformed path. | No, fix the request. |
| 401 | Missing, invalid, revoked or expired API key. `WWW-Authenticate: Bearer`. | No. |
| 402 | Out of credits, over the monthly allowance, a key over its spend limit, or a paid feature on a plan without it. | No, top up or upgrade. |
| 403 | The key lacks a scope, the account is suspended, a view or VNC token is wrong, or a feature is not enabled for the key. | No. |
| 404 | No such session, tab, job or saved login for this key, or the session's record has aged out. | No, except on DELETE where 404 means already gone. |
| 405 | `GET /mcp/`. The MCP transport takes POST and DELETE only. | No. |
| 406 | `POST /mcp/` without an `Accept` header that lists both `application/json` and `text/event-stream`. | No. |
| 409 | A conflict with the session's state: a region or proxy type that cannot be served, the last tab, no tab, no page loaded, a fetch already running on the session, an idempotency key still in flight, a code already redeemed. | Usually no; `session_busy` and `idempotency_key_in_use` after a moment. |
| 410 | The session has ended. `detail` is its terminal record. | No, create a new session. |
| 413 | Request body over 4 MB, or a download over its cap. | No. |
| 422 | The body failed validation, or a value is outside your plan (`ttl_exceeds_plan`, `session_ttl_cannot_hold_job`), or a task or template was given bad params. | No, fix the request. |
| 429 | A throttle, the concurrency cap, the tab cap, the inspect cap, or an auth block. `Retry-After` is set. | Yes, after `Retry-After`. |
| 500 | An unhandled failure. The body says only `internal error`; the cause is logged against `X-Request-ID`. | Once. |
| 501 | A feature that is not available: structured extraction, verifications, connect links. | No. |
| 502 | A browser operation failed: a session could not be launched, a page could not be rendered or captured, a file could not be downloaded, a sidecar did not answer. | Yes, with backoff. |
| 503 | No capacity, the requested exit or full browser is unavailable, the auth or session backend is unreachable. `Retry-After` is set where retrying helps. | Yes, after `Retry-After`. |
| 504 | A deadline: the synchronous deadline, a fetch past its budget (`deadline`), an inspect or file fetch past its cap, a page that stopped answering the browser (`browser_timeout`). The session is kept. | Yes, or run it as a job. |

Retry transient 429, 502, 503 and 504 responses with exponential backoff and jitter, honouring `Retry-After`; send an `Idempotency-Key` when retrying session creation. Retry 409 `session_busy` after the current work ends, or `idempotency_key_in_use` after the create settles. For `session_driven_by_client`, disconnect the main CDP, native Playwright, or VNC client that holds the session. CDP observers can remain connected. Other 4xx usually require changing the request.

Custom-proxy validation and connection failures use `customer_proxy_*` codes. See [Custom proxy errors](https://docs.tilion.dev/sessions/custom-proxy.md#errors). CDP connection limits use WebSocket close codes `4409`, `4429`, and `4403`; see [Connection close codes](https://docs.tilion.dev/sessions/playwright-cdp.md#connection-close-codes).

## Error slugs by route family

The `error` values the API answers with, grouped by where you meet them. A slug appears with the status it carries.

Authentication and account, any route: 401 (plain string), 403 `account_suspended`, 403 scope refusal (plain string), 402 `insufficient_credits`, 402 `quota_exceeded`, 402 key spend limit (plain string), 429 `rate_limited`, 400 `bad_path`.

Session create, `POST /v1/session`: 400 `stealth_always_on`, 400 `stealth_max_needs_premium_egress`, 402 `premium_egress_exhausted`, 409 `idempotency_key_in_use`, 409 `region_unavailable` (with `available`), 409 `proxy_type_unavailable`, 422 `ttl_exceeds_plan` (with `max_ttl`), 429 `concurrency_limit` (with `limit`, `tier`, `upgrade_url`), 502 `session_launch_failed`, 503 `capacity_unavailable` (`Retry-After: 5`), 503 `residential_vendor_unavailable` (`Retry-After: 60`), 503 `residential_egress_unavailable`, 503 `full_browser_unavailable`, 503 `recording_unavailable`.

Session reads and release, `GET /v1/session/{id}`, `/status`, `DELETE`: 404 (plain string), 410 terminal record, 410 `browser_crashed` (`retryable: false`), 410 plain string when the session's server is unreachable, 503 plain string when a release could not be confirmed.

Page reads, `POST /v1/fetch`, `GET /v1/job/{id}`: 404 plain string for an unknown job, 409 `session_busy` (with the running `job_id`), 410 `session_closed_during_fetch` (with `reason`, `origin`, `cause`), 422 `session_ttl_cannot_hold_job`, 504 with `error: "deadline"` in the fetch body itself.

Extract, scrape, crawl, inspect: 501 `structured_extraction_unavailable`, 502 `extract_failed`, 502 `scrape_failed` (`retryable: true`), 502 `inspect_failed`, 504 `inspect_timeout`, 429 `too_many_concurrent_inspects`.

Search, `POST /v1/search`: 422 `invalid_region`, 422 `unsupported_region`, 422 `region_with_session`, 429 `concurrency_limit`, 502 `session_launch_failed`, 502 `region_unavailable`, 503 `region_routing_unavailable`.

Tabs, elements, act, screenshot, navigate: 404 `element_not_found`, 400 `bad_selector`, 409 `no_page_loaded`, 409 `last_tab`, 409 `session_busy`, 409 `session_driven_by_client`, 404 plain `No such tab`, 429 `tab_limit`, 502 `Data Plane tab creation failed`.

Files and downloads, `POST /v1/session/{id}/files/fetch`: 409 `no_tab`, 413 plain string, 502 `file_fetch_failed` (`retryable: true`), 502 `download_failed`, 504 `file_fetch_timeout`.

Saved logins, `POST /v1/session/{id}/save-auth`, `/v1/auth/*`: 400 `incomplete_cookie_set`, 400 plain string when there is nothing to save, 404 plain string for an unknown domain.

Agent and templates, `POST /v1/agent`, `POST /v1/template`: 422 `invalid_agent_params`, 422 `invalid_template_params` (with `issues`), 400 plain string for an unknown task or template.

Sidecar, `/v1/session/{id}/sidecar*`: 403 `sidecar_not_enabled`, 409 `sidecar_unavailable`, 422 `sidecar_start_failed`, 502 `sidecar_unreachable`.

Credits, `POST /v1/credits/redeem`: 400 `unknown_code`, 409 `already_redeemed`.

Deadlines, any route: 504 `deadline_exceeded`, 504 `browser_timeout` (with `cdp_method` and `retry_with`).

## When a session has ended

A session ends when you release it, when it idles past `idle_timeout`, when it reaches `ttl`, when its browser crashes, or when the service ends it. Its record is kept for an hour after that, and during that hour:

* `GET /v1/session/{id}` and every drive call answer 410 with the record in `detail`.
* `GET /v1/session/{id}/status` answers 200 with the same record.
* `DELETE /v1/session/{id}` answers 204 with `Location: /v1/session/{id}/status`.
* After the hour, all of them answer 404 `No such session`.

The record:

```json theme={"dark"}
{
  "detail": {
    "session_id": "sess_6ac44536ab704d77b335f09ca281ada1",
    "owner": "acme",
    "state": "closed",
    "mode": "ephemeral",
    "region": null,
    "reason": "closed",
    "origin": "customer",
    "cause": null,
    "created_at": 1792420162.081036,
    "last_active": 1792420172.3192174,
    "idle_timeout": 300.0,
    "ttl": 3600.0,
    "closed_at": 1792420172.3192456,
    "cleanup": "done",
    "cleaned_at": 1792420172.5016863,
    "cleanup_finished_at": 1792420172.501687,
    "retained_until": 1792423772.3192456
  }
}
```

| Field | Meaning |
| - | - |
| `state` | `closed` (released, crashed or ended by the service) or `expired` (idle or ttl). |
| `reason` | `closed`, `idle_expired`, `ttl_expired`, or `data_plane:<why>` when the service ended it. |
| `origin` | Who ended it: `customer` (you), `expired` (idle or ttl), `data_plane` (the service, see `cause`), `host` (the machine it ran on). |
| `cause` | The failure that ended it when the service decided, else null. |
| `closed_at`, `cleanup`, `cleaned_at`, `cleanup_finished_at` | When it ended and whether teardown is `pending`, `done` or `failed`. |
| `retained_until` | When the record ages out and the id turns 404. |

Two 410s carry a different `detail`: `{"error": "browser_crashed", "retryable": false, "message": "..."}` when the browser crashed before the record was written, and `{"error": "session_closed_during_fetch", "message": "...", "reason": ..., "origin": ..., "cause": ...}` on a fetch whose session ended under it. A WebSocket on an ended session is refused on the handshake with the same 410 or 404; a bad token is refused with 403; an open socket whose browser crashes is closed with code 4410.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.