> 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 classify documents

> Label a document, or each page, against categories you define, or let the API suggest a document type. Use it to route and triage files before processing.

Classify labels a whole document, or each page, against the categories you define. Without categories, it suggests a document type. Use it to **route or triage** documents before processing them.

|                 |                                                                    |
| --------------- | ------------------------------------------------------------------ |
| **You provide** | A document (+ categories, optional)                                |
| **Endpoint**    | `POST /doc-ai/v1/job/classify`                                     |
| **Output**      | A document type (or one per page), with evidence and a review flag |
| **Input**       | PDF, PNG, JPEG, TIFF; up to 500 pages and 512 MB per document      |

## Quick start

Classify takes the same document input, plus the **categories** you want to choose between. It labels the whole document, or each page. Without categories, it suggests a document type.

#### 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 classify job in one call
    with open("./sample-document.pdf", "rb") as f:
        job = client.doc_ai.classify(
            file=[("sample-document.pdf", f, "application/pdf")],
            request={
                "config": {
                    "scope": "page",
                    "categories": [
                        {"type": "Invoice", "description": "A supplier bill with totals and payment details."},
                        {"type": "Receipt", "description": "Proof of a completed payment."},
                    ],
                },
            },
        )
    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} phase={st.phase}")
        if st.status.lower() in TERMINAL:
            break
        time.sleep(5)

    # Fetch the decisions
    if st.status.lower() in ("completed", "partially_completed"):
        results = client.doc_ai.get_results(job_id=job.job_id)
        for d in results.result.decisions:
            print(d.input_page, d.type, d.classification_status, "review:", d.review_required)
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 classify job in one call
    const job = await client.docAi.classify({
      file: [fs.createReadStream("./sample-document.pdf")],
      request: {
        config: {
          scope: "page",
          categories: [
            { type: "Invoice", description: "A supplier bill with totals and payment details." },
            { type: "Receipt", description: "Proof of a completed payment." },
          ],
        },
      },
    });
    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} phase=${st.phase}`);
      if (TERMINAL.has(st.status.toLowerCase())) break;
      await new Promise((resolve) => setTimeout(resolve, 5000));
    }

    // Fetch the decisions
    if (["completed", "partially_completed"].includes(st.status.toLowerCase())) {
      const results = await client.docAi.getResults(job.job_id);
      for (const d of results.result.decisions) {
        console.log(d.input_page, d.type, d.classification_status, "review:", d.review_required);
      }
    }
  } catch (err) {
    console.error("Classify job failed:", err.message ?? err);
  }
}

main();
```

#### cURL

```bash
# Create and submit a classify job
curl -X POST https://api.sarvam.ai/doc-ai/v1/job/classify \
  -H "api-subscription-key: $SARVAM_API_KEY" \
  -F 'file=@./sample-document.pdf;type=application/pdf' \
  -F 'request={
    "config": {
      "scope": "page",
      "categories": [
        {"type": "Invoice", "description": "A supplier bill with totals and payment details."},
        {"type": "Receipt", "description": "Proof of a completed payment."}
      ]
    }
  }'
# {"job_id": "<JOB_ID>", "status": "accepted", ...}

# Poll until the job reaches a terminal state
curl https://api.sarvam.ai/doc-ai/v1/job/<JOB_ID>/status \
  -H "api-subscription-key: $SARVAM_API_KEY"
# {"status": "running", "phase": "read", ...}

# Once status is "completed", fetch the decisions
curl --compressed https://api.sarvam.ai/doc-ai/v1/job/<JOB_ID>/results \
  -H "api-subscription-key: $SARVAM_API_KEY"
# {"result": {"decisions": [{"input_page": 1, "type": "Invoice", ...}]}}
```

## Classify options

* `config.scope`: `document` (one decision for the file, the default) or `page` (one decision per page, returned in `decisions`).
* `config.categories`: up to 20 entries, each with a `type` and a `description`. Names are yours to choose and must be unique. Omit them to let the API suggest a document type.
* `config.unmatched_behavior`: `suggest_type` (the default) returns a `suggested_type` when no category fits; `other` returns `type: "other"` instead.
* `config.prompt`: your own instruction text, up to 8,000 characters.
* `output.include_evidence`: `true` returns the pages, text blocks, and region that support each decision.
* `language`: a BCP-47 code such as `en-IN`. Omit it for the Indic default, as for Parse.
* `reasoning_input`: `auto` (the default) or `text_only`.

Write each category `description` so it separates that category from the others, for example "A supplier bill with totals and payment details" rather than "financial document".

## Choose which pages to read

Use one of `config.max_pages` (the first N pages), `config.target_pages` (such as `[1, 6, 8]`), or `config.inspection`. Combining them is rejected.

| `inspection.policy` | Reads                                      | Example                                                                               |
| ------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------- |
| `first`             | The first N pages                          | `{"policy": "first", "count": 2}`                                                     |
| `last`              | The last N pages                           | `{"policy": "last", "count": 2}`                                                      |
| `head_tail`         | The beginning and the end                  | `{"policy": "head_tail", "head_pages": 2, "tail_pages": 1}`                           |
| `explicit`          | Page ranges you choose                     | `{"policy": "explicit", "ranges": [{"start": 1, "end": 2}]}`                          |
| `all`               | Every selected page                        | `{"policy": "all"}`                                                                   |
| `adaptive`          | Starts with a selection, expands if needed | `{"policy": "adaptive", "initial": {"policy": "first", "count": 2}, "max_pages": 10}` |

`count` must be at least 1. Page ranges are 1-based and inclusive. For `adaptive`, `initial` can be `first`, `last`, `head_tail`, or `explicit`, and `max_pages` must cover the initial selection.

> **Warning**
>
> Reading only some pages is faster, but a bundle of several documents can look like one. In testing, a PDF holding an invoice followed by two receipts classified as `Invoice` when only the first page was read, and as `mixed` when every page was read. Use `all`, or [split](/api/api-guides-tutorials/document-intelligence/how-to/split) the file first.

## Classify results

```json
{
  "job_id": "01a17998-120b-73dc-ad42-5cd66e0fe0ff",
  "type": "classify",
  "status": "completed",
  "result": {
    "scope": "document",
    "type": "Invoice",
    "classification_status": "matched",
    "confidence": 1,
    "reasoning": "The document is explicitly titled 'INVOICE #2001' and contains a total amount due.",
    "review_required": false,
    "review_reasons": [],
    "suggested_type": null
  },
  "usage": {"pages_processed": 2, "pages_source": 2}
}
```

| Field                   | Meaning                                                                                                                                 |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `type`                  | The matching category. `null` when the status is `unknown` or `mixed`; `other` when nothing matched and `unmatched_behavior` is `other` |
| `classification_status` | `matched` (a category fits), `unknown` (none fits), or `mixed` (the file holds several documents)                                       |
| `reason_code`           | On `unknown`: `no_matching_type`, or `insufficient_evidence` when the pages were not enough to tell                                     |
| `confidence`            | A token-based score from 0 to 1, or `null`. It is not a measured probability of being correct                                           |
| `review_required`       | Whether a person should check the answer. Low or missing confidence and unresolved answers require review                               |
| `review_reasons`        | For example `NO_MATCHING_TYPE`, `MODEL_SCORE_UNAVAILABLE`, or `MULTIPLE_DOCUMENTS_DETECTED`                                             |
| `suggested_type`        | A type you did not supply, with its `name` and supporting `evidence`. `null` when there is nothing to suggest                           |

When no category matches, the job still completes: you get `classification_status: "unknown"` and `reason_code: "no_matching_type"`, and, with `suggest_type`, the document's own type in `suggested_type`. With `scope: "page"`, `result` holds a `decisions` list with one entry per page instead of a single decision.

Results are short by default. Add `?include=details` to the results request for the inspected pages and provenance.

## Split, then classify

Classify reads the pages you point it at, so a bundle of several documents can look like one. [Split](/api/api-guides-tutorials/document-intelligence/how-to/split) first, then classify each child document by its `document_ref`. To classify one child document, send a JSON body instead of a file:

```bash
curl -X POST https://api.sarvam.ai/doc-ai/v1/job/classify \
  -H "api-subscription-key: $SARVAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input": {"document_ref": "doc_..."}, "config": {"scope": "document", "categories": [{"type": "Invoice", "description": "A supplier bill."}]}}'
```

> **Note**
>
> Classify shares its request behaviours (multiple documents, `Idempotency-Key`, `execution: "auto"`, capabilities) with Split. See [Request behaviours](/api/api-guides-tutorials/document-intelligence/how-to/split#request-behaviours).

## Next steps

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

Separate a bundle into documents first.

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

Job states, polling, and error handling.