> ## 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.

# Overview

> Parsing converts any document into clean, layout-aware, LLM-ready markdown and structured blocks.

**Parsing** converts documents into clean, structured, LLM-ready content. It turns PDFs, images, spreadsheets, presentations, and scanned files into layout-aware markdown, split into chunks, alongside a block-level breakdown (text, tables, figures, and key-value pairs) with spatial metadata. Use it as the foundation for RAG pipelines, custom ingestion workflows, downstream extraction, and agents.

## Quick start

We'll parse a sample bank statement. For this quick-start we've uploaded the file [here.](https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_example.pdf)

<img src="https://files.buildwithfern.com/extendconfig.docs.buildwithfern.com/6054ef645b2cc17010f5472ca513d6b4c02ad1c15ba0abc4dbe982408f517322/assets/images/quickstart/bank_statement_page_1.png" alt="Bank statement page 1" decoding="async" />

Grab a key from the [Developers](https://dashboard.extend.ai/developers) page and store it as the `EXTEND_API_KEY` environment variable. If you're using an SDK, see the [installation instructions](/sdks).

```bash
export EXTEND_API_KEY="your_api_key_here"
```

```python
from extend_ai import Extend

client = Extend()

response = client.parse(
    file={
        "url": "https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_example.pdf",
    }
)

print(response)
```

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

const client = new ExtendClient();

const response = await client.parse({
  file: {
    url: "https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_example.pdf",
  },
});

console.log(response);
```

```java
import ai.extend.ExtendClient;
import ai.extend.requests.ParseRequest;
import ai.extend.types.FileFromUrl;
import ai.extend.types.ParseRequestFile;
import ai.extend.types.ParseRun;

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

ParseRun response = client.parse(ParseRequest.builder()
    .file(ParseRequestFile.of(FileFromUrl.builder()
        .url("https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_example.pdf")
        .build()))
    .build());

System.out.println(response);
```

```go
package main

import (
	"context"
	"fmt"
	"log"

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

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

	response, err := c.Parse(context.TODO(), &extend.ParseRequest{
		File: &extend.ParseRequestFile{
			FileFromURL: &extend.FileFromURL{
				URL: "https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_example.pdf",
			},
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(response)
}
```

```bash
curl -X POST "https://api.extend.ai/parse" \
  -H "x-extend-api-version: 2026-02-09" \
  -H "Authorization: Bearer $EXTEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "file": {
      "url": "https://extend-public-files.s3.us-east-2.amazonaws.com/bank_statement_example.pdf"
    }
  }'
```

Want to parse your own document? [Upload it](/api-reference/endpoints/file/upload-file) first, then pass the returned file `id` instead of a `url`.

```python
with open("bank_statement.pdf", "rb") as f:
    uploaded = client.files.upload(file=f)

response = client.parse(file={"id": uploaded.id})
```

```typescript
import { createReadStream } from "fs";

const uploaded = await client.files.upload(createReadStream("bank_statement.pdf"), {});

const response = await client.parse({ file: { id: uploaded.id } });
```

```java
import ai.extend.requests.FilesUploadRequest;
import ai.extend.types.File;
import ai.extend.types.FileFromId;
import ai.extend.types.ParseRequestFile;

File uploaded = client.files().upload(
    new java.io.File("bank_statement.pdf"),
    FilesUploadRequest.builder().build());

ParseRun response = client.parse(ParseRequest.builder()
    .file(ParseRequestFile.of(FileFromId.builder().id(uploaded.getId()).build()))
    .build());
```

```go
f, err := os.Open("bank_statement.pdf")
if err != nil {
	log.Fatal(err)
}
defer f.Close()

uploaded, err := c.Files.Upload(context.TODO(), f, &extend.FilesUploadRequest{})
if err != nil {
	log.Fatal(err)
}

response, err := c.Parse(context.TODO(), &extend.ParseRequest{
	File: &extend.ParseRequestFile{
		FileFromID: &extend.FileFromID{ID: uploaded.ID},
	},
})
```

```bash
# Upload the file to get an id
curl -X POST https://api.extend.ai/files/upload \
  -H "Authorization: Bearer $EXTEND_API_KEY" \
  -H "x-extend-api-version: 2026-02-09" \
  -F "file=@bank_statement.pdf"

# Then parse using the returned id
curl -X POST https://api.extend.ai/parse \
  -H "Authorization: Bearer $EXTEND_API_KEY" \
  -H "x-extend-api-version: 2026-02-09" \
  -H "Content-Type: application/json" \
  -d '{ "file": { "id": "file_xK9mLPqRtN3vS8wF5hB2cQ" } }'
```

### Example response

After you run the code snippet above, you’ll see a response like this. This example response is truncated for brevity. The response is organized into output.chunks, which in this case are page-level units. Each chunk includes a formatted content string for the full page and a blocks array for block-level elements (like text, tables, and figures) with metadata and spatial data.

```json
{
  "object": "parse_run",
  "id": "pr_3f1j6I1gsw5k96xFiCnkM",
  "file": {
    "object": "file",
    "id": "file_GzKUy0VDhHscv7tweODYb",
    "name": "bank_statement.pdf"
  },
  "status": "PROCESSED",
  "output": {
    "chunks": [
      {
        "id": "chunk_qncr8Txe-wYvmFjipXgMD",
        "type": "page",
        "content": "CHASE JPMorgan Chase Bank, N.A. P O Box 659754...",
        "metadata": { "pageRange": { "start": 1, "end": 1 } },
        "blocks": [
          {
            "object": "block",
            "id": "block_WNoJ0WbMj4pRW9MpMpUox",
            "type": "text",
            "content": "CHASE JPMorgan Chase Bank, N.A. P O Box 659754 San Antonio, TX 78265 - 9754",
            "details": {},
            "metadata": { "page": { "number": 1, "width": 612, "height": 792 } },
            "polygon": [
              { "x": 56.873, "y": 35.374 },
              { "x": 162.173, "y": 35.215 },
              { "x": 162.245, "y": 81.158 },
              { "x": 56.938, "y": 81.317 }
            ],
            "boundingBox": { "left": 56.873, "top": 35.215, "right": 162.245, "bottom": 81.317 }
          }
        ]
      }
    ]
  },
  "metrics": { "pageCount": 7, "processingTimeMs": 8293 },
  "usage": { "credits": 14 }
}
```

### Key fields

| Field                     | What it contains                                                                |
| ------------------------- | ------------------------------------------------------------------------------- |
| `output.chunks`           | Parsed content units (page, section, or document-level based on config).        |
| `output.chunks[].content` | Formatted content string for the chunk.                                         |
| `output.chunks[].blocks`  | Block array with structured elements and their layout data.                     |
| `blocks[].type`           | What kind of element this is: `text`, `table`, `figure`, `key_value`, and more. |
| `blocks[].boundingBox`    | Coordinates showing where the element appears on the page.                      |

For full request/response details, see the [Create Parse Run API reference](/api-reference/endpoints/parse/create-parse-run).

### Use the output

You can pass each chunk's formatted `content` straight into an LLM, or walk individual `blocks` for more control over tables and layout.

```python
# Access the formatted content of each chunk
for index, chunk in enumerate(response.output.chunks):
    print(f"Page {index + 1}:", chunk.content)

# Or work with individual blocks for more control
for chunk in response.output.chunks:
    for block in chunk.blocks:
        print(f"{block.type}:", block.content)
```

```typescript
// Access the formatted content of each chunk
response.output.chunks.forEach((chunk, index) => {
  console.log(`Page ${index + 1}:`, chunk.content);
});

// Or work with individual blocks for more control
response.output.chunks.forEach((chunk) => {
  chunk.blocks.forEach((block) => {
    console.log(`${block.type}:`, block.content);
  });
});
```

```java
var chunks = response.getOutput().get().getChunks();

// Access the formatted content of each chunk
for (int i = 0; i < chunks.size(); i++) {
    System.out.println("Page " + (i + 1) + ": " + chunks.get(i).getContent());
}

// Or work with individual blocks for more control
for (var chunk : chunks) {
    for (var block : chunk.getBlocks()) {
        System.out.println(block.getType() + ": " + block.getContent());
    }
}
```

```go
// Access the formatted content of each chunk
for i, chunk := range response.Output.Chunks {
	fmt.Printf("Page %d: %s\n", i+1, chunk.Content)
}

// Or work with individual blocks for more control
for _, chunk := range response.Output.Chunks {
	for _, block := range chunk.Blocks {
		fmt.Printf("%s: %s\n", block.Type, block.Content)
	}
}
```

For a deeper guide on how to use the output of this endpoint, see [Response Format](/parsing/response-format).

## Sync vs async

The example above calls the synchronous `/parse` endpoint. We also have an asynchronous `/parse_runs` endpoint that should be used for large files and high volume use cases.

See [Async Processing](/general/async-processing) for the full comparison, polling options, and webhook setup.

## Configuration

The quick start uses default settings. To control how a document is parsed, pass a `config` object alongside `file`. Here are the most commonly changed options; for the full reference, see [Configuration](/parsing/configuration).

### Engine

Choose the parsing engine based on your accuracy and latency needs.

```json
{ "config": { "engine": "parse_performance" } }
```

| Engine              | When to use                                                                                                                                                                                                  |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `parse_performance` | Highest accuracy; best for strong checkbox support, complex tables, handwriting, and multilingual documents (default).                                                                                       |
| `parse_light`       | Faster, lower-cost parsing for high-volume ingestion. Handles layout well, but trades some accuracy on lower-quality scans, hard handwriting, large tables, non-Latin languages, and dense checkbox regions. |

### Chunking

By default, Parse returns one chunk per page. For RAG, `section` chunking splits at semantic boundaries so each chunk is a complete unit you can embed and retrieve independently.

```json
{
  "config": {
    "chunkingStrategy": {
      "type": "section",
      "options": { "minCharacters": 500, "maxCharacters": 2000 }
    }
  }
}
```

| Type       | Behavior                                                                                                             |
| ---------- | -------------------------------------------------------------------------------------------------------------------- |
| `page`     | One chunk per page (default).                                                                                        |
| `section`  | Splits at logical sections (headings); keeps tables and figures intact. Best for RAG. Requires `target: "markdown"`. |
| `document` | The entire document as a single chunk.                                                                               |

[Full chunking options →](/parsing/configuration#chunking-strategy)

### Table format

Controls how tables appear in each block's `content`.

```json
{ "config": { "blockOptions": { "tables": { "targetFormat": "markdown" } } } }
```

| Format     | When to use                                                   |
| ---------- | ------------------------------------------------------------- |
| `html`     | Complex tables with merged cells or nested headers (default). |
| `markdown` | Simple tables and Markdown-based or LLM workflows.            |

### Figures and charts

Figures are parsed by default. Disable them for the fastest parsing, or enable advanced chart extraction to convert charts into structured tables.

```json
{
  "config": {
    "blockOptions": {
      "figures": { "enabled": true, "advancedChartExtractionEnabled": true }
    }
  }
}
```

### Agentic OCR and tables

Agentic processing uses a vision model to review and correct parsing output. It's off by default and adds latency, so enable it only where it helps:

* **`text.agentic`** — corrects low-confidence OCR. Enable for handwriting, faded or skewed scans, or when you see garbled characters in the output.
* **`tables.agentic`** — reviews and fixes table structure. Enable for tables with misaligned columns, merged cells, or values landing in the wrong column.

Don't enable either for clean digital PDFs; they parse correctly without it and you'll just add latency.

```json
{
  "config": {
    "blockOptions": {
      "text": { "agentic": { "enabled": true } },
      "tables": { "agentic": { "enabled": true } }
    }
  }
}
```

### Page range

Process only specific pages. Page numbers are 1-based and inclusive, and you're only billed for pages actually processed.

```json
{ "config": { "advancedOptions": { "pageRanges": [{ "start": 1, "end": 10 }] } } }
```

For every option, including the full block options, Excel settings, and OCR output, see the [Configuration](/parsing/configuration) reference.

***

## Next steps

Customize chunking, output format, and block options

The full shape of chunks and blocks in the parse response.

Tune for speed or accuracy, plus ready-to-use recipes

Handle parse errors with sync and async error-handling patterns