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

# Parse

> Turn a document into Markdown and elements, with the options you choose.

Parse reads a document and returns its content as Markdown in reading order and as elements: the text blocks, tables and figures on each page, with their positions. Extract, Split, Tables and Charts then read the completed parse by its `parse_id`, so a document is parsed once however many steps read it.

```mermaid theme={null}
%%{init: {"themeVariables": {"fontSize": "14px"}, "flowchart": {"nodeSpacing": 24, "rankSpacing": 32, "padding": 8}}}%%
flowchart LR
    doc[Document] --> parse["POST /parse"]
    parse --> result["markdown, elements, extensions"]
    parse -->|parse_id| steps["Extract, Split, Tables, Charts"]
```

## Parse a document

This request parses a sample bank statement from a URL, waits for the parse to finish, and prints the start of its Markdown:

```python Python theme={null}
import os
from pulse import Pulse

client = Pulse(api_key=os.environ["PULSE_API_KEY"])

job = client.parse.run(
    file_url="https://platform.runpulse.com/api/examples/637e5678-30b1-45fa-acc4-877f2d636419/pdf",
    config={"model": "ultra-2"},
)
result = client.parse.get_result(job.parse_id)
print(result.markdown[:500])
```

```typescript TypeScript theme={null}
import { PulseClient } from "@pulse-software/sdk";

const client = new PulseClient({ apiKey: process.env.PULSE_API_KEY! });

const { parseId } = await client.parse.run({
  fileUrl: "https://platform.runpulse.com/api/examples/637e5678-30b1-45fa-acc4-877f2d636419/pdf",
  config: { model: "ultra-2" },
});
const result = await client.parse.getResult({ parseId });
console.log(result.markdown.slice(0, 500));
```

`model` is the only option you must send, and `ultra-2` is its only value. For a long document, send `async: true` and poll the job instead, as [Jobs and results](https://docs.runpulse.com/v2/jobs.md) shows. To upload a file, and for size limits, see [Files and limits](https://docs.runpulse.com/v2/files.md).

> [!NOTE]
>
> Steps work from the parse's result, not the document. Set the options a later step depends on, such as `page_range` or a `tables` refine pass, on the parse itself.

***

## Choose options

Every option sits in `config`; [Run Parse](https://docs.runpulse.com/api-reference/v2/parse/run-parse.md) lists them all. If a refine pass, a figure description, footnotes, metadata or chunks can't be finished, the parse still completes and its `warnings` say what was skipped.

This request parses the first 10 pages of an annual report with several options on:

```python Python theme={null}
job = client.parse.run(
    file_url="https://example.com/annual-report.pdf",
    config={
        "model": "ultra-2",
        "page_range": "1-10",
        "prompt": "Amounts are in thousands of US dollars.",
        "enhancements": {"refine": {"modes": [{"mode": "tables"}]}, "figures": {"describe": ["all"]}},
        "extensions": {"footnotes": True, "chunking": {"strategies": [{"strategy": "semantic"}]}},
    },
)
result = client.parse.get_result(job.parse_id)
```

```typescript TypeScript theme={null}
const { parseId } = await client.parse.run({
  fileUrl: "https://example.com/annual-report.pdf",
  config: {
    model: "ultra-2",
    pageRange: "1-10",
    prompt: "Amounts are in thousands of US dollars.",
    enhancements: { refine: { modes: [{ mode: "tables" }] }, figures: { describe: ["all"] } },
    extensions: { footnotes: true, chunking: { strategies: [{ strategy: "semantic" }] } },
  },
});
const result = await client.parse.getResult({ parseId });
```

`prompt` adds your instructions to Pulse's own, such as the units the document's numbers are in. It doesn't change the result's shape.

### Choose pages

`page_range` picks the pages to parse, such as `"1-3, 6, 8-end"`, counting from 1, with `end` for the last page. If you leave it out, every page is parsed. Only the pages you parse are billed. For a PDF, an image or a workbook, a range past the last page is refused with a `400` before any work starts.

The result numbers pages within your selection: with `"page_range": "5-7"`, the document's page 5 is `page: 1`. A later step's `page_range` uses these numbers, not the document's.

## Improve the parse

These options sit under `enhancements`.

### Correct tables, text and layout

`refine.modes` lists passes that check the parse against the page images and correct it. Each mode can appear once:

| Mode | What it corrects |
| - | - |
| `tables` | Each table's cells and structure. |
| `formatting` | Bold, italics, headings, lists and math, keeping the words. |
| `text` | Misread words and numbers, keeping the formatting. |
| `layout` | A figure read as a table, or a table read as a figure. |

Each mode except `layout` takes its own `prompt`; sending one with `layout` is refused. This config refines tables with an instruction and checks the layout:

```json theme={null}
{
  "model": "ultra-2",
  "enhancements": {
    "refine": {
      "modes": [
        { "mode": "tables", "prompt": "Keep footnote markers inside their cells." },
        { "mode": "layout" }
      ]
    }
  }
}
```

A page that a pass can't correct comes back as it was first parsed, and the result warns `REFINE_SKIPPED`.

### Describe figures

`figures.describe: ["all"]` adds a `description`, written by a model, to every `Figure` element, such as a chart, diagram or photo. `figures.include: true` returns each figure's image, base64-encoded, in its `figure_base64`.

A figure that can't be described comes back without a `description`, or with its caption or `Image` in place of one, and the result's `warnings` say so. For the numbers behind a chart, use [Charts](https://docs.runpulse.com/v2/charts.md).

### Detect checkboxes

`detect: ["selections"]` finds checkboxes, radio buttons and similar marks, and adds `selected: true` to each ticked one.

### Read spreadsheets

`spreadsheet` applies to workbooks: `.xlsx`, `.xlsm`, `.xls`, `.xlsb` and `.csv` files.

| Option | What it does |
| - | - |
| `include_hidden` | Reads hidden `rows`, `columns` or `sheets`, which are otherwise skipped and not billed. |
| `trim_empty` | Drops empty but formatted `rows` or `columns` past a large sheet's last value. It works on `.xlsx` and `.xlsm` files. |
| `raw_values: true` | Adds each cell's stored value as `raw_value`, such as `1201.67` where the sheet shows `$1,202`. |
| `cell_formatting: true` | Adds each cell's font, fill, borders and alignment as `style`. It isn't available for `.xls` files. |

Each sheet is a page, hidden sheets included, and a `page_range` of only hidden sheets is refused unless you also ask for hidden sheets. A workbook is billed by the filled cells the result shows: see [Pricing](https://docs.runpulse.com/v2/pricing.md).

![A sheet of yearly financial metrics for FY2021 to FY2025](https://mintcdn.com/pulseai/OJ897TEZf8bV6lk8/images/platform/spreadsheet-executive-summary.png?fit=max\&auto=format\&n=OJ897TEZf8bV6lk8\&q=85\&s=75a0d31ef05d6c7b1f652488187a2bdc)

The workbook's first sheet, Executive Summary.

![A sheet of quarterly interest income and expense lines](https://mintcdn.com/pulseai/OJ897TEZf8bV6lk8/images/platform/spreadsheet-quarterly-pl-compact.png?fit=max\&auto=format\&n=OJ897TEZf8bV6lk8\&q=85\&s=402bfad47014b524ab8ecf01c280a807)

Another sheet of the same workbook, with quarterly profit and loss lines.

![The Executive Summary sheet parsed into a table, with a tab for each sheet above it](https://mintcdn.com/pulseai/OJ897TEZf8bV6lk8/images/platform/spreadsheet-processing-config.png?fit=max\&auto=format\&n=OJ897TEZf8bV6lk8\&q=85\&s=a4f4ee66e9ed5b312a88bf1fb576984a)

The parsed workbook in the dashboard: one tab per sheet, each sheet's tables as Markdown.

This request trims a workbook's empty rows and columns, then prints each table cell's address, shown text, stored value and formula:

```python theme={null}
job = client.parse.run(
    file_url="https://example.com/acme-financial-review.xlsx",
    config={
        "model": "ultra-2",
        "enhancements": {"spreadsheet": {"trim_empty": ["rows", "columns"], "raw_values": True}},
    },
)

for element in client.parse.get_result(job.parse_id).elements:
    if element.category == "Table":
        for cell in element.cells:
            print(element.sheet, cell.range, cell.content, cell.raw_value, cell.formula)
```

In `.xlsx` and `.xlsm` workbooks, a cell also carries its `formula` and `comment` when it has them.

## Add outputs

These options sit under `extensions`, and each output comes back under its option's name in the result's `extensions`.

### Link footnotes

`footnotes: true` finds the document's footnotes and links each one to the elements whose text cites it.

![A research paper with its footnotes outlined, beside a list of the five footnotes found](https://mintcdn.com/pulseai/OJ897TEZf8bV6lk8/images/platform/footnote-references-input.png?fit=max\&auto=format\&n=OJ897TEZf8bV6lk8\&q=85\&s=4cf8f8f1f95703ea645c25a25e79315f)

A paper in the dashboard, with each footnote found and listed beside the page.

This request prints each footnote in that paper and the page and box of every element citing it:

```python theme={null}
job = client.parse.run(
    file_url="https://platform.runpulse.com/api/examples/3be15d23-d622-4f27-9843-ec2929140eec/pdf",
    config={"model": "ultra-2", "extensions": {"footnotes": True}},
)
result = client.parse.get_result(job.parse_id)
elements = {e.id: e for e in result.elements}
elements |= {c.id: c for e in result.elements if e.category == "Table" for c in e.cells}

for note in result.extensions.footnotes:
    print(f"[{note.symbol}] {note.text}")
    for ref_id in note.reference_element_ids or []:
        if ref_id in elements:
            print("  cited on page", elements[ref_id].page, elements[ref_id].bbox)
```

Each footnote has its `symbol` and `text`, the `footnote_element_id` of the element holding it, and the `reference_element_ids` of the elements citing it. Each id is an element's `id`, or a table cell's `id` when the note or the text citing it is in a table.

![The list of footnotes, two of them expanded to show the in-text references and their pages](https://mintcdn.com/pulseai/OJ897TEZf8bV6lk8/images/platform/footnote-references-output.png?fit=max\&auto=format\&n=OJ897TEZf8bV6lk8\&q=85\&s=e56ef8fbc2cdb22ede14ff3d04afc968)

Each footnote, opened to show the passages that cite it.

### Chunk text for retrieval

`chunking` splits the text into chunks. The `semantic` strategy cuts where the meaning shifts, so each chunk holds one idea. `max_characters`, from 20 to 100,000, cuts any longer chunk again. If you leave it out, chunks have no length cap. This request chunks a paper into pieces of at most 2,000 characters:

```python theme={null}
job = client.parse.run(
    file_url="https://platform.runpulse.com/api/examples/3be15d23-d622-4f27-9843-ec2929140eec/pdf",
    config={
        "model": "ultra-2",
        "extensions": {"chunking": {"strategies": [{"strategy": "semantic", "max_characters": 2000}]}},
    },
)

for chunk in client.parse.get_result(job.parse_id).extensions.chunking.semantic:
    print(chunk.id, chunk.pages, len(chunk.content))
```

If chunking fails, the list is empty and the result warns `CHUNKS_SKIPPED`. [RAG ingestion](https://docs.runpulse.com/v2/cookbooks/rag-ingestion.md) shows how to choose `max_characters` and store chunks.

### Get word-level boxes

`word_level_bounding_boxes: true` returns every word as a `Word` element with its own `bbox`, and makes results much larger. Your organization must have word boxes enabled; otherwise the request is refused with a `400`. [Elements and bounding boxes](https://docs.runpulse.com/v2/elements.md#word-boxes) shows how to draw them.

### Get other formats

`additional_formats` returns the document as `html` (the Markdown as HTML), `xml` (the elements, grouped by page) or `tagged_md` (the Markdown with each element's `id` written in, to map text back to [elements](https://docs.runpulse.com/v2/elements.md)).

### Read the file's metadata

`document_metadata: true` returns what the file stores about itself, such as its title, authors and creation date. Metadata is what the file claims, not checked fact, and it can name people, so treat it as sensitive. If part of it can't be read, the result warns `METADATA_INCOMPLETE`.

## Keep or discard the result

By default, a parse is saved: its result is kept with no expiry and appears in your organization's dashboard. `storage.folder_id` saves it into one of your dashboard folders; copy a folder's ID from its menu in the dashboard.

To keep a result only briefly, send `"storage": {"save": false}`. The result is then kept for 24 hours from when Pulse accepts the request, until its `expires_at`. You can't combine `save: false` with a `folder_id`, and free plans can't send `save: false`. Your organization's settings take precedence: see [Retention](https://docs.runpulse.com/v2/jobs.md#retention).

> [!WARNING]
>
> A step can't read an expired or deleted parse, so run every step on an unsaved parse before its `expires_at`. If your organization deletes each result on its first read, let every step finish, or use a [pipeline](https://docs.runpulse.com/v2/pipelines.md), before you read the parse itself.

***

## The result

`GET /parse/{parse_id}/result` (`client.parse.get_result`) returns a completed parse's result:

| Field | What it holds |
| - | - |
| `markdown` | The document in reading order, with tables as HTML. |
| `elements` | The text blocks, tables and figures in reading order. See [Elements and bounding boxes](https://docs.runpulse.com/v2/elements.md). |
| `extensions` | The extra outputs you asked for. |
| `warnings` | Issues that didn't stop the parse but may affect the result. |
| `usage` | The pages and credits this parse used, and your organization's totals. |
| `expires_at` | When the result will be deleted. Absent when the result is kept with no expiry. |

A saved result for the sample bank statement looks like this, shortened:

```json theme={null}
{
  "parse_id": "parse-3f6c1a2e-8d4b-4f7a-9c2e-5b1d0e7a4c93",
  "markdown": "# Statement of Account\n\n12345678\n\nJAMES C. MORRISON\n1765 SHERIDAN DRIVE ...",
  "elements": [
    {
      "id": "Title-0",
      "category": "Title",
      "content": "Statement of Account",
      "confidence": 0.99,
      "page": 1,
      "bbox": [0.2196, 0.1225, 0.4578, 0.1348, 0.4557, 0.1537, 0.2174, 0.1417]
    },
    {
      "id": "Text-1",
      "category": "Text",
      "content": "12345678",
      "confidence": 0.98,
      "page": 1,
      "bbox": [0.0841, 0.0602, 0.1912, 0.0602, 0.1912, 0.0741, 0.0841, 0.0741]
    }
  ],
  "usage": {
    "request": { "pages_processed": 2, "credits_used": 2.0 },
    "org": { "pages_processed": 1250, "credits_used": 1310.5 }
  },
  "history": { "file_name": "statement.pdf" }
}
```

A long document can complete with some pages missing. `failed_pages` then lists them, each is marked in `markdown` with an HTML comment, and the result warns `PAGES_FAILED`. Treat a missing or empty `failed_pages` as every page parsed. Pages that failed aren't billed.

## Next steps

> [card] [Elements and bounding boxes](https://docs.runpulse.com/v2/elements.md)
>
> Map Markdown and citations to elements.

> [card] [Extract](https://docs.runpulse.com/v2/extract.md)
>
> Fill a JSON Schema with values from a parse.

> [card] [Saved configs](https://docs.runpulse.com/v2/saved-configs.md)
>
> Reuse a parse config by its `config_id`.

> [card] [Parse API reference](https://docs.runpulse.com/api-reference/v2/parse/run-parse.md)
>
> Every option, field and limit.


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