> 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 extract fields from a document

> Pull the fields you define from a document as structured JSON, CSV, or XLSX, using an inline schema or a saved config_id.

Extract is schema-based key-value extraction. You define the fields you want and Sarvam Vision returns them as structured data. Use it for KYC, invoices, forms, and other structured data capture where you want **specific fields**, not the whole page.

|                 |                                                |
| --------------- | ---------------------------------------------- |
| **You provide** | A document + a schema (or a saved `config_id`) |
| **Endpoint**    | `POST /doc-ai/v1/job/extract`                  |
| **Output**      | The fields you define, as JSON, CSV, or XLSX   |
| **Page limit**  | 20 pages per PDF or ZIP                        |

## Quick start

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.

#### Submit the job

Send the document with a schema describing the fields you want (or a saved `config_id`). You get a `job_id` back right away.

#### Python

```python
import json, os
from sarvamai import SarvamAI

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"},
    },
}

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.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 });

// 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" },
  },
};

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.job_id, job.status); // status starts as "pending"
```

#### cURL

```bash
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": "<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
```

#### Get the extracted fields

When the job is `completed` or `partially_completed`, fetch the field values.

#### Python

```python
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": ...}
else:
    print("job did not complete:", st.status)
```

#### JavaScript

```javascript
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: ... }
} else {
  console.log("job did not complete:", st.status);
}
```

#### cURL

```bash
curl https://api.sarvam.ai/doc-ai/v1/job/<JOB_ID>/results \
  -H "api-subscription-key: $SARVAM_API_KEY"
# {"result": {"policy_number": ..., "insured_name": ..., "sum_insured": ...}}
```

#### 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 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:
    # 1. Submit the job
    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.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"):
        results = client.doc_ai.get_results(job_id=job.job_id)
        print(results.result)  # {"policy_number": ..., "insured_name": ..., "sum_insured": ...}
    else:
        print("job did not complete:", st.status)
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 {
    // 1. Submit the job
    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.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 results = await client.docAi.getResults(job.job_id);
      console.log(results.result); // { policy_number: ..., insured_name: ..., sum_insured: ... }
    } else {
      console.log("job did not complete:", st.status);
    }
  } catch (err) {
    console.error("Extract job failed:", err.message ?? err);
  }
}

main();
```

For a one-page invoice and a schema with `invoice_number`, `customer_name`, and `total_due`, `results.result` is a flat object of your fields:

```json
{
  "invoice_number": "2001",
  "customer_name": "Asha Rao",
  "total_due": 4500
}
```

## Parameters

| Parameter       | Required                      | Default | Description                                                                                                                           |
| --------------- | ----------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `file`          | Yes                           |         | The document to process. See [Languages, formats & limits](/api/api-guides-tutorials/document-intelligence/languages-formats-limits). |
| `schema`        | One of `schema` / `config_id` |         | The fields to extract, as a JSON **string**. See [Schema rules](#schema-rules).                                                       |
| `config_id`     | One of `schema` / `config_id` |         | A saved schema to reuse. See [Reuse a schema](#reuse-a-schema).                                                                       |
| `language`      | No                            | Indic   | A BCP-47 code such as `en-IN` or `hi-IN`. Always pass the correct code for best accuracy.                                             |
| `output_format` | No                            | `json`  | `json`, `csv`, or `xlsx`.                                                                                                             |

> **Warning**
>
> **Provide exactly one of `schema` or `config_id`.** Sending both, or neither, returns a `400`.

### Common gotchas

* **`schema` is a JSON string, not an object.** It is sent as a multipart form field. 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'`.
* **The JavaScript SDK uses wire names, not camelCase.** Parameters and response fields are `output_format`, `job_id`, and `pages_processed`.
* **`file` takes an array** in both SDKs.
* **`job_id` is positional in JavaScript:** `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.

## Write specific descriptions

Each field's `description` guides the model. A specific description outperforms a generic one:

| Weak            | Strong                                                |
| --------------- | ----------------------------------------------------- |
| `policy number` | `Insurance policy number, top-right, format ABC-1234` |
| `date`          | `Date the policy was issued, as YYYY-MM-DD`           |
| `amount`        | `Total sum insured, in INR, digits only`              |

## Reuse a schema

To reuse the same fields across many calls without re-sending the schema, save it as a Config and pass its `config_id` in place of `schema`.

1. In the [Doc Agents](/docai/getting-started/overview) dashboard at [indus.sarvam.ai](https://indus.sarvam.ai/akshar/dashboard?ref=docs), start an Extract project, upload a document, and describe the fields you want.
2. When the project finishes, open the **Config** tab, adjust the schema, and click **Save**.
3. Copy the `config_id` shown on the saved Config (a UUID). Alternatively, click **Deploy as API endpoint** for a ready-made cURL command with the `config_id` filled in.

Then send it with your document:

```python
job = client.doc_ai.extract(
    file=[("insurance-policy.pdf", f, "application/pdf")],
    config_id="019ff577-91b7-7dde-b128-c5f418d06881",
    language="en-IN",
    output_format="json",
)
```

## Output formats

| Format | `output_format` value | Description                               |
| ------ | --------------------- | ----------------------------------------- |
| JSON   | `json`                | Structured field values (default)         |
| CSV    | `csv`                 | Flat tabular output, one row per document |
| XLSX   | `xlsx`                | Spreadsheet output                        |

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

## Next steps

* [Jobs & Errors](/api/api-guides-tutorials/document-intelligence/job-lifecycle): job states, polling, and error handling.
* [How to classify](/api/api-guides-tutorials/document-intelligence/how-to/classify): route documents to the right schema by type.