> 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 Intelligence Overview > Transform documents into structured, queryable data with Sarvam's Document AI API. Powered by Sarvam Vision 1.5 for text digitization and schema-based field extraction across 23 languages (22 Indian + English). Sarvam's Document AI API provides enterprise-grade document processing powered by [Sarvam Vision 1.5](/api/getting-started/models/sarvam-vision), our state-of-the-art multimodal model. Turn any document into structured, searchable, machine-readable data, whether you need the full page digitized or specific fields extracted. #### [Sarvam Vision 1.5](/api/getting-started/models/sarvam-vision) An upgraded Sarvam Vision, trained to do both OCR and key-value extraction. SOTA performance on global and Indic document benchmarks. ## Two ways to process a document Document AI gives you two endpoints over the same job lifecycle. Pick based on what you need out the other end. #### Digitise Full-document OCR. Extracts all text, preserves layout and reading order, and parses tables into HTML, Markdown, or JSON. Use when you want the **whole document** as clean, structured content. #### Extract Schema-based key-value extraction. You define the fields you want; Sarvam Vision 1.5 returns them as structured JSON. Use when you want **specific fields**, not the whole page. | Aspect | Digitise | Extract | | --------------- | ------------------------------------------------------------------ | ---------------------------------------------- | | **Output** | Full text + layout (HTML, Markdown, or JSON) | Just the fields you define (JSON / CSV / XLSX) | | **You provide** | A document | A document + a schema (or saved `config_id`) | | **Best for** | Archival, search, RAG ingestion, reading-order-faithful conversion | KYC, invoices, forms, structured data capture | | **Endpoint** | `POST /doc-ai/v1/job/digitise` | `POST /doc-ai/v1/job/extract` | ## What's new > **Note** > > **Document AI supersedes the earlier Document Digitization API.** Two changes matter most: > > 1. Processing is now powered by an **upgraded Sarvam Vision 1.5**, trained to do both OCR and key-value extraction. > 2. Alongside full-document **Digitise** (OCR), we now offer **Extract** for schema-based field extraction. > > The API namespace is `doc_ai`, and jobs are created with a single `digitise()` or `extract()` call. ## Key Features #### 23 Language Support Native support for all Constitutionally recognized Indian languages and English with script-native accuracy. #### Digitise + Extract Full-document digitization and targeted key-value extraction from one API and one job model. #### Table Extraction Intelligent table detection and conversion to structured HTML, Markdown, or JSON. #### Schema-Based Fields Define exactly the fields you want in Extract, returned as structured data. #### Layout Preservation Intelligent reading-order detection and complex layout handling. #### Enterprise-Ready Scalable API with job management, progress tracking, and error handling. ## Supported Languages Document AI supports all 22 Constitutionally recognized Indian languages plus English: | Language | Code | | --------- | -------- | | Hindi | `hi-IN` | | Bengali | `bn-IN` | | Tamil | `ta-IN` | | Telugu | `te-IN` | | Marathi | `mr-IN` | | Gujarati | `gu-IN` | | Kannada | `kn-IN` | | Malayalam | `ml-IN` | | Odia | `od-IN` | | Punjabi | `pa-IN` | | Assamese | `as-IN` | | Bodo | `brx-IN` | | Dogri | `doi-IN` | | Kashmiri | `ks-IN` | | Konkani | `kok-IN` | | Maithili | `mai-IN` | | Manipuri | `mni-IN` | | Nepali | `ne-IN` | | Sanskrit | `sa-IN` | | Santali | `sat-IN` | | Sindhi | `sd-IN` | | Urdu | `ur-IN` | | English | `en-IN` | ## 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 of document page images (JPG or PNG, max 10 images) | > **Warning** > > **Page Limit:** Both PDF and ZIP uploads are limited to a maximum of **10 pages**. Exceeding this limit returns a `400 Bad Request` with code `invalid_request_error`. Split larger documents into batches of 10 pages or fewer 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. ## Output Formats ### Digitise 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 | The download is a ZIP archive holding the primary file, a `metadata/page_NNN.json` per page, and a `manifest.json`. ### Extract | Format | `output_format` value | Description | | ------ | --------------------- | ----------------------------------------- | | JSON | `json` | Structured field values (default) | | CSV | `csv` | Flat tabular output, one row per document | | XLSX | `xlsx` | Spreadsheet output | ## Quick Start: Digitise > **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. #### Python ```python import os, time from sarvamai import SarvamAI from sarvamai.core.api_error import ApiError client = SarvamAI(api_subscription_key=os.environ["SARVAM_API_KEY"]) try: # Create and submit a digitise job in one call with open("./sample-document.pdf", "rb") as f: job = client.doc_ai.digitise( file=[("sample-document.pdf", f, "application/pdf")], language="en-IN", output_format="md", ) print("job created:", job.job_id, "| status:", job.status) # Poll until the job 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) # Fetch the output if st.status.lower() in ("completed", "partially_completed"): dl = client.doc_ai.get_download_url(job_id=job.job_id) print("download:", dl.method, dl.url) 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 { // Create and submit a digitise job in one call const job = await client.docAi.digitise({ file: [fs.createReadStream("./sample-document.pdf")], language: "en-IN", output_format: "md", }); console.log("job created:", job.job_id, "| status:", job.status); // Poll until the job 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)); } // Fetch the output if (["completed", "partially_completed"].includes(st.status.toLowerCase())) { const dl = await client.docAi.getDownloadUrl(job.job_id); console.log("download:", dl.method, dl.url); } } catch (err) { console.error("Digitise job failed:", err.message ?? err); } } main(); ``` #### cURL ```bash # Create and submit a digitise job curl -X POST https://api.sarvam.ai/doc-ai/v1/job/digitise \ -H "api-subscription-key: $SARVAM_API_KEY" \ -F file=@./sample-document.pdf \ -F language="en-IN" \ -F output_format="md" # {"job_id": "", "status": "pending", ...} # Poll until the job reaches a terminal state curl https://api.sarvam.ai/doc-ai/v1/job//status \ -H "api-subscription-key: $SARVAM_API_KEY" # {"status": "running", "usage": {"pages_processed": 6, "pages_total": 10}, ...} # Once status is "completed" or "partially_completed", fetch the output curl https://api.sarvam.ai/doc-ai/v1/job//download-url \ -H "api-subscription-key: $SARVAM_API_KEY" # {"method": "GET", "url": "https://..."} ``` ## Quick Start: Extract Extract takes the same document input, plus a **schema** describing the fields you want (or a saved `config_id`). The result is structured JSON with the field values you defined. #### Python ```python import json, os, time from sarvamai import SarvamAI from sarvamai.core.api_error import ApiError client = SarvamAI(api_subscription_key=os.environ["SARVAM_API_KEY"]) # Define the fields you want back schema = { "type": "object", "properties": { "policy_number": {"type": "string", "description": "Insurance policy number"}, "insured_name": {"type": "string", "description": "Name of the insured person"}, "sum_insured": {"type": "number", "description": "Total sum insured, in INR"}, }, } try: # Create and submit an extract job in one call with open("./insurance-policy.pdf", "rb") as f: job = client.doc_ai.extract( file=[("insurance-policy.pdf", f, "application/pdf")], schema=json.dumps(schema), # or: config_id="cfg_abc123" language="en-IN", output_format="json", ) print("job created:", job.job_id, "| status:", job.status) # Poll until the job 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) # Fetch the extracted fields if st.status.lower() in ("completed", "partially_completed"): results = client.doc_ai.get_results(job_id=job.job_id) print(results.result) # {"policy_number": ..., "insured_name": ..., "sum_insured": ...} except FileNotFoundError: print("Document file not found: ./insurance-policy.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 }); // Define the fields you want back const schema = { type: "object", properties: { policy_number: { type: "string", description: "Insurance policy number" }, insured_name: { type: "string", description: "Name of the insured person" }, sum_insured: { type: "number", description: "Total sum insured, in INR" }, }, }; async function main() { try { // Create and submit an extract job in one call const job = await client.docAi.extract({ file: [fs.createReadStream("./insurance-policy.pdf")], schema: JSON.stringify(schema), // or: config_id: "cfg_abc123" language: "en-IN", output_format: "json", }); console.log("job created:", job.job_id, "| status:", job.status); // Poll until the job 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)); } // Fetch the extracted fields if (["completed", "partially_completed"].includes(st.status.toLowerCase())) { const results = await client.docAi.getResults(job.job_id); console.log(results.result); // { policy_number: ..., insured_name: ..., sum_insured: ... } } } catch (err) { console.error("Extract job failed:", err.message ?? err); } } main(); ``` #### cURL ```bash # Create and submit an extract job curl -X POST https://api.sarvam.ai/doc-ai/v1/job/extract \ -H "api-subscription-key: $SARVAM_API_KEY" \ -F file=@./insurance-policy.pdf \ -F schema='{ "type": "object", "properties": { "policy_number": {"type": "string", "description": "Insurance policy number"}, "insured_name": {"type": "string", "description": "Name of the insured person"}, "sum_insured": {"type": "number", "description": "Total sum insured, in INR"} } }' \ -F language="en-IN" \ -F output_format="json" # {"job_id": "", "status": "pending", ...} # Poll until the job reaches a terminal state curl https://api.sarvam.ai/doc-ai/v1/job//status \ -H "api-subscription-key: $SARVAM_API_KEY" # {"status": "running", "usage": {"pages_processed": 6, "pages_total": 10}, ...} # Once status is "completed" or "partially_completed", fetch the extracted fields curl https://api.sarvam.ai/doc-ai/v1/job//results \ -H "api-subscription-key: $SARVAM_API_KEY" # {"result": {"policy_number": ..., "insured_name": ..., "sum_insured": ...}} ``` > **Note** > > **Provide exactly one of `schema` or `config_id`.** Use an inline `schema` for ad-hoc extraction, or a saved `config_id` for a schema you've set up on the platform. Sending both, or neither, returns a `400`. > **Note** > > **Pass `schema` as a JSON string, not an object.** It's sent as a multipart form field, so both SDKs type it as a string. Use `json.dumps(schema)` in Python and `JSON.stringify(schema)` in JavaScript. Passing a dict in Python raises `AttributeError: 'dict' object has no attribute 'read'`. > > Note also that the JavaScript SDK is generated without a serialization layer, so its request parameters and response fields use the wire names (`output_format`, `job_id`, `pages_processed`), not camelCase. `file` takes an **array**, and `job_id` is a **positional** argument, `getStatus(job_id)`, not `getStatus({ job_id })`. ### Schema rules The inline schema is a JSON object with these constraints: * Root must be `type: "object"` with a non-empty `properties` map. * Every field needs a `type` and a non-empty `description`. The description guides the model, so make it specific. * Supported field types: `string`, `number`, `integer`, `boolean`, `object`, `array`. * An optional `enum` may be supplied to constrain values. * Maximum nesting depth is 4. ## Job Lifecycle Both `digitise()` and `extract()` create and submit a job in a single call, returning a `job_id` and an initial status. You then poll `get_status()` until the job reaches a terminal state, and fetch output with `get_results()` (structured data) or `get_download_url()` (output file). ``` digitise() / extract() → poll get_status() → get_results() / get_download_url() ``` ### Job States | State | Description | | --------------------- | --------------------------------------------------------------- | | `pending` | Job created, queued for processing | | `running` | Job is being processed | | `completed` | All pages processed successfully | | `partially_completed` | Some pages succeeded, some failed | | `failed` | All pages failed or a job-level error occurred | | `rejected` | Job rejected before processing (e.g. validation or entitlement) | Terminal states are `completed`, `partially_completed`, `failed`, and `rejected`. ### Status Response ```json { "job_id": "019fb81e-2543-70bd-a737-b5a2d62a3bb7", "status": "completed", "pipeline": "digitise", "usage": { "pages_total": 1, "pages_processed": 1, "pages_succeeded": 1, "pages_failed": 0 }, "created_at": "2026-07-31T12:20:11Z", "updated_at": "2026-07-31T12:20:15Z" } ``` ## Error Handling #### Error Handling Code Example ```python import json, os from sarvamai import SarvamAI from sarvamai.core.api_error import ApiError client = SarvamAI(api_subscription_key=os.environ["SARVAM_API_KEY"]) try: with open("./insurance-policy.pdf", "rb") as f: job = client.doc_ai.extract( file=[("insurance-policy.pdf", f, "application/pdf")], schema=json.dumps({ "type": "object", "properties": { "policy_number": {"type": "string", "description": "Insurance policy number"}, }, }), output_format="json", ) # ... poll get_status() until terminal ... results = client.doc_ai.get_results(job_id=job.job_id) print(results.result) except ApiError as e: if e.status_code == 400: print(f"Bad request or invalid schema: {e.body}") elif e.status_code in (402, 403): print("Billing or entitlement error") elif e.status_code == 409: print("Results requested before the job reached a terminal state") elif e.status_code == 413: print("File too large") 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: ./insurance-policy.pdf") ``` ### Error Codes The full error-code table, retry guidance, and SDK exception reference live on the central [Errors & Troubleshooting](/api/getting-started/errors-troubleshooting) page. Errors specific to this API: | HTTP Status | Description | | ------------- | -------------------------------------------------------------------------- | | `400` | Invalid request or schema (includes documents exceeding the 10-page limit) | | `402` / `403` | Billing or entitlement error | | `404` | Resource unavailable (e.g. job not found) | | `409` | Results or download requested before the job reached a terminal state | | `413` | File too large | | `422` | Invalid file format or corrupted file | | `429` | Rate or admission limit | | `503` | Billing unavailable | ## Best Practices #### Digitise vs. Extract Reach for Digitise when you want the whole document, and Extract when you want specific fields. Don't post-process a full digitise just to pull three fields; define a schema and let Extract do it. #### Write Specific Descriptions In Extract, each field's `description` guides the model. "Insurance policy number, top-right, format ABC-1234" outperforms "policy number". #### Specify Language Always specify the correct language code for optimal accuracy, especially for Indian-language documents. #### Handle Large Documents Monitor `usage` in the status response to track progress, and handle `partially_completed` jobs gracefully. ## Limits | Limit | Value | | ----------------------- | ---------------------------------------------------------------------------------- | | Max pages per PDF | 10 (`400 invalid_request_error` 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/getting-started/ratelimits) | ## Next Steps #### [Sarvam Vision 1.5](/api/getting-started/models/sarvam-vision) Learn about the upgraded model powering Document AI. #### [API Reference](/api/api-guides-tutorials/document-intelligence/overview) Complete API documentation with all parameters and options. #### [Try in API Dashboard](https://dashboard.sarvam.ai) Upload and process documents in the API Dashboard. > Transform documents into structured, queryable data with Sarvam's Document AI API. Powered by Sarvam Vision 1.5 for text digitization and schema-based field extraction across 23 languages (22 Indian + English).