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

# Parse a document

> Turn a file into markdown, text, blocks, and optional chunks

Parse converts a document into structured text. OCR runs when the file needs it;
`ocr_applied` on the result says whether it did.

<Tabs>
  <Tab title="Python">
    ```python theme={"dark"}
    from ndi_sdk import NdiClient

    with NdiClient() as client:
        upload = client.documents.create_upload("report.pdf")
        job = client.jobs.wait(
            client.documents.parse(upload, wait_seconds=30).job_id,
            timeout=600,
        )
        print(job.result.markdown)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts theme={"dark"}
    import { NdiClient } from "ndi-sdk";

    const client = new NdiClient();
    const upload = await client.documents.createUpload("report.pdf");
    const job = await client.jobs.wait(
      (await client.documents.parse(upload, { wait_seconds: 30 })).job_id,
      { timeout: 600 },
    );
    console.log(job.result && "markdown" in job.result ? job.result.markdown : job.result);
    ```
  </Tab>

  <Tab title="curl">
    ```bash theme={"dark"}
    UPLOAD_ID=$(curl -s -X POST "$NDI_BASE_URL/v1/uploads" \
      -H "X-API-Key: $NDI_API_KEY" \
      -F "file=@report.pdf" | jq -r '.upload_id')

    curl -s -X POST "$NDI_BASE_URL/v1/parse?wait_seconds=60" \
      -H "X-API-Key: $NDI_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: parse-report-v1" \
      -d "{\"source\":{\"type\":\"upload\",\"upload_id\":\"$UPLOAD_ID\"}}"
    ```
  </Tab>
</Tabs>

## Sources

Upload, URL, workspace file, or a prior parse (`ParseResultSource` /
`{ type: "parse_result", job_id }`). Reusing a parse skips page selection and
`include_images`. See [Sources](/concepts/sources-and-uploads).

## Useful options

| Option                        | Default              | Meaning                                 |
| ----------------------------- | -------------------- | --------------------------------------- |
| `page_ranges`                 | all pages            | Inclusive 1-indexed ranges              |
| `output.formats`              | `markdown`, `blocks` | Also `text`                             |
| `output.table_format`         | `html`               | or `markdown`                           |
| `output.include_images`       | `false`              | Figure crops (PDF and raster images)    |
| `output.include_page_markers` | `true`               | `--- Page N ---` in markdown/text       |
| `figures.mode`                | `describe`           | `omit` / `include` / `describe`         |
| `diagrams.mode`               | `omit`               | `omit` or `mermaid`                     |
| `chunking.strategy`           | `none`               | `none` / `page` / `section`             |
| `spreadsheet`                 | all sheets           | Sheet/row/column filters for workbooks  |
| `password`                    | unset                | Encrypted PDF; write-only, never echoed |

```python theme={"dark"}
from ndi_sdk import NdiClient

with NdiClient() as client:
    upload = client.documents.create_upload("report.pdf")
    job = client.jobs.wait(
        client.documents.parse(
            upload,
            page_ranges=[{"start": 1, "end": 1}],
            output={"formats": ["markdown", "blocks"], "table_format": "html"},
            chunking={"strategy": "page"},
            wait_seconds=30,
        ).job_id
    )
    print(len(job.result.document.chunks))
```

## Async batch

Share one `AsyncNdiClient`. Each `jobs.wait` polls independently.

```python theme={"dark"}
import asyncio

from ndi_sdk import AsyncNdiClient


async def parse_one(client: AsyncNdiClient, name: str) -> str:
    upload = await client.documents.create_upload(name)
    job = await client.documents.parse(upload, wait_seconds=30)
    job = await client.jobs.wait(job.job_id, timeout=600)
    return job.result.markdown


async def main() -> None:
    async with AsyncNdiClient() as client:
        pages = await asyncio.gather(
            parse_one(client, "invoice.pdf"),
            parse_one(client, "report.pdf"),
            return_exceptions=True,
        )
    print([type(p).__name__ if isinstance(p, Exception) else len(p) for p in pages])


asyncio.run(main())
```

TypeScript is already async — `Promise.all` over the same client.

## Errors

* **`invalid_request`** — extra body fields, bad source, password on a non-PDF
* **`unsupported_file_type`**
* **`JobFailedError`** — job `status=failed` (for example `corrupt_file`)

## Next

* [Parse response format](/guides/parse-response)
* [Extract without reparsing](/guides/extract)
* [POST /v1/parse](/api-reference/document-operations)
