> This page is for Model APIs.

> Sarvam Voice Agents (Samvaad) is a voice AI agent platform for Indian languages: build, test and deploy AI calling agents on your own phone numbers, from ₹3 a minute. Start here: https://docs.sarvam.ai/voice-agents/why-voice-agents.
> For clean Markdown of any page, append `.md` to the page URL.
> For a complete documentation index, see https://docs.sarvam.ai/llms.txt. For a section index, append `/llms.txt` to any section URL (for example https://docs.sarvam.ai/voice-agents/llms.txt).
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.sarvam.ai/_mcp/server.

# How to parse a document

> Convert a whole document into clean HTML, Markdown, or JSON with layout, reading order, and tables preserved. Powered by Sarvam Vision 2.1 across 23 languages.

Parse runs full-document OCR. It extracts all the text, keeps layout and reading order, and parses tables into HTML, Markdown, or JSON. Use it when you want the **whole document** as clean, structured content: archival, search, RAG ingestion, or reading-order-faithful conversion.

If you only need specific fields (an invoice total, a policy number), [extract them](/api/api-guides-tutorials/document-intelligence/how-to/extract) instead of post-processing a full parse.

|                 |                                                                                          |
| --------------- | ---------------------------------------------------------------------------------------- |
| **You provide** | A document                                                                               |
| **Endpoint**    | `POST /doc-ai/v1/job/parse`                                                              |
| **Output**      | A ZIP with the primary file (HTML, Markdown, or JSON), per-page metadata, and a manifest |
| **Page limit**  | 30 pages per PDF or ZIP                                                                  |

## Quick start

> **Note**
>
> **API parameter names:** Document AI uses `language` and `output_format` values `"md"`, `"html"`, or `"json"`. Do not use `language_code` (it's ignored) or `"markdown"` (it returns `400`; use `"md"` instead). This differs from STT, translate, and LID endpoints.

> **Note**
>
> **JavaScript SDK:** as of `sarvamai` 1.1.12, `parse()` does not send `output_format`, so JavaScript always returns HTML. Use the Python SDK or cURL for Markdown or JSON.

#### Submit the job

Create and submit the job in one call. You get a `job_id` back right away.

#### Python

```python
import os
from sarvamai import SarvamAI

client = SarvamAI(api_subscription_key=os.environ["SARVAM_API_KEY"])

with open("./sample-document.pdf", "rb") as f:
    job = client.doc_ai.parse(
        file=[("sample-document.pdf", f, "application/pdf")],
        language="en-IN",
        output_format="md",
    )

print(job.job_id, job.status)  # status starts as "pending"
```

#### JavaScript

```javascript
import fs from "fs";
import { SarvamAIClient } from "sarvamai";

const client = new SarvamAIClient({ apiSubscriptionKey: process.env.SARVAM_API_KEY });

const job = await client.docAi.parse({
  file: [fs.createReadStream("./sample-document.pdf")],
  language: "en-IN",
});

console.log(job.job_id, job.status); // status starts as "pending"
```

#### cURL

```bash
curl -X POST https://api.sarvam.ai/doc-ai/v1/job/parse \
  -H "api-subscription-key: $SARVAM_API_KEY" \
  -F file=@./sample-document.pdf \
  -F language="en-IN" \
  -F output_format="md"
# {"job_id": "<JOB_ID>", "status": "pending", ...}
```

#### Wait for it to finish

Poll `get_status()` every few seconds until the job reaches a [terminal state](/api/api-guides-tutorials/document-intelligence/job-lifecycle#job-states).

#### Python

```python
import time

TERMINAL = {"completed", "partially_completed", "failed", "rejected"}

while True:
    st = client.doc_ai.get_status(job_id=job.job_id)
    print(f"status={st.status} pages={st.usage.pages_processed}/{st.usage.pages_total}")
    if st.status.lower() in TERMINAL:
        break
    time.sleep(5)
```

#### JavaScript

```javascript
const TERMINAL = new Set(["completed", "partially_completed", "failed", "rejected"]);

let st;
while (true) {
  st = await client.docAi.getStatus(job.job_id);
  console.log(`status=${st.status} pages=${st.usage.pages_processed}/${st.usage.pages_total}`);
  if (TERMINAL.has(st.status.toLowerCase())) break;
  await new Promise((resolve) => setTimeout(resolve, 5000));
}
```

#### cURL

```bash
curl https://api.sarvam.ai/doc-ai/v1/job/<JOB_ID>/status \
  -H "api-subscription-key: $SARVAM_API_KEY"
# {"status": "running", "usage": {"pages_processed": 6, "pages_total": 10}, ...}
# Repeat until status is completed, partially_completed, failed, or rejected
```

#### Download the output

When the job is `completed` or `partially_completed`, ask for the download link and save the ZIP. It holds your Markdown, HTML, or JSON file. See [Read the output](#read-the-output).

#### Python

```python
import urllib.request

if st.status.lower() in ("completed", "partially_completed"):
    dl = client.doc_ai.get_download_url(job_id=job.job_id)
    urllib.request.urlretrieve(dl.url, "output.zip")
    print("saved output.zip")
else:
    print("job did not complete:", st.status)
```

#### JavaScript

```javascript
if (["completed", "partially_completed"].includes(st.status.toLowerCase())) {
  const dl = await client.docAi.getDownloadUrl(job.job_id);
  const res = await fetch(dl.url);
  fs.writeFileSync("output.zip", Buffer.from(await res.arrayBuffer()));
  console.log("saved output.zip");
} else {
  console.log("job did not complete:", st.status);
}
```

#### cURL

```bash
curl https://api.sarvam.ai/doc-ai/v1/job/<JOB_ID>/download-url \
  -H "api-subscription-key: $SARVAM_API_KEY"
# {"method": "GET", "url": "https://..."}

# Then download the ZIP from that url
curl -o output.zip "<DOWNLOAD_URL>"
```

#### Full example (copy and run)

Submit, wait, and get the output in one script. Save it next to your document and run it.

#### Python

```python
import os, time
import urllib.request
from sarvamai import SarvamAI
from sarvamai.core.api_error import ApiError

client = SarvamAI(api_subscription_key=os.environ["SARVAM_API_KEY"])

try:
    # 1. Submit the job
    with open("./sample-document.pdf", "rb") as f:
        job = client.doc_ai.parse(
            file=[("sample-document.pdf", f, "application/pdf")],
            language="en-IN",
            output_format="md",
        )

    print(job.job_id, job.status)  # status starts as "pending"

    # 2. Wait until it reaches a terminal state
    TERMINAL = {"completed", "partially_completed", "failed", "rejected"}

    while True:
        st = client.doc_ai.get_status(job_id=job.job_id)
        print(f"status={st.status} pages={st.usage.pages_processed}/{st.usage.pages_total}")
        if st.status.lower() in TERMINAL:
            break
        time.sleep(5)

    # 3. Get the output
    if st.status.lower() in ("completed", "partially_completed"):
        dl = client.doc_ai.get_download_url(job_id=job.job_id)
        urllib.request.urlretrieve(dl.url, "output.zip")
        print("saved output.zip")
    else:
        print("job did not complete:", st.status)
except FileNotFoundError:
    print("Document file not found: ./sample-document.pdf")
except ApiError as e:
    print(f"API error {e.status_code}: {e.body}")
```

#### JavaScript

```javascript
import fs from "fs";
import { SarvamAIClient } from "sarvamai";

const client = new SarvamAIClient({ apiSubscriptionKey: process.env.SARVAM_API_KEY });

async function main() {
  try {
    // 1. Submit the job
    const job = await client.docAi.parse({
      file: [fs.createReadStream("./sample-document.pdf")],
      language: "en-IN",
    });

    console.log(job.job_id, job.status); // status starts as "pending"

    // 2. Wait until it reaches a terminal state
    const TERMINAL = new Set(["completed", "partially_completed", "failed", "rejected"]);

    let st;
    while (true) {
      st = await client.docAi.getStatus(job.job_id);
      console.log(`status=${st.status} pages=${st.usage.pages_processed}/${st.usage.pages_total}`);
      if (TERMINAL.has(st.status.toLowerCase())) break;
      await new Promise((resolve) => setTimeout(resolve, 5000));
    }

    // 3. Get the output
    if (["completed", "partially_completed"].includes(st.status.toLowerCase())) {
      const dl = await client.docAi.getDownloadUrl(job.job_id);
      const res = await fetch(dl.url);
      fs.writeFileSync("output.zip", Buffer.from(await res.arrayBuffer()));
      console.log("saved output.zip");
    } else {
      console.log("job did not complete:", st.status);
    }
  } catch (err) {
    console.error("Parse job failed:", err.message ?? err);
  }
}

main();
```

## Parameters

| Parameter       | Required | Default | Description                                                                                                                                                              |
| --------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `file`          | Yes      |         | The document to process. See [Languages, formats & limits](/api/api-guides-tutorials/document-intelligence/languages-formats-limits) for accepted types and size limits. |
| `language`      | No       | Indic   | A BCP-47 code such as `en-IN` or `hi-IN`. Always pass the correct code for best accuracy, and set `en-IN` for English documents.                                         |
| `output_format` | No       | `html`  | `html`, `md`, or `json`.                                                                                                                                                 |

## Output formats

The `output_format` parameter controls the primary content format. Choose `html` (the default), `md` (Markdown), or `json`.

| Format   | `output_format` value | Description                                                        |
| -------- | --------------------- | ------------------------------------------------------------------ |
| HTML     | `html`                | Rich HTML output for web rendering (default)                       |
| Markdown | `md`                  | Human-readable Markdown output                                     |
| JSON     | `json`                | Structured JSON with blocks containing text, tag, and bounding box |

## Read the output

The ZIP you saved in the last step holds the primary file, a `metadata/page_NNN.json` per page, and a `manifest.json`:

```
manifest.json
sample-document.pdf/
├── sample-document.md          # primary file (.html or .json for those formats)
└── metadata/
    └── page_001.json           # one file per page
```

The folder and file names follow your uploaded file name. The download link is a plain `GET` and needs no API key header. To read the Markdown file straight from the ZIP:

#### Python

```python
import zipfile

with zipfile.ZipFile("output.zip") as z:
    print(z.namelist())
    markdown = next(z.read(n).decode() for n in z.namelist() if n.endswith(".md"))
    print(markdown)
```

#### cURL

```bash
unzip output.zip -d output
cat output/*/*.md
```

Match on `.html` or `.json` instead if you chose another `output_format`.

## Limits

> **Warning**
>
> **Page limit:** PDF and ZIP uploads are limited to **30 pages for Parse**. Exceeding the limit returns a `400 Bad Request` with code `invalid_request_error`. Split larger documents into batches before uploading.

> **Note**
>
> For ZIP files, include only JPG and PNG document pages in a flat structure (no nested folders). The API processes all pages in the archive and maintains page order based on filename.

## Next steps

#### [How to extract](/api/api-guides-tutorials/document-intelligence/how-to/extract)

Pull specific fields into JSON instead of the whole page.

#### [Jobs & Errors](/api/api-guides-tutorials/document-intelligence/job-lifecycle)

Job states, polling, and error handling.