> 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 full documentation content in one file, see https://docs.sarvam.ai/llms-full.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.sarvam.ai/_mcp/server.

# Document Digitization Overview

> Transform documents into structured, queryable data with Sarvam's Document Digitization API. Powered by Sarvam Vision for accurate text extraction and table parsing across 23 languages (22 Indian + English).

Sarvam's Document Digitization API provides enterprise-grade document processing powered by [Sarvam Vision](/api/getting-started/models/sarvam-vision), our state-of-the-art multimodal model.

<p>
  Transform any document into structured, searchable, and machine-readable data with world-class accuracy.
</p>

Our 3B parameter multimodal model powering Document Digitization. SOTA performance on global and Indic benchmarks.

***

## What is Document Digitization?

Document Digitization is a comprehensive document processing pipeline powered by Sarvam Vision that:

1. **Extracts Text**: High-fidelity text extraction across 23 languages (22 Indian + English)
2. **Preserves Structure**: Maintains document layout, reading order, and hierarchies
3. **Parses Tables**: Transforms tables into structured HTML or Markdown formats
4. **Outputs Structured Data**: Generates clean, machine-readable HTML or Markdown output

***

## Key Features

Native support for all Constitutionally recognized Indian languages and English with script-native accuracy.

Export to HTML or Markdown files, delivered as a ZIP archive. A JSON file with structured page-level data is always included alongside your chosen format.

Intelligent table detection and conversion to structured formats.

Process multi-page documents and ZIP archives with automatic page handling.

Intelligent reading order detection and complex layout handling.

Scalable API with job management, progress tracking, and error handling.

***

## Supported Languages

Document Digitization supports all 22 Constitutionally recognized Indian languages:

| Language  | Code    | Script     |
| --------- | ------- | ---------- |
| Hindi     | `hi-IN` | Devanagari |
| Bengali   | `bn-IN` | Bengali    |
| Tamil     | `ta-IN` | Tamil      |
| Telugu    | `te-IN` | Telugu     |
| Marathi   | `mr-IN` | Devanagari |
| Gujarati  | `gu-IN` | Gujarati   |
| Kannada   | `kn-IN` | Kannada    |
| Malayalam | `ml-IN` | Malayalam  |
| Odia      | `od-IN` | Odia       |
| Punjabi   | `pa-IN` | Gurmukhi   |
| English   | `en-IN` | Latin      |

| Language | Code     | Script            |
| -------- | -------- | ----------------- |
| Assamese | `as-IN`  | Assamese          |
| Urdu     | `ur-IN`  | Perso-Arabic      |
| Sanskrit | `sa-IN`  | Devanagari        |
| Nepali   | `ne-IN`  | Devanagari        |
| Konkani  | `kok-IN` | Devanagari        |
| Maithili | `mai-IN` | Devanagari        |
| Sindhi   | `sd-IN`  | Devanagari/Arabic |
| Kashmiri | `ks-IN`  | Perso-Arabic      |
| Dogri    | `doi-IN` | Devanagari        |
| Manipuri | `mni-IN` | Meetei Mayek      |
| Bodo     | `brx-IN` | Devanagari        |
| Santali  | `sat-IN` | Ol Chiki          |

***

## Supported Input Formats

| Format | Extension       | Description                                                            |
| ------ | --------------- | ---------------------------------------------------------------------- |
| PDF    | `.pdf`          | Multi-page PDF documents (max 10 pages)                                |
| PNG    | `.png`          | Document page images                                                   |
| JPEG   | `.jpg`, `.jpeg` | Document page images                                                   |
| ZIP    | `.zip`          | Flat archive containing document page images — JPG/PNG (max 10 images) |

**Page Limit:** Both PDF and ZIP uploads are limited to a maximum of **10 pages**. Exceeding this limit will return a `422 Unprocessable Entity` error with code `max_page_limit_exceeded`. Split larger documents into batches of 10 pages or fewer before uploading.

For ZIP files, include only JPG and PNG document pages in a flat structure (no nested folders). The API will process all pages in the archive and maintain page order based on filename.

***

## Output Formats

The `output_format` parameter controls the primary content format in the output ZIP archive. You can choose between `html` and `md` (Markdown).

**JSON is always included by default.** Regardless of whether you choose HTML or Markdown as your output format, a JSON file with structured page-level data is always included in the output ZIP archive. This JSON contains the same extracted content in a machine-readable format, making it easy to programmatically process the results alongside the human-readable HTML or Markdown output.

| Format   | `output_format` value | Description                                         |
| -------- | --------------------- | --------------------------------------------------- |
| Markdown | `md`                  | Human-readable Markdown output + JSON page data     |
| HTML     | `html`                | Rich HTML output for web rendering + JSON page data |

***

## Quick Start

**API parameter names:** Document Digitization uses `language` and `output_format` values `"md"` or `"html"` in `job_parameters`. Do not use `language_code` (ignored by the API) or `"markdown"` (returns `400` — use `"md"` instead). This differs from STT, translate, and LID endpoints.

Get started with Document Digitization in minutes:

```python
from sarvamai import SarvamAI

client = SarvamAI(api_subscription_key="YOUR_SARVAM_API_KEY")

# Create a digitization job
job = client.document_intelligence.create_job(language="hi-IN", output_format="md")
print(f"Job created: {job.job_id} (language={job.language}, output={job.output_format})")

# Upload your document (PDF, PNG, JPG, or ZIP)
job.upload_file("document.pdf")
print("Uploaded — starting processing...")

# Start the job and wait for it to finish
job.start()
status = job.wait_until_complete()
print(f"Processing finished: {status.job_state}")

# Download the results
job.download_output("output.zip")
print("Saved to output.zip — contains your document (.md) plus page-by-page JSON data")
```

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

const client = new SarvamAIClient({
    apiSubscriptionKey: "YOUR_SARVAM_API_KEY"
});

async function processDocument() {
    // Create a digitization job
    const job = await client.documentIntelligence.createJob({
        language: "hi-IN",
        outputFormat: "md"
    });
    console.log(`Job created: ${job.jobId} (language=${job.language}, output=${job.outputFormat})`);

    // Upload your document (PDF, PNG, JPG, or ZIP)
    await job.uploadFile("document.pdf");
    console.log("Uploaded — starting processing...");

    // Start the job and wait for it to finish
    await job.start();
    const status = await job.waitUntilComplete();
    console.log(`Processing finished: ${status.job_state}`);

    // Download the results
    await job.downloadOutput("output.zip");
    console.log("Saved to output.zip — contains your document (.md) plus page-by-page JSON data");
}

processDocument();
```

***

## Polling Defaults

`job.wait_until_complete()` polls `GET /doc-digitization/job/v1/{job_id}/status` in a loop until the job reaches a terminal state. The Python and JavaScript SDKs behave differently here:

```python
def wait_until_complete(self, poll_interval: float = 2.0, timeout: Optional[float] = None) -> DocDigitizationJobStatusResponse
```

| Parameter       | Default                  | Meaning                                                                                                  |
| --------------- | ------------------------ | -------------------------------------------------------------------------------------------------------- |
| `poll_interval` | **2 seconds**            | Time between status checks                                                                               |
| `timeout`       | **None (waits forever)** | Pass an explicit value (e.g. `timeout=600`) if you want a `TimeoutError` instead of waiting indefinitely |

In Python, `wait_until_complete()` has no default timeout. If a job never reaches a terminal state, the call blocks forever unless you pass `timeout` explicitly.

```typescript
new DocumentIntelligenceJob(client, jobId, {
  pollingIntervalMs?: number;    // default: 2000
  maxPollingAttempts?: number;   // default: 150
})
```

| Parameter            | Default       | Meaning                                                                    |
| -------------------- | ------------- | -------------------------------------------------------------------------- |
| `pollingIntervalMs`  | **2000 (2s)** | Time between status checks, matches Python's interval                      |
| `maxPollingAttempts` | **150**       | Maximum poll attempts before giving up. 150 × 2s = 300 seconds (5 minutes) |

Once `maxPollingAttempts` is reached, `waitUntilComplete()` throws:

```
Error: Job did not complete within 300 seconds
```

Unlike Python, the JavaScript SDK has a default timeout of 5 minutes, expressed as an attempt count rather than a time value. Pass a larger `maxPollingAttempts` for longer documents (e.g. `1800` for roughly 1 hour at the default 2s interval), or `Infinity` to wait indefinitely, matching Python's default behavior.

This differs from Batch STT's `wait_until_complete()`, which defaults to a 5-second poll interval and a 600-second timeout in both SDKs. See [Batch STT → Polling Defaults](/api/speech-to-text/apis/batch#polling-defaults).

## Webhook Support

For long-running jobs, use a webhook instead of polling. You'll be notified when the job completes instead of calling `wait_until_complete()`.

### Setting Up Webhooks

The convenience `create_job()` / `createJob()` methods only accept a plain `callback_url` string. They do not accept an `auth_token`. If you need the `X-SARVAM-JOB-CALLBACK-TOKEN` auth header, use the lower-level method shown in the second tab below.

```python
from sarvamai import SarvamAI

client = SarvamAI(api_subscription_key="YOUR_SARVAM_API_KEY")

job = client.document_intelligence.create_job(
    language="hi-IN",
    output_format="md",
    callback_url="https://your-server.com/webhook-endpoint"
)
```

```python
from sarvamai import SarvamAI

client = SarvamAI(api_subscription_key="YOUR_SARVAM_API_KEY")

response = client.document_intelligence.with_raw_response.initialise(
    job_parameters={"language": "hi-IN", "output_format": "md"},
    callback={
        "url": "https://your-server.com/webhook-endpoint",
        "auth_token": "your-secret-token"
    }
)
job_id = response.data.job_id
```

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

const client = new SarvamAIClient({
    apiSubscriptionKey: "YOUR_SARVAM_API_KEY"
});

const job = await client.documentIntelligence.createJob({
    language: "hi-IN",
    outputFormat: "md",
    callbackUrl: "https://your-server.com/webhook-endpoint"
});
```

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

const client = new SarvamAIClient({
    apiSubscriptionKey: "YOUR_SARVAM_API_KEY"
});

const response = await client.documentIntelligence.initialise({
    job_parameters: { language: "hi-IN", output_format: "md" },
    callback: {
        url: "https://your-server.com/webhook-endpoint",
        auth_token: "your-secret-token"
    }
});
```

### Webhook Payload

When the job finishes (or fails), Sarvam AI sends a **POST** to your callback URL. The body matches the same `DocDigitizationJobStatusResponse` schema returned by [`GET /doc-digitization/job/v1/{job_id}/status`](/api/document-intelligence/overview#response-format). It is a status notification, not the extracted document content itself.

Sarvam includes your `auth_token` in the **`X-SARVAM-JOB-CALLBACK-TOKEN`** request header (validate this on every request).

```json
{
  "job_id": "20260707_6eb74174-e2c3-490d-afe5-d2e7e9e2b6c7",
  "job_state": "Completed",
  "created_at": "2026-07-07T11:13:27Z",
  "updated_at": "2026-07-07T11:15:02Z",
  "storage_container_type": "Azure_V1",
  "total_files": 1,
  "successful_files_count": 1,
  "failed_files_count": 0,
  "error_message": "",
  "job_details": [
    {
      "inputs": [{ "file_name": "document.pdf", "file_id": "input-0" }],
      "outputs": [{ "file_name": "output.zip", "file_id": "output-0" }],
      "state": "Success",
      "total_pages": 3,
      "pages_processed": 3,
      "pages_succeeded": 3,
      "pages_failed": 0,
      "error_message": "",
      "error_code": null,
      "page_errors": []
    }
  ]
}
```

**Extracted content is not in the webhook.** Once `job_state` is `Completed`, call [`GET /doc-digitization/job/v1/{job_id}/download-files`](/api/document-intelligence/overview) (or `job.download_output()` in the SDK) to fetch the output ZIP.

Your webhook server must respond with a `200` status code promptly. Make sure your webhook URL is publicly accessible and uses HTTPS. `http://` URLs are rejected at job-creation time.

***

## Response Format

### Job Status Response

```json
{
  "job_id": "abc123-def456-ghi789",
  "job_state": "Completed",
  "created_at": "2026-02-04T10:30:00Z",
  "updated_at": "2026-02-04T10:35:00Z",
  "page_metrics": {
    "total_pages": 10,
    "pages_processed": 10,
    "pages_succeeded": 10,
    "pages_failed": 0
  }
}
```

### Job States

| State                | Description                         |
| -------------------- | ----------------------------------- |
| `Accepted`           | Job created, awaiting file upload   |
| `Pending`            | File uploaded, waiting to start     |
| `Running`            | Job is being processed              |
| `Completed`          | All pages processed successfully    |
| `PartiallyCompleted` | Some pages succeeded, some failed   |
| `Failed`             | All pages failed or job-level error |

***

## Error Handling

```python
from sarvamai import SarvamAI
from sarvamai.core.api_error import ApiError

client = SarvamAI(api_subscription_key="YOUR_SARVAM_API_KEY")

try:
    job = client.document_intelligence.create_job(
        language="hi-IN",
        output_format="md"
    )
    job.upload_file("document.pdf")
    job.start()
    status = job.wait_until_complete()
    
    if status.job_state == "Completed":
        job.download_output("./output.zip")
        print("Output saved to ./output.zip")
    else:
        print(f"Job failed: {status}")
        
except ApiError as e:
    if e.status_code == 400:
        print(f"Bad request: {e.body}")
    elif e.status_code == 403:
        print("Invalid API key")
    elif e.status_code == 429:
        print("Rate limit exceeded")
    else:
        print(f"Error {e.status_code}: {e.body}")
except FileNotFoundError:
    print("Document file not found")
```

### Error Codes

The full error-code table, retry guidance, and SDK exception reference live on the central [Errors & Troubleshooting](/api/errors-troubleshooting) page. Errors specific to this API:

| HTTP Status | Error Code                   | Description                                                      |
| ----------- | ---------------------------- | ---------------------------------------------------------------- |
| `404`       | `not_found_error`            | Job not found                                                    |
| `422`       | `unprocessable_entity_error` | Invalid file format or corrupted file                            |
| `422`       | `max_page_limit_exceeded`    | Document exceeds the 10-page limit (applies to both PDF and ZIP) |

***

## Best Practices

Use Markdown for human-readable output and HTML for web rendering and rich formatting. JSON page-level data is always included regardless of your choice.

Always specify the correct language code for optimal text extraction accuracy, especially for Indian languages.

For large documents, monitor `page_metrics` to track progress and handle partial failures gracefully.

Choose HTML output format when you need to preserve table structures and rich formatting.

***

## Limits

| Limit                   | Value                                                               |
| ----------------------- | ------------------------------------------------------------------- |
| Max pages per PDF       | 10 (`422 max_page_limit_exceeded` if exceeded)                      |
| Max images per ZIP      | 10                                                                  |
| Max file size           | 200 MB                                                              |
| Supported input formats | PDF, PNG, JPG, ZIP                                                  |
| Rate limit              | 10 requests/minute (all plans) — see [Rate Limits](/api/ratelimits) |

***

## Next Steps

Learn about the model powering Document Digitization.

Complete API documentation with all parameters and options.

Upload and process documents in the API Dashboard.