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

# Extract

## POST /v1/extract

Load a page in the session's browser and return its main content as Markdown. Output removes navigation, consent banners, share bars, related content, footers, and hidden elements. Links are absolute, and code blocks include their language. Optional formats include plain text, links, metadata, and rendered HTML.

Like `fetch`, extraction can attempt challenge solving and exit recovery for protected pages. Custom-proxy sessions keep their proxy exit. Disconnect the main CDP client and any native Playwright or VNC client before calling these routes. CDP observers can remain connected; coordinate their commands with extraction.

Auth: Bearer API key.

Request:

| Field | Type | Default | Meaning |
| - | - | - | - |
| `session_id` | string | required | A ready session you own. |
| `url` | string | required | Public http or https URL, up to 4096 characters. |
| `wait_ms` | integer, 0 to 60000 | 8000 | Ceiling for the page to finish loading before it is read. A page that is ready sooner is read sooner. Raise it for a heavy single-page app. |
| `formats` | array of `markdown`, `text`, `html`, `links`, `metadata` | null (markdown only) | What to return. With no `formats` the response is the markdown contract alone. With `formats`, `markdown` is returned only if listed. |
| `links` | `inline`, `footnote`, `none` | `inline` | How hyperlinks render in the markdown. `footnote` writes `[label][n]` references; `none` drops the URLs. |
| `max_chars` | integer, 100 to 10000000 | null | Cut the markdown and the text at a block boundary at this length; `truncated: true` reports a cut. |
| `include_images` | boolean | true | `false` drops every image from the markdown. |
| `schema` | object (JSON Schema) | null | Structured extraction into this shape. Not currently available: answers 501 `structured_extraction_unavailable`. |
| `prompt` | string, up to 2000 characters | null | Structured extraction by instruction. Not currently available: answers 501 `structured_extraction_unavailable`. |
| `async_mode` | boolean | false | Return at once with `status: "pending"` and a `job_id`; poll `GET /v1/job/{job_id}`. |

A plain request:

```json theme={"dark"}
{"session_id": "sess_6ac4459ad3e24b8289fc20911d76374f", "url": "https://en.wikipedia.org/wiki/Web_scraping"}
```

Response:

| Field | Type | Meaning |
| - | - | - |
| `session_id` | string | The session used. |
| `final_url` | string | Where the navigation landed. |
| `status` | string | `ok`, or a typed outcome: `source_empty` (the page produced no readable text: an unhydrated app shell or an image-only challenge), `challenged` (an anti-bot interstitial that was not cleared), `hard_blocked` (a block page or a 403 without a sign-in surface), `requires_login` (a sign-in wall or a 401), `rate_limited` (429 or a "too many requests" page). `pending` on an async start. |
| `blocked_reason` | string or null | A one-line reason, set when `status` is not `ok`. |
| `self_healed` | boolean | The content was reached only after a residential unlock. |
| `premium_required` | string or null | A blocked source could have been reached by residential unlock but the plan or the credit balance does not allow it. |
| `title` | string or null | Page title. |
| `markdown` | string or null | The main content as markdown. Null when `formats` omits `markdown`; can be empty on a non-`ok` outcome. |
| `markdown_len` | integer | Length of the returned markdown. |
| `tokens_estimate` | integer | A rough token count of the returned markdown (about a quarter of its length). |
| `text` | string or null | Plain text (link labels without URLs, no image or heading syntax). Only with `text` in `formats`. |
| `html` | string or null | The rendered page HTML. Only with `html` in `formats`. |
| `links` | array of strings or null | Absolute URLs of the page's links. Only with `links` in `formats`. |
| `metadata` | object or null | `{title, description, canonical, lang, og_title, og_description, og_image, og_site_name, published_time, modified_time, author, word_count, reading_time_min}`. Only with `metadata` in `formats`. |
| `truncated` | boolean | A `max_chars` cut was applied. |
| `data` | object or null | The structured result when `schema` or `prompt` was given and a model is available. |
| `extract_tokens` | integer or null | Model tokens spent on `data`; 0 when a cached answer was served. |
| `job_id` | string or null | Set on an async start. |

The Wikipedia page (markdown cut):

```json theme={"dark"}
{
 "job_id": null,
 "data": null,
 "extract_tokens": null,
 "session_id": "sess_6ac4459ad3e24b8289fc20911d76374f",
 "final_url": "https://en.wikipedia.org/wiki/Web_scraping",
 "status": "ok",
 "blocked_reason": null,
 "self_healed": false,
 "premium_required": null,
 "title": "Web scraping - Wikipedia",
 "markdown": "# Web scraping\n\nFrom Wikipedia, the free encyclopedia\n\nFor broader coverage of this topic, see [Data scraping](https://en.wikipedia.org/wiki/Data_scraping).\n\n...",
 "markdown_len": 48547,
 "tokens_estimate": 12136,
 "text": null,
 "html": null,
 "links": null,
 "metadata": null,
 "truncated": false
}
```

A news front page with every format, footnote links, no images and a 3000 character cut. The request was `{"session_id": "...", "url": "https://www.bbc.com/news", "formats": ["markdown", "text", "links", "metadata"], "links": "footnote", "max_chars": 3000, "include_images": false}` (markdown, text and the links list cut):

```json theme={"dark"}
{
 "session_id": "sess_6ac4459ad3e24b8289fc20911d76374f",
 "final_url": "https://www.bbc.com/news",
 "status": "ok",
 "blocked_reason": null,
 "self_healed": false,
 "premium_required": null,
 "title": "BBC News - Breaking news, video and the latest top stories from the U.S. and around the world",
 "markdown": "[LIVE][1]\n\n## ...",
 "markdown_len": 2897,
 "tokens_estimate": 724,
 "text": "LIVE\n\n...",
 "html": null,
 "links": [
  "https://www.bbc.com/watch-live-news/",
  "https://www.bbc.com/",
  "https://www.bbc.com/news",
  "https://www.bbc.com/sport",
  "..."
 ],
 "metadata": {
  "title": "BBC News - Breaking news, video and the latest top stories from the U.S. and around the world",
  "description": "Visit BBC News for the latest news, breaking news, video, audio and analysis. ...",
  "canonical": "https://www.bbc.com/news",
  "lang": "en-GB",
  "og_title": "BBC News - Breaking news, video and the latest top stories from the U.S. and around the world",
  "og_description": "Visit BBC News for the latest news, breaking news, video, audio and analysis. ...",
  "og_image": null,
  "og_site_name": null,
  "published_time": null,
  "modified_time": null,
  "author": null,
  "word_count": 1375,
  "reading_time_min": 7
 },
 "truncated": true
}
```

Async extract and its poll. `{"session_id": "...", "url": "https://example.com/", "async_mode": true}` answers with `status: "pending"` and a `job_id`. The poll on `GET /v1/job/{job_id}` reports `kind: "extract"` and, once done, the extract fields in `result`.

Residential unlock escalation. When the page comes back `challenged`, `hard_blocked`, `rate_limited` or `requires_login`, the read is retried through residential egress with server-side CAPTCHA solving before you see the typed status. If that reaches the content, the response is `ok` with `self_healed: true`. If your plan has no premium egress left, or the balance cannot cover a solve, the typed status is returned with `premium_required` set to a sentence that says which of the two it was. A `source_empty` page is not escalated: it is a hydration problem, so raise `wait_ms` instead.

Errors:

| Status | `error` or message | When |
| - | - | - |
| 400 | `unsafe url: ...` | The URL is not public http or https. |
| 402 | `insufficient_credits`, `quota_exceeded` | No credit for one action, 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. |
| 410 | terminal record | The session was released or expired. |
| 422 | validation error | Bad field; an empty `formats` list is refused. |
| 429 | rate limit | The per-key drive window was exceeded. |
| 501 | `structured_extraction_unavailable` | `schema` or `prompt` was given. The refusal comes before the page is loaded, so nothing is billed. |
| 504 | `extract exceeded 43s ... the page never stopped loading (infinite scroll / streaming feed). Retry with a smaller wait_ms, or target a URL that settles.` | The page did not settle inside the synchronous ceiling of 43 seconds. Run such pages with `async_mode: true`. |

Notes:

* Timing: a plain page returns in 1 to 3 seconds. The synchronous ceiling is 43 seconds for the navigation and the read together; `wait_ms` above that has no effect on a synchronous call. As a job the ceiling is `wait_ms` plus a margin for the read.
* The tab the extract opened stays open on the session, so a following `/act` or `/elements` acts on the page you just read.
* Billing: 1 credit per successful extract plus 1 credit per MB of markdown; nothing for a typed non-`ok` outcome. When the residential escalation delivered the page, its bytes are billed as premium egress and the solve as 20 credits. Billing is on the full markdown even when `max_chars` cuts what is returned.


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