Document AI Overview

View as Markdown

Sarvam’s Document AI API provides enterprise-grade document processing powered by Sarvam Vision 1.5, 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.

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 or Markdown. 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.

DigitiseExtract
OutputFull text + layout (HTML or Markdown, plus JSON page data)Just the fields you define (JSON / CSV / XLSX)
You provideA documentA document + a schema (or saved config_id)
Best forArchival, search, RAG ingestion, reading-order-faithful conversionKYC, invoices, forms, structured data capture
EndpointPOST /doc-ai/v1/job/digitisePOST /doc-ai/v1/job/extract

What’s new

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; and (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() / 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 or Markdown.

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:

LanguageCodeScript
Hindihi-INDevanagari
Bengalibn-INBengali
Tamilta-INTamil
Telugute-INTelugu
Marathimr-INDevanagari
Gujaratigu-INGujarati
Kannadakn-INKannada
Malayalamml-INMalayalam
Odiaod-INOdia
Punjabipa-INGurmukhi
Englishen-INLatin

Supported Input Formats

FormatExtensionDescription
PDF.pdfMulti-page PDF documents (max 10 pages)
PNG.pngDocument page images
JPEG.jpg, .jpegDocument page images
ZIP.zipFlat 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 returns a 400 Bad Request with code invalid_request_error. 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 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) or md (Markdown).

JSON page-level data is always included by default. Whether you choose HTML or Markdown, a JSON file with structured page-level data is always included alongside it, so there is no json option here — passing one returns 400 OUTPUT_FORMAT_INVALID. (Extract does accept json; see below.)

Formatoutput_format valueDescription
HTMLhtmlRich HTML output for web rendering + JSON page data (default)
MarkdownmdHuman-readable Markdown output + JSON page data

The download is a ZIP archive holding the primary file, a metadata/page_NNN.json per page, and a manifest.json.

Extract

Formatoutput_format valueDescription
JSONjsonStructured field values (default)
CSVcsvFlat tabular output, one row per document
XLSXxlsxSpreadsheet output

Quick Start — Digitise

API parameter names: Document AI uses language and output_format values "md", "html", or "json". Do not use language_code (ignored) or "markdown" (returns 400 — use "md"). This differs from STT, translate, and LID endpoints.

1import os, time
2from sarvamai import SarvamAI
3
4client = SarvamAI(api_subscription_key=os.environ["SARVAM_API_KEY"])
5
6# Create and submit a digitise job in one call
7with open("/tmp/test-doc.pdf", "rb") as f:
8 job = client.doc_ai.digitise(
9 file=[("test-doc.pdf", f, "application/pdf")],
10 language="en-IN",
11 output_format="md",
12 )
13print("job created:", job.job_id, "| status:", job.status)
14
15# Poll until the job reaches a terminal state
16TERMINAL = {"completed", "partially_completed", "failed", "rejected"}
17while True:
18 st = client.doc_ai.get_status(job_id=job.job_id)
19 print(f"status={st.status} pages={st.usage.pages_processed}/{st.usage.pages_total}")
20 if st.status.lower() in TERMINAL:
21 break
22 time.sleep(5)
23
24# Fetch the output
25if st.status.lower() in ("completed", "partially_completed"):
26 dl = client.doc_ai.get_download_url(job_id=job.job_id)
27 print("download:", dl.method, dl.url)

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.

1import json, os, time
2from sarvamai import SarvamAI
3
4client = SarvamAI(api_subscription_key=os.environ["SARVAM_API_KEY"])
5
6# Define the fields you want back
7schema = {
8 "type": "object",
9 "properties": {
10 "policy_number": {"type": "string", "description": "Insurance policy number"},
11 "insured_name": {"type": "string", "description": "Name of the insured person"},
12 "sum_insured": {"type": "number", "description": "Total sum insured, in INR"},
13 },
14}
15
16# Create and submit an extract job in one call
17with open("/tmp/policy.pdf", "rb") as f:
18 job = client.doc_ai.extract(
19 file=[("policy.pdf", f, "application/pdf")],
20 schema=json.dumps(schema), # or: config_id="cfg_abc123"
21 language="en-IN",
22 output_format="json",
23 )
24print("job created:", job.job_id, "| status:", job.status)
25
26# Poll until the job reaches a terminal state
27TERMINAL = {"completed", "partially_completed", "failed", "rejected"}
28while True:
29 st = client.doc_ai.get_status(job_id=job.job_id)
30 print(f"status={st.status} pages={st.usage.pages_processed}/{st.usage.pages_total}")
31 if st.status.lower() in TERMINAL:
32 break
33 time.sleep(5)
34
35# Fetch the extracted fields
36if st.status.lower() in ("completed", "partially_completed"):
37 results = client.doc_ai.get_results(job_id=job.job_id)
38 print(results.result) # {"policy_number": ..., "insured_name": ..., "sum_insured": ...}

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.

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

StateDescription
pendingJob created, queued for processing
runningJob is being processed
completedAll pages processed successfully
partially_completedSome pages succeeded, some failed
failedAll pages failed or a job-level error occurred
rejectedJob rejected before processing (e.g. validation or entitlement)

Terminal states are completed, partially_completed, failed, and rejected.

Status Response

1{
2 "job_id": "019fb81e-2543-70bd-a737-b5a2d62a3bb7",
3 "status": "completed",
4 "pipeline": "digitise",
5 "usage": {
6 "pages_total": 1,
7 "pages_processed": 1,
8 "pages_succeeded": 1,
9 "pages_failed": 0
10 },
11 "created_at": "2026-07-31T12:20:11Z",
12 "updated_at": "2026-07-31T12:20:15Z"
13}

Error Handling

1import json, os
2from sarvamai import SarvamAI
3from sarvamai.core.api_error import ApiError
4
5client = SarvamAI(api_subscription_key=os.environ["SARVAM_API_KEY"])
6
7try:
8 with open("policy.pdf", "rb") as f:
9 job = client.doc_ai.extract(
10 file=[("policy.pdf", f, "application/pdf")],
11 schema=json.dumps({
12 "type": "object",
13 "properties": {
14 "policy_number": {"type": "string", "description": "Insurance policy number"},
15 },
16 }),
17 output_format="json",
18 )
19
20 # ... poll get_status() until terminal ...
21
22 results = client.doc_ai.get_results(job_id=job.job_id)
23 print(results.result)
24
25except ApiError as e:
26 if e.status_code == 400:
27 print(f"Bad request or invalid schema: {e.body}")
28 elif e.status_code in (402, 403):
29 print("Billing or entitlement error")
30 elif e.status_code == 409:
31 print("Results requested before the job reached a terminal state")
32 elif e.status_code == 413:
33 print("File too large")
34 elif e.status_code == 429:
35 print("Rate limit exceeded")
36 else:
37 print(f"Error {e.status_code}: {e.body}")
38except FileNotFoundError:
39 print("Document file not found")

Error Codes

The full error-code table, retry guidance, and SDK exception reference live on the central Errors & Troubleshooting page. Errors specific to this API:

HTTP StatusDescription
400Invalid request or schema (includes documents exceeding the 10-page limit)
402 / 403Billing or entitlement error
404Resource unavailable (e.g. job not found)
409Results or download requested before the job reached a terminal state
413File too large
422Invalid file format or corrupted file
429Rate or admission limit
503Billing unavailable

Best Practices

Digitise vs Extract

Reach for Digitise when you want the whole document; reach for 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

LimitValue
Max pages per PDF10 (400 invalid_request_error if exceeded)
Max images per ZIP10
Max file size200 MB
Supported input formatsPDF, PNG, JPG, ZIP
Rate limit10 requests/minute (all plans) — see Rate Limits

Next Steps