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

# Parse a document

> Convert a PDF or image into Markdown plus structured, reading-order content blocks.

Upload a document and receive it as a single Markdown string plus a flat list of content blocks in reading order. Accepts a single PDF or image (`.pdf`, `.png`, `.jpg`, `.jpeg`). PDFs are parsed page by page; an image is treated as a single page.

## Authentication

<ParamField header="Authorization" type="string" required>
  Your API key as a bearer token: `Bearer sk-pr-...`.
</ParamField>

## Request

Send the document as `multipart/form-data`.

<ParamField body="file" type="file" required>
  The document to parse. Must be a PDF or image (`.pdf`, `.png`, `.jpg`, `.jpeg`), up to **50 pages**.
</ParamField>

<ParamField body="model" default="mineru2.5-pro-2605" type="string">
  The parsing model to use. `mineru2.5-pro-2605` is currently the only option and is used by default if omitted.

  Models are versioned and never change behavior once released. For production, we recommend pinning this explicitly so a future default model never alters your results without you opting in.
</ParamField>

### Coming soon

These options are planned but **not yet available** – the endpoint doesn't accept them today, and the features they control are currently off. The response fields they populate (`text_level`, `img_path`, `content`) are still returned on every block, but are always `null` for now.

<ParamField body="title_levels" type="boolean">
  **Coming soon.** Detect heading levels. When available, title blocks will carry a `text_level` (1–4) and headings will render with matching `#` depth in `markdown`. Today, titles are returned flat.
</ParamField>

<ParamField body="extract_images" type="boolean">
  **Coming soon.** Crop images, tables, and charts to hosted storage and return their URLs as `img_path` on the relevant blocks (and as image links in `markdown`). Today, those blocks carry no URL.
</ParamField>

<ParamField body="image_analysis" type="boolean">
  **Coming soon.** Generate a short text description of each image and chart block, returned as `content`. Today, no description is generated.
</ParamField>

## Response

<ResponseField name="markdown" type="string">
  The whole document rendered as a single Markdown string – headings, with tables and equations inline. (Per-level heading depth and hosted image links are coming soon – see `title_levels` and `extract_images`.)
</ResponseField>

<ResponseField name="page_count" type="integer">
  The number of pages parsed. `1` for an image.
</ResponseField>

<ResponseField name="content_list" type="object[]">
  A flat list of content blocks in reading order across the whole document (this mirrors MinerU's `content_list`). Every block carries the full set of fields below; which ones are populated depends on `type` (the rest are `null`). Additional fields (e.g. captions and footnotes) may also appear and are passed through as-is.

  <Expandable title="block">
    <ResponseField name="type" type="string">
      The block kind. Common values: `text` (body text – and titles; per-title `text_level` is coming soon), `list`, `equation`, `image`, `chart`, `table`, `code`, `header`, `footer`, `page_number`, `aside_text`, `page_footnote`, `ref_text`.
    </ResponseField>

    <ResponseField name="page_idx" type="integer">
      The 0-based index of the page the block belongs to.
    </ResponseField>

    <ResponseField name="bbox" type="integer[] | null">
      The block's bounding box as `[x_min, y_min, x_max, y_max]`, scaled to `0–1000` of the page width and height, with the origin at the top-left. `null` when not available.
    </ResponseField>

    <ResponseField name="text" type="string | null">
      The block's text. Present for text-like blocks (`text`, titles, `header`, `footer`, etc.) and, for `equation`, the LaTeX source. `null` otherwise.
    </ResponseField>

    <ResponseField name="text_level" type="integer | null">
      Heading level `1`–`4` on title blocks, driving the `#`-depth of the heading in `markdown`. Always present in every block; `null` for non-titles – and `null` everywhere for now, as per-heading levels are **coming soon**.
    </ResponseField>

    <ResponseField name="text_format" type="string | null">
      The format of `text` when applicable, e.g. `latex` for an `equation`. `null` otherwise.
    </ResponseField>

    <ResponseField name="img_path" type="string | null">
      Public URL of the cropped asset for `image`, `chart`, and `table` blocks. Always present; `null` when no asset is hosted – and `null` everywhere for now, as hosted image extraction is **coming soon**.
    </ResponseField>

    <ResponseField name="content" type="string | null">
      A generated description of an `image` or `chart` block. Always present; `null` unless a description was produced – and `null` everywhere for now, as image analysis is **coming soon**.
    </ResponseField>

    <ResponseField name="table_body" type="string | null">
      The table rendered as HTML. Present for `table` blocks; `null` otherwise.
    </ResponseField>

    <ResponseField name="list_items" type="string[] | null">
      The items of a `list` block, in order. `null` otherwise.
    </ResponseField>

    <ResponseField name="code_body" type="string | null">
      The source of a `code` block. `null` otherwise.
    </ResponseField>

    <ResponseField name="sub_type" type="string | null">
      A finer-grained subtype for blocks that have one (e.g. `list`, `image`/`chart`, `code`). `null` otherwise.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.parserouter.com/v1/mineru/parse \
    -H "Authorization: Bearer sk-pr-..." \
    -F "file=@invoice.pdf" \
    -F "model=mineru2.5-pro-2605"
  ```

  ```python Python theme={null}
  import requests

  resp = requests.post(
      "https://api.parserouter.com/v1/mineru/parse",
      headers={"Authorization": "Bearer sk-pr-..."},
      files={"file": open("invoice.pdf", "rb")},
      data={
          "model": "mineru2.5-pro-2605",
      },
      timeout=600,
  )
  resp.raise_for_status()
  data = resp.json()

  markdown = data["markdown"]
  for block in data["content_list"]:
      print(block["type"], block.get("text", ""))
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "markdown": "# Invoice #2041\n\n<table><tr><td>Item</td><td>Qty</td><td>Price</td></tr><tr><td>API credits</td><td>10,000</td><td>$99.00</td></tr></table>",
    "page_count": 1,
    "content_list": [
      {
        "type": "text",
        "page_idx": 0,
        "bbox": [80, 60, 420, 110],
        "text": "Invoice #2041",
        "text_level": null,
        "text_format": null,
        "img_path": null,
        "content": null,
        "sub_type": null,
        "list_items": null,
        "table_body": null,
        "code_body": null
      },
      {
        "type": "table",
        "page_idx": 0,
        "bbox": [80, 200, 920, 480],
        "text": null,
        "text_level": null,
        "text_format": null,
        "img_path": null,
        "content": null,
        "sub_type": null,
        "list_items": null,
        "table_body": "<table><tr><td>Item</td><td>Qty</td><td>Price</td></tr><tr><td>API credits</td><td>10,000</td><td>$99.00</td></tr></table>",
        "code_body": null
      }
    ]
  }
  ```
</ResponseExample>

## Errors

ParseRouter uses standard HTTP status codes. Error responses have the shape `{ "detail": "..." }`.

| Status | Meaning                                        | What to do                                                        |
| ------ | ---------------------------------------------- | ----------------------------------------------------------------- |
| `400`  | The file is not a valid PDF or image.          | Check the file is one of the supported formats and not corrupted. |
| `401`  | Missing or invalid API key.                    | Send a valid `Authorization: Bearer sk-pr-...` header.            |
| `402`  | Insufficient credits.                          | Top up your balance on the Billing page.                          |
| `413`  | The document exceeds 50 pages.                 | Split the document into smaller files.                            |
| `429`  | Rate limit exceeded.                           | Back off and retry after the `Retry-After` header (seconds).      |
| `503`  | The parsing backend is temporarily overloaded. | Retry after the `Retry-After` header. No charge is made.          |

<Note>
  You're only charged for successful parses (`200`). Failed requests – including `400`, `413`, and `503` – never consume credits.
</Note>
