> This page is for version v2026-02-09 (default).
> For other versions, use one of these documentation indexes:
> - v2026-02-09 (default): https://docs.extend.ai/2026-02-09/llms.txt
> - v2025-04-21: https://docs.extend.ai/2025-04-21/llms.txt
> - v2024-12-23: https://docs.extend.ai/2024-12-23/llms.txt

> ## Documentation Index
> Fetch the complete documentation index at: https://docs.extend.ai/llms.txt
> Use this file to discover all available pages before exploring further.
>
> ## API version
> The current API version is `2026-02-09`, served at the site root (no version prefix in URLs).
> If this page URL contains `/2025-04-21/` or `/2024-12-23/`, you are reading an older API version.
> Prefer the current docs at https://docs.extend.ai/llms.txt unless the user explicitly needs that older version.
> Do not treat older-version pages as the source of truth for new integrations.

# Parsing for RAG

> Use Extend Parse as the ingestion layer for RAG, search, and agent memory: layout-aware markdown, semantic chunks with reading order, bounding boxes for citations, and figures, charts, and equations rendered as text.

Extend Parse is a document-to-markdown API built for retrieval-augmented generation (RAG). It turns PDFs, scans, images, spreadsheets, presentations, and email into clean, layout-aware markdown that is already split into semantic chunks, each carrying the page number and bounding box you need to cite the source. You embed the chunks with the model and vector store of your choice.

This page is the end-to-end recipe. For field-level options, see [Configuration](/2026-02-09/parsing/configuration); for the response shape, see [Response Format](/2026-02-09/parsing/response-format).

## What Parse gives a retrieval pipeline

| Need                                      | What Parse returns                                                                                                                                                                                   |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Markdown for the LLM**                  | `output.chunks[].content` — layout-aware markdown in reading order, with headings, lists, tables, and figure summaries inline.                                                                       |
| **Chunks ready to embed**                 | `chunkingStrategy.type: "section"` splits at semantic boundaries (headings, tables, figures) and never breaks a markdown element across chunks. `page` and `document` strategies are also available. |
| **Citations**                             | Every block has `metadata.page.number`, a `boundingBox`, and a `polygon`, so a retrieved chunk can be highlighted on the original page.                                                              |
| **Tables that survive retrieval**         | Tables are emitted as HTML or markdown; nested and merged cells are preserved in HTML mode, and agentic table correction can repair misaligned structure.                                            |
| **Figures, charts, and diagrams as text** | Figures are summarized by a vision model and classified (`chart`, `diagram`, `image`, `logo`). Advanced chart extraction converts charts into data tables so the values are searchable.              |
| **Equations as LaTeX**                    | `formula` blocks return a `latex` representation for mathematical content.                                                                                                                           |
| **Scale**                                 | Async `/parse_runs` and batch processing for large document sets; Light, Auto, and Performance engines to trade accuracy against cost per page.                                                      |

## Recommended configuration

```json
{
  "config": {
    "engine": "parse_auto",
    "target": "markdown",
    "chunkingStrategy": {
      "type": "section",
      "options": { "minCharacters": 500, "maxCharacters": 2000 }
    },
    "blockOptions": {
      "tables": { "targetFormat": "markdown" },
      "figures": { "enabled": true, "advancedChartExtractionEnabled": true },
      "formulas": { "enabled": true }
    }
  }
}
```

* **`parse_auto`** routes simple pages through the Light pipeline and complex pages through Performance, which keeps cost low on mixed document sets. Use `parse_light` for born-digital text at the lowest cost, or `parse_performance` when scans, handwriting, or dense tables dominate.
* **`section` chunking** requires `target: "markdown"`. Tune `minCharacters` and `maxCharacters` to your embedding model's ideal input size.
* **Markdown tables** embed well for most retrieval use cases. Switch to `html` when tables have merged cells or nested headers that markdown cannot express.
* **Advanced chart extraction** requires `parse_performance` v2.0.0 (or Auto routing a page to Performance) and adds latency proportional to the number of charts.

## Ingest: parse, embed, store

Each snippet parses one file with the recommended configuration and builds one record per chunk: the markdown to embed plus the file ID, page numbers, and block IDs you will need to cite the source.

#### Python

```python
from extend_ai import Extend

client = Extend()

response = client.parse(
    file={"url": "https://example.com/quarterly-report.pdf"},
    config={
        "engine": "parse_auto",
        "target": "markdown",
        "chunkingStrategy": {
            "type": "section",
            "options": {"minCharacters": 500, "maxCharacters": 2000},
        },
        "blockOptions": {
            "tables": {"targetFormat": "markdown"},
            "figures": {"enabled": True, "advancedChartExtractionEnabled": True},
            "formulas": {"enabled": True},
        },
    },
)

records = []
for index, chunk in enumerate(response.output.chunks):
    # PDFs and images carry page metadata; spreadsheet blocks carry sheet metadata instead
    pages = sorted({block.metadata.page.number for block in chunk.blocks if block.metadata.page})
    sheets = sorted({block.metadata.sheet.name for block in chunk.blocks if block.metadata.sheet})
    records.append(
        {
            "id": f"{response.file.id}:{index}",
            "text": chunk.content,
            "metadata": {
                "file_id": response.file.id,
                "pages": pages,
                "sheets": sheets,
                "block_ids": [block.id for block in chunk.blocks],
            },
        }
    )

# Embed `text` with your model and upsert `records` into your vector store.
```

#### TypeScript

```typescript
import { ExtendClient } from "extend-ai";

const client = new ExtendClient();

const response = await client.parse({
  file: { url: "https://example.com/quarterly-report.pdf" },
  config: {
    engine: "parse_auto",
    target: "markdown",
    chunkingStrategy: {
      type: "section",
      options: { minCharacters: 500, maxCharacters: 2000 },
    },
    blockOptions: {
      tables: { targetFormat: "markdown" },
      figures: { enabled: true, advancedChartExtractionEnabled: true },
      formulas: { enabled: true },
    },
  },
});

const records = response.output.chunks.map((chunk, index) => ({
  id: `${response.file.id}:${index}`,
  text: chunk.content,
  metadata: {
    fileId: response.file.id,
    // PDFs and images carry page metadata; spreadsheet blocks carry sheet metadata instead
    pages: [...new Set(chunk.blocks.flatMap((block) => (block.metadata.page ? [block.metadata.page.number] : [])))],
    sheets: [...new Set(chunk.blocks.flatMap((block) => (block.metadata.sheet ? [block.metadata.sheet.name] : [])))],
    blockIds: chunk.blocks.map((block) => block.id),
  },
}));

// Embed `text` with your model and upsert `records` into your vector store.
```

#### Java

```java
import ai.extend.ExtendClient;
import ai.extend.requests.ParseRequest;
import ai.extend.types.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

ExtendClient client = ExtendClient.builder().build();

ParseRun response = client.parse(ParseRequest.builder()
    .file(ParseRequestFile.of(FileFromUrl.builder()
        .url("https://example.com/quarterly-report.pdf")
        .build()))
    .config(ParseConfig.builder()
        .engine(ParseConfigEngine.valueOf("parse_auto"))
        .target(ParseConfigTarget.MARKDOWN)
        .chunkingStrategy(ParseConfigChunkingStrategy.builder()
            .type(ParseConfigChunkingStrategyType.SECTION)
            .options(ParseConfigChunkingStrategyOptions.builder()
                .minCharacters(500)
                .maxCharacters(2000)
                .build())
            .build())
        .blockOptions(ParseConfigBlockOptions.builder()
            .tables(ParseConfigBlockOptionsTables.builder()
                .targetFormat(ParseConfigBlockOptionsTablesTargetFormat.MARKDOWN)
                .build())
            .figures(ParseConfigBlockOptionsFigures.builder()
                .enabled(true)
                .advancedChartExtractionEnabled(true)
                .build())
            .formulas(ParseConfigBlockOptionsFormulas.builder()
                .enabled(true)
                .build())
            .build())
        .build())
    .build());

String fileId = response.getFile().getId();
List<Chunk> chunks = response.getOutput().get().getChunks();
List<Map<String, Object>> records = new ArrayList<>();

for (int index = 0; index < chunks.size(); index++) {
    Chunk chunk = chunks.get(index);
    // PDFs and images carry page metadata; spreadsheet blocks carry sheet metadata instead
    TreeSet<Integer> pages = new TreeSet<>();
    TreeSet<String> sheets = new TreeSet<>();
    List<String> blockIds = new ArrayList<>();
    for (Block block : chunk.getBlocks()) {
        block.getMetadata().getPage().ifPresent(page -> pages.add(page.getNumber()));
        block.getMetadata().getSheet().ifPresent(sheet -> sheets.add(sheet.getName()));
        blockIds.add(block.getId());
    }
    records.add(Map.of(
        "id", fileId + ":" + index,
        "text", chunk.getContent(),
        "metadata", Map.of("fileId", fileId, "pages", pages, "sheets", sheets, "blockIds", blockIds)));
}

// Embed each record's text with your model and upsert `records` into your vector store.
```

#### Go

```go
package main

import (
	"context"
	"fmt"
	"log"
	"sort"

	extend "github.com/extend-hq/extend-go-sdk"
	client "github.com/extend-hq/extend-go-sdk/client"
)

type record struct {
	ID       string
	Text     string
	FileID   string
	Pages    []int
	Sheets   []string
	BlockIDs []string
}

func main() {
	c := client.NewClient()

	response, err := c.Parse(context.TODO(), &extend.ParseRequest{
		File: &extend.ParseRequestFile{
			FileFromURL: &extend.FileFromURL{URL: "https://example.com/quarterly-report.pdf"},
		},
		Config: &extend.ParseConfig{
			Engine: extend.ParseConfigEngine("parse_auto").Ptr(),
			Target: extend.ParseConfigTargetMarkdown.Ptr(),
			ChunkingStrategy: &extend.ParseConfigChunkingStrategy{
				Type: extend.ParseConfigChunkingStrategyTypeSection.Ptr(),
				Options: &extend.ParseConfigChunkingStrategyOptions{
					MinCharacters: extend.Int(500),
					MaxCharacters: extend.Int(2000),
				},
			},
			BlockOptions: &extend.ParseConfigBlockOptions{
				Tables: &extend.ParseConfigBlockOptionsTables{
					TargetFormat: extend.ParseConfigBlockOptionsTablesTargetFormatMarkdown.Ptr(),
				},
				Figures: &extend.ParseConfigBlockOptionsFigures{
					Enabled:                        extend.Bool(true),
					AdvancedChartExtractionEnabled: extend.Bool(true),
				},
				Formulas: &extend.ParseConfigBlockOptionsFormulas{Enabled: extend.Bool(true)},
			},
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	fileID := response.File.ID
	var records []record
	for index, chunk := range response.Output.Chunks {
		// PDFs and images carry page metadata; spreadsheet blocks carry sheet metadata instead
		pageSet := map[int]struct{}{}
		sheetSet := map[string]struct{}{}
		var blockIDs []string
		for _, block := range chunk.Blocks {
			if block.Metadata != nil && block.Metadata.Page != nil {
				pageSet[block.Metadata.Page.Number] = struct{}{}
			}
			if block.Metadata != nil && block.Metadata.Sheet != nil {
				sheetSet[block.Metadata.Sheet.Name] = struct{}{}
			}
			blockIDs = append(blockIDs, block.ID)
		}
		pages := make([]int, 0, len(pageSet))
		for page := range pageSet {
			pages = append(pages, page)
		}
		sort.Ints(pages)
		sheets := make([]string, 0, len(sheetSet))
		for sheet := range sheetSet {
			sheets = append(sheets, sheet)
		}
		sort.Strings(sheets)

		records = append(records, record{
			ID:       fmt.Sprintf("%s:%d", fileID, index),
			Text:     chunk.Content,
			FileID:   fileID,
			Pages:    pages,
			Sheets:   sheets,
			BlockIDs: blockIDs,
		})
	}

	// Embed each record's Text with your model and upsert `records` into your vector store.
}
```

Store the block IDs, page numbers, and bounding boxes alongside each embedding. At answer time, they let you cite the exact region of the source document rather than only the file. The `engine` value `parse_auto` is passed as a string in the Java and Go snippets because those SDKs' typed enum constants predate the Auto engine; both accept unknown enum values.

## Retrieve and cite

When a chunk is retrieved, its blocks carry everything needed for a source-grounded answer:

```json
{
  "id": "chunk_qncr8Txe-wYvmFjipXgMD",
  "content": "## Revenue\n\n| Quarter | Revenue |\n| --- | --- |\n| Q1 | $4.2M |\n| Q2 | $4.9M |",
  "blocks": [
    {
      "id": "block_WNoJ0WbMj4pRW9MpMpUox",
      "type": "table",
      "metadata": { "page": { "number": 7, "width": 612, "height": 792 } },
      "boundingBox": { "left": 61.2, "top": 158.4, "right": 550.8, "bottom": 356.4 }
    }
  ]
}
```

Pass `content` to the LLM as context, and render `boundingBox` on `page.number` as the citation highlight. See [Bounding box coordinates](/2026-02-09/parsing/response-format#bounding-box-coordinates) for reconciling coordinates with your renderer.

## Scale to large document sets

* Use the asynchronous [`/parse_runs`](/2026-02-09/general/async-processing) endpoint and webhooks for anything larger than a handful of pages, and [batch processing](/2026-02-09/general/batch-processing) for backfills.
* Bill only for what you need: Light Parse is 0.5 credits per page and Performance is 2 credits per page; see [How Credits Work](/2026-02-09/general/how-credits-work) for the per-engine rates and worked monthly totals.
* Re-parse only when the source changes. Block IDs are derived from content, so unchanged blocks keep stable IDs across runs.

## When to add extraction

Parse alone answers "what does this document say." When you also need "what are the fields," run [Extract](/2026-02-09/extraction/overview) against a schema on the same file. Extraction reuses the parse, returns [citations with bounding boxes](/2026-02-09/extraction/response-format#citations) and confidence scores per field, and can feed a [human review](/2026-02-09/workflows/human-review) step for accuracy-critical records.

## Next steps

#### [Chunking strategy](/2026-02-09/parsing/configuration#chunking-strategy)

Page, section, and document chunking options.

#### [Best Practices](/2026-02-09/parsing/best-practices)

Retrieval quality, speed, and cost recipes.

#### [Response Format](/2026-02-09/parsing/response-format)

Chunks, blocks, block types, and coordinates.

#### [Batch Processing](/2026-02-09/general/batch-processing)

Parse large document sets asynchronously.