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

# Parsing documents

> Convert any document to clean Markdown, synchronously or as an async job.

<Frame>
  <img src="https://mintcdn.com/lighton-developers/2zPPvXRyCmHZlhqh/images/parse_usecase.png?fit=max&auto=format&n=2zPPvXRyCmHZlhqh&q=85&s=de760511bbd2656d400138ee80c1dc68" alt="Illustration of the Parse use case." style={{ borderRadius: '0.5rem' }} width="1120" height="494" data-path="images/parse_usecase.png" />
</Frame>

Parse converts a document to structured Markdown without storing or indexing it. By default the call is synchronous: send a file, get Markdown back immediately. For larger documents, an async mode queues the job and lets you poll for the result.

Use Parse when you want the text content of a document for your own purposes: feeding it to an LLM, storing it in your database, displaying it in a UI, or pre-processing it before ingestion. Nothing is saved on LightOn's side.

<Tip>
  If you want the document to be **searchable**, use [Files](/tutorials/files) instead: it ingests and indexes the document permanently. The full schema for [`POST /api/v3/parse`](/api-reference/parse/parse-a-document-to-markdown) is in the [API reference](/api-reference/introduction).
</Tip>

## Parse a document

<CodeGroup>
  ```python From a file theme={null}
  import requests

  response = requests.post(
      "https://api.lighton.ai/api/v3/parse",
      headers={"Authorization": "Bearer $LIGHTON_API_KEY"},
      files={"file": open("report.pdf", "rb")},
  )

  pages = response.json()["result"]["pages"]
  # Each page is {"index": <int>, "markdown": <str>}. Join them for the full document:
  markdown = "\n\n".join(page["markdown"] for page in pages)
  print(markdown[:200])
  # → # Q4 Financial Report\n\n## Executive Summary\n\nRevenue grew 18%…
  ```

  ```python From a URL theme={null}
  response = requests.post(
      "https://api.lighton.ai/api/v3/parse",
      headers={"Authorization": "Bearer $LIGHTON_API_KEY"},
      json={"document": "https://example.com/whitepaper.pdf"},
  )
  ```
</CodeGroup>

`file` (multipart) and `document` (JSON URL) are mutually exclusive: pass one or the other.

## What comes back

The response includes the parsed Markdown, broken out **per page**, along with metadata about the original document:

```json theme={null}
{
  "id": "parse_0196e4b2a3c14d5e8f7a9b2c1d0e3f4a",
  "status": "completed",
  "created_at": "2026-03-31T10:00:00+00:00",
  "completed_at": "2026-03-31T10:00:03+00:00",
  "processing_time_ms": 2840,
  "document": {
    "filename": "report.pdf",
    "page_count": 3,
    "file_size_bytes": 245120,
    "mime_type": "application/pdf"
  },
  "result": {
    "pages": [
      { "index": 1, "markdown": "# Invoice\n\nInvoice Number: INV-2026-001\nDate: March 15, 2026\n…" },
      { "index": 2, "markdown": "## Line items\n\n| Item | Qty | Price |\n…" },
      { "index": 3, "markdown": "## Totals\n\nSubtotal: …" }
    ]
  },
  "usage": {
    "pages_processed": 3
  }
}
```

`result.pages` is an array, one entry per page, each with a 1-based `index` and the `markdown` for that page. Headings, lists, tables, and code blocks are preserved where the source document supports them. Concatenate the per-page `markdown` (in `index` order) to reconstruct the full document.

## Large documents: async mode

Sync mode is capped at 20 MB / 15 pages. For larger documents, set `options.async = true`. The call returns **202** immediately with a `parse_<token>` job id instead of blocking, and you poll [`GET /api/v3/parse/{id}`](/api-reference/parse/get-the-status-and-result-of-an-async-parse-job) until the job reaches a terminal status.

<CodeGroup>
  ```python Submit async job theme={null}
  response = requests.post(
      "https://api.lighton.ai/api/v3/parse",
      headers={"Authorization": "Bearer $LIGHTON_API_KEY"},
      json={
          "document": "https://example.com/large-report.pdf",
          "options": {"async": True},
      },
  )
  job_id = response.json()["id"]  # e.g. "parse_Kg"
  ```

  ```python Poll for the result theme={null}
  import time

  while True:
      r = requests.get(
          f"https://api.lighton.ai/api/v3/parse/{job_id}",
          headers={"Authorization": "Bearer $LIGHTON_API_KEY"},
      )
      job = r.json()
      if job["status"] in ("completed", "failed"):
          break
      time.sleep(5)

  pages = job["result"]["pages"]  # null until status == "completed"
  markdown = "\n\n".join(page["markdown"] for page in pages)
  ```
</CodeGroup>

For multipart uploads, pass `options` as a JSON-encoded form field: `-F 'options={"async":true}'`.

The polling response uses the same envelope as the sync result: `result`, `usage`, `completed_at`, and `processing_time_ms` are `null` while the job is in flight and populated once `status` is `completed`. On terminal failure, `status` is `failed` and an `error` block describes the cause.

<Tip>
  Recommended polling cadence: every 1s for the first 10s, then every 5s, capped at 30s. Stop once `status` is `completed` or `failed`.
</Tip>

## Supported formats and limits

**Formats:** `.pdf`, `.png`, `.jpg`, `.jpeg`, `.pptx`, `.ppt`, `.odp`, `.docx`, `.odt`, `.doc`, `.html`

| Mode                           | Max file size | Max pages  |
| ------------------------------ | ------------- | ---------- |
| Sync (default)                 | 20 MB         | 15 pages   |
| Async (`options.async = true`) | 100 MB        | 1000 pages |

To make a document **searchable** rather than just converting it once, ingest it with [Files](/tutorials/files) and retrieve relevant sections with [Search](/tutorials/search).

## Common errors

| Status | Cause                                                                |
| ------ | -------------------------------------------------------------------- |
| `400`  | Missing document, unsupported format, or page limit exceeded         |
| `401`  | Missing or invalid API key                                           |
| `413`  | File exceeds the size limit for the mode (20 MB sync / 100 MB async) |
| `429`  | Rate limit exceeded                                                  |
| `503`  | Parsing backend is overloaded. Retry later                           |
