> ## 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.

# Inspect and solve

## POST /v1/inspect

Drive the session's browser to a URL, capture all network traffic for a period, and return a report of the site's API calls, auth signals and asset loading. Use it to find the JSON endpoints behind a page before you scrape it.

Auth: Bearer API key.

Request:

| Field | Type | Default | Meaning |
| - | - | - | - |
| `session_id` | string | required | A ready session you own. |
| `url` | string | required | The page to watch. Public http or https. |
| `duration` | number, 0 or more | 60 | Seconds of network capture, capped at 180. The whole call ends within the capture plus 45 seconds. |
| `async_mode` | boolean | false | Return a job at once; poll `GET /v1/job/{job_id}`. |

Request:

```json theme={"dark"}
{"session_id": "sess_6ac4459ad3e24b8289fc20911d76374f", "url": "https://example.com/", "duration": 3}
```

Response (synchronous):

| Field | Type | Meaning |
| - | - | - |
| `url` | string | The page watched. |
| `duration_s` | number | Actual capture length. |
| `total_calls` | integer | Network calls seen. |
| `by_kind` | object | Count per kind: `document`, `api`, `asset`, ... |
| `api_endpoints` | array | First-party JSON and XHR calls, each with method, URL (query stripped), status, mime and kind. `body_sample` is null; response bodies are not included. |
| `auth_signals` | array | Calls that set cookies or carried an `Authorization` header. |
| `asset_summary` | object | Count per asset type. |
| `markdown` | string | The same report as a markdown document. |

```json theme={"dark"}
{
 "url": "https://example.com/",
 "duration_s": 4.1,
 "total_calls": 2,
 "by_kind": {"document": 1, "asset": 1},
 "api_endpoints": [],
 "auth_signals": [],
 "asset_summary": {"Script": 1},
 "markdown": "# Site reconnaissance: https://example.com/\n\nCaptured **2** network calls over 4.1s.\n\n| kind | count |\n|---|---|\n| document | 1 |\n| asset | 1 |\n\n## API endpoints (0)\n_none observed_\n\n## Auth signals (0)\n_none observed_\n\n## Assets\n- Script: 1"
}
```

With `async_mode: true` the answer is a job envelope:

```json theme={"dark"}
{"session_id": "sess_6ac445c25d9d4d6eafac24308f4060e7", "job_id": "sess_6ac445c25d9d4d6eafac24308f4060e7.c543a77567f24fce8d95bd23af4a8d54", "kind": "inspect", "status": "pending"}
```

and the poll's `result` is the report above once `status` is `done`.

Concurrency: three captures may run at once per API key, counted across the whole service. A fourth answers 429 `too_many_concurrent_inspects` with `Retry-After: 5`, `X-RateLimit-Limit: 3`, `X-RateLimit-Remaining: 0`. Run concurrent captures on separate sessions; captures on one session contend for the same browser and can time out against each other.

Errors:

| Status | `error` or message | When |
| - | - | - |
| 400 | `unsafe url: ...` | The URL is not public http or https. |
| 402 | `insufficient_credits`, `quota_exceeded` | No credit, or the plan allowance is used up. |
| 404 | `No such session` | Unknown or foreign session id. |
| 409 | `session not ready`, `session browser is not alive` | The session cannot be driven. |
| 409 | `session_busy` | Another call or job is driving the session. Retry when it ends. |
| 409 | `session_driven_by_client` | A main CDP, native Playwright, or VNC client holds the session. Disconnect it first. CDP observers can remain connected. |
| 410 | terminal record | The session was released or expired. |
| 429 | `too_many_concurrent_inspects` | A fourth capture for this key. |
| 502 | `inspect_failed` | The capture itself failed (the page could not be navigated, the browser refused the capture); `detail` names it. |
| 504 | `inspect_timeout` | The capture did not finish within `duration + 45` seconds. On a job this lands in the poll's `result.error`. |

Notes:

* Timing: a 3 second capture answers in about 4.5 seconds. Anything that may run past 57 seconds in total must use `async_mode`, so in practice a synchronous `duration` above about 10 seconds is a risk.
* Billing: 1 action credit per capture on credit-funded accounts. Browser time is metered separately.

## POST /v1/solve

Clear a challenge widget or interstitial blocking the page the session is on: hCaptcha, reCAPTCHA v2 and v3, Turnstile, a Cloudflare interstitial. You do not have to name the wall. Call it after a `/navigate` or `/act` hit a challenge, or after a `/v1/fetch` came back `blocked`, and carry on with the same session: the clearance stays on the tab that earned it.

Auth: Bearer API key.

Request:

| Field | Type | Default | Meaning |
| - | - | - | - |
| `session_id` | string | required | The session whose page is blocked. |
| `url` | string | null | Navigate here first. Omit to solve whatever the session is already on; a session with no open tab then needs a `url`. |
| `kind` | string | null | Accepted for compatibility; the hosted service detects the challenge and does not use this hint. |
| `timeout` | number, 1 to 300 | 45 | Seconds to spend, navigation included. A synchronous call lowers anything above 50 to 50 and reports it; an async one lowers anything above 180. |
| `async_mode` | boolean | false | Return a job at once; poll `GET /v1/job/{job_id}`. |

Response:

| Field | Type | Meaning |
| - | - | - |
| `solved` | boolean | A solver ran and the wall is verified gone. The page is re-probed after the solve; a solver that claims success while the wall is still up answers false. |
| `already_clear` | boolean | No challenge was found, so nothing was solved and nothing is billed. |
| `kind` | string | The challenge family the probe named, or empty. Decide on `solved` and `already_clear`, not on `kind`. |
| `reason` | string | `cleared`, `nothing_to_solve: ...`, `challenge_not_cleared`, `not_enough_time_to_heal`, a navigation failure, or exactly `deadline` when the time ran out. |
| `detail` | string or null | Additional explanation, such as which phase used the budget. |
| `session_kept` | boolean, when present | The session remains open after the attempt. |
| `detected`, `recovery` | object or null | The challenge detected and recovery attempted, as on fetch. |
| `cause`, `failure_type` | string or null | Failure classification when the attempt could not complete. |
| `elapsed_ms` | integer | Time spent. |
| `session_id` | string | The session. |
| `timeout_s` | number | The budget applied. |
| `timeout_clamped` | boolean | The requested `timeout` was lowered to the limit. |
| `job_id`, `status` | string | With `async_mode`: the job to poll and `pending`. |

A page without a challenge:

```json theme={"dark"}
{
 "solved": false,
 "kind": "cloudflare",
 "reason": "nothing_to_solve: no challenge found on the page",
 "elapsed_ms": 170,
 "session_id": "sess_6ac4459ad3e24b8289fc20911d76374f",
 "timeout_s": 45.0,
 "timeout_clamped": false,
 "already_clear": true
}
```

Errors:

| Status | `error` or message | When |
| - | - | - |
| 400 | `unsafe url: ...` | `url` is not public http or https. |
| 402 | `solving a challenge needs more credits (top up at /dashboard)` | The balance cannot cover one solve (20 credits). Checked before anything runs. |
| 404 | `No such session` | Unknown or foreign session id. |
| 409 | `session not ready`, `session browser is not alive` | The session cannot be driven. |
| 410 | terminal record | The session was released or expired. |
| 429 | rate limit | The per-key drive window was exceeded. |

Notes:

* Timing: the default budget is 45 seconds, capped at 50 for synchronous calls and 180 for async calls. A timeout can return `solved: false`, `reason: "deadline"`, a separate `detail`, and `session_kept: true`. Compare `reason` to `"deadline"` exactly. Retry with `async_mode: true` and a larger `timeout` (up to 180), or use `POST /v1/fetch` with `async_mode: true` for exit escalation within its 900-second job budget. For an IP-tied block, prefer fetch or rotate the exit.
* Billing: 20 credits when `solved` is true (a solver ran and the wall is gone), including the case where the navigation itself cleared the wall. Nothing for `already_clear` or a failed solve.
* A `/v1/fetch` that comes back `blocked` has usually already tried the solvers on its own; `/v1/solve` is for the page you are driving by hand.


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