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

# Extract

> Readable text and markdown from URLs you already have.

`POST /api/extract` runs Truscan's extractor on URLs you supply. It is the same
extractor that fills `content` on search results, reached directly.

```bash theme={"dark"}
curl https://api.truscan.co/api/extract \
  -H "Authorization: Bearer $TRUSCAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [
      "https://example.com/article",
      "https://example.org/paper"
    ]
  }'
```

<ParamField body="urls" type="string[]" required>
  Between 1 and 8 absolute `http` or `https` URLs.
</ParamField>

## Options

Every option is additive. Omit them all and you get the whole readable body,
which is what search asks for.

<ParamField body="max_chars" type="integer">
  Caps the emitted text. The page is still fetched and parsed in full, so
  `words` and `excerpt` describe the page rather than the slice you kept, and
  `truncated` reports that the cut happened.
</ParamField>

<ParamField body="omit_text" type="boolean">
  Drops `text` and `markdown`, keeping the excerpt, the metadata and anything
  else you asked for. Use it when you want highlights or links and would
  otherwise pay to move an entire page over the wire to throw it away.
</ParamField>

<ParamField body="highlight_query" type="string">
  Scores the page's sentences against this query and returns the best matching
  passages with their offsets into `text`.
</ParamField>

<ParamField body="highlights_max" type="integer" default="3">
  How many passages to return. Clamped to 20.
</ParamField>

<ParamField body="links" type="boolean">
  Collects the article's outbound links and images as absolute URLs. Taken from
  the extracted article, not the whole document: a page's navigation is the same
  on every page of the site and is never what you meant.
</ParamField>

<Note>
  Options only ever narrow or annotate the response. Billing counts pages read,
  so asking for highlights or dropping the body changes what comes back, never
  what it costs. The fetch and the parse happened either way.
</Note>

## Response

`result` is an object keyed by the URL you submitted, so you can look each one
up directly:

```json theme={"dark"}
{
  "success": true,
  "message": "Extracted the pages we could reach.",
  "result": {
    "https://example.com/article": {
      "text": "Plain readable text...",
      "markdown": "## Heading\n\nReadable markdown...",
      "excerpt": "First meaningful paragraph...",
      "words": 1420,
      "lang": "en",
      "author": "Jane Doe",
      "site_name": "Example",
      "truncated": false
    }
  }
}
```

| Field             | Meaning                                                                         |
| ----------------- | ------------------------------------------------------------------------------- |
| `text`            | Body copy with navigation, ads, and boilerplate removed                         |
| `markdown`        | The same content with headings, lists, and links preserved                      |
| `excerpt`         | A short summary-length opening extract                                          |
| `words`           | Word count of the full page, before any `max_chars` cut                         |
| `lang`            | Detected language                                                               |
| `highlights`      | Present with `highlight_query`. Each carries `text`, `start`, `end` and `score` |
| `links`, `images` | Present with `links: true`                                                      |
| `truncated`       | `true` if the page exceeded the extraction limit                                |
| `error`           | Present only when that URL could not be extracted                               |

## Partial failures

A URL that cannot be fetched or parsed does not fail the request. Its entry
carries an `error` instead of content, so always check before reading `text`:

```json theme={"dark"}
{
  "https://example.com/paywalled": { "error": "This page could not be fetched." }
}
```

<Warning>
  Requests to private, loopback, and link-local addresses are refused. Only
  publicly routable URLs can be extracted.
</Warning>

## Cost

Extraction is billed at **\$1.00 per 1,000 pages**, counted per URL successfully
extracted. A page that returns an `error` is not billed.

## Freshness

`POST /api/extract` **always fetches**. Call it when you need the page as it is
right now, and that is the reason to use it rather than a search.

Page text extracted **during a search** is different: it is held for up to seven
days and reused, so a result can carry body text older than the search that
returned it. For articles and documentation that is what you want, and ranking
is always computed fresh regardless.

A cached page costs the same as a fetched one. The cache makes a search faster,
not cheaper.
