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

# PDF index for IA agents

> /pdf/index + pages= usage pattern so your AI agent pays 20-30x fewer tokens on large PDFs.

When an AI agent needs to respond to a 500-page PDF, the natural thing to do would be to convert the entire thing to Markdown and put it in the prompt. **This is expensive**: 250,000 tokens per LLM, seconds to mine, and most of the content is unused.

The correct pattern is **index first, then extract only what is necessary**.

## Advantages

<Tip>
  * **Tokens at LLM**: 20-30x less. AI client pays less.
  * **Latency**: spine in 300-700ms even for 500MB PDFs.
  * **Backend cost**: extract only the requested pages, not the 5000. **Your bill is reduced proportionally**.
  * **No server cache**: the spine travels to the client, it is not saved. Respect privacy.
  * **Stateless**: the agent decides. No session or job required.
</Tip>

## When NOT to use

<Warning>
  * **Small PDFs** (\<10 pages): the fixed cost of the spine is not worth it. Use direct `/convert/from-url`.
  * **You need the complete Markdown**: skip the index and ask for `/convert/from-url` without `pages=`.
  * **Agent that cannot make routing decisions**: needs logic to map question -> sections.
</Warning>

## How it works internally

1. The backend downloads the PDF (1 time, no cache).
2. Makes **sample of 32 distributed pages** (start, middle, end) to build the font model.
3. Go through all the pages reading **only the head** (first 8 spans) to detect headings.
4. Detects repeated headers/footers in the top/bottom 12% of each page.
5. Returns JSON \~5-15KB with the entire map.
6. Delete the temporary and finish.

**Constant cost with respect to the size of the PDF**: a PDF with 50 pages and one with 5000 pages take almost the same time to be indexed.

## Usage pattern for AI agents

### Pattern 1 - search by section

```python theme={null}
# Agent asks: "What was the revenue in Q3?"
spine = index_pdf(url)

# Search relevant section by title
target_section = next(
    s for s in spine["sections"]
    if "Revenue" in s["text"] or "Financial" in s["text"]
)
next_section = next(
    (s for s in spine["sections"] if s["page"] > target_section["page"]),
    None
)
end_page = (next_section["page"] - 1) if next_section else spine["page_count"]

# Order SOLO that section
markdown = convert(url, pages=f"{target_section['page']}-{end_page}")
# ~5-15K tokens instead of 250K
```

### Pattern 2 - search by density

```python theme={null}
# Agent asks: "Are there any graphs or tables in this document?"
spine = index_pdf(url)

# Pages with very few chars are probably visual
visual_pages = [p["page"] for p in spine["pages"] if p["chars"] < 200]
if visual_pages:
    markdown = convert(url, pages=",".join(map(str, visual_pages[:5])))
```

### Pattern 3 - user guided navigation

```python theme={null}
# Show the user an interactive TOC before downloading
spine = index_pdf(url)
toc = [
    {"page": s["page"], "title": s["text"], "level": s["level"]}
    for s in spine["sections"]
]
# User clicks on section -> request to /convert/from-url with that page
```

## Actual comparison

PDF of 500 pages (academic paper, 10MB decompressed in Markdown):

| Operation                            | Without index |                                  With index |
| ------------------------------------ | ------------: | ------------------------------------------: |
| Tokens to LLM (GPT-4o input)         |       250,000 |             **5K spine + 8K section = 13K** |
| AI client cost (\$5/1M input tokens) |        \$1.25 |                                  **\$0.07** |
| Backend extraction time              |            5s | 0.4 s (spine) + 0.2 s (section) = **0.6 s** |
| Billed Lambda GB-s                   |          1.0x |                                   **0.12x** |
| Egress bytes from backend            |          10MB |     15 KB spine + 20 KB section = **35 KB** |

**AI Client pays 18x less. Your backend bills 8x less compute.** Both win.

## Honest limitations

* **Heuristic detection**. Sections are detected by font size + numbering + bold. It's not real semantics. It works very well with structured documents, but worse with free-form PDFs.
* **Tables not detected v1**. Only headings + body chars + repeated headers/footers. For tables use `mode=balanced`.
* **`pages[]` truncated to 200**. PDFs >200 pages expose `pages_truncated: true`; the agent must navigate to `sections[]`.
* **No server cache**: each call re-downloads the PDF. If you are going to make many, keep `content_hash` on the client to reuse it via `cache_key=` in `/convert/from-url`.

## Compatibility with other endpoints

* The spine tells you **what to ask for**. The actual extraction always goes through **`/convert/from-url`** (or any other conversion endpoint) with `pages=`.
* `mode=ultra_fast` + `pages=` = faster combination.

See also: [POST /pdf/index](/docs/api/pdf-index), [POST /convert/from-url](/docs/api/convert-from-url), [Modes](/docs/concepts/modes), and [Formats](/docs/concepts/formats).
