> 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 split a document bundle

> Divide a bundle of pages into separate documents by fixed page groups, explicit ranges, markers such as QR codes or blank pages, or let the model find the boundaries.

Split divides a bundle of pages into separate documents. Use it when **one file holds several documents**, such as a scanned stack of invoices and receipts.

|                 |                                                                           |
| --------------- | ------------------------------------------------------------------------- |
| **You provide** | A document (+ a split method)                                             |
| **Endpoint**    | `POST /doc-ai/v1/job/split`                                               |
| **Output**      | Child documents, each with its original page numbers and a `document_ref` |
| **Input**       | PDF, PNG, JPEG, TIFF; up to 500 pages and 512 MB per document             |

## Quick start

Split takes the same document input, plus a **method** describing where one document ends and the next begins. The result lists the child documents, each with its original page numbers and a `document_ref` you can reuse in other requests.

#### 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 split job in one call
    with open("./sample-document.pdf", "rb") as f:
        job = client.doc_ai.split(
            file=[("sample-document.pdf", f, "application/pdf")],
            request={
                "config": {
                    "method": "fixed_pages",
                    "pages_per_segment": 2,
                    "classify_segments": False,
                },
            },
        )
    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 child documents
    if st.status.lower() in ("completed", "partially_completed"):
        results = client.doc_ai.get_results(job_id=job.job_id)
        for seg in results.result.segments:
            print(seg.pages, seg.document_ref, "review:", seg.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 split job in one call
    const job = await client.docAi.split({
      file: [fs.createReadStream("./sample-document.pdf")],
      request: {
        config: {
          method: "fixed_pages",
          pages_per_segment: 2,
          classify_segments: false,
        },
      },
    });
    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 child documents
    if (["completed", "partially_completed"].includes(st.status.toLowerCase())) {
      const results = await client.docAi.getResults(job.job_id);
      for (const seg of results.result.segments) {
        console.log(seg.pages, seg.document_ref, "review:", seg.review_required);
      }
    }
  } catch (err) {
    console.error("Split job failed:", err.message ?? err);
  }
}

main();
```

#### cURL

```bash
# Create and submit a split job
curl -X POST https://api.sarvam.ai/doc-ai/v1/job/split \
  -H "api-subscription-key: $SARVAM_API_KEY" \
  -F 'file=@./sample-document.pdf;type=application/pdf' \
  -F 'request={"config": {"method": "fixed_pages", "pages_per_segment": 2, "classify_segments": false}}'
# {"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 child documents
curl --compressed https://api.sarvam.ai/doc-ai/v1/job/<JOB_ID>/results \
  -H "api-subscription-key: $SARVAM_API_KEY"
# {"result": {"segments": [{"pages": [1, 2], "document_ref": "doc_...", ...}]}}
```

> **Note**
>
> **Send the options as a JSON string in `request`.** Split and Classify read their options from a `request` form field next to the file. In Python the SDK takes it as a dict (`request={...}`); with cURL, pass the JSON text. In Python, `file` takes a list and `job_id` is passed by name, `get_status(job_id=...)`. In JavaScript, `file` takes an array, `job_id` is positional, `getStatus(job_id)`, and request and response fields use the wire names (`pages_per_segment`, `review_required`), as for Parse and Extract.

## Split methods

Set `config.method` to one of:

| Method        | Use it when                                                         | Also needs                                  |
| ------------- | ------------------------------------------------------------------- | ------------------------------------------- |
| `semantic`    | The model should find where each document starts and ends (default) | `categories`, or `classify_segments: false` |
| `fixed_pages` | Every document has the same number of pages                         | `pages_per_segment`                         |
| `page_ranges` | You already know the boundaries                                     | `segment_ranges`                            |
| `triggers`    | Documents start at a marker such as a blank page or a line of text  | `split_on`                                  |

Other options:

* `classify_segments`: `true` (the default) labels each child document with one of your `categories`; `false` only splits. With `true`, `categories` is required. With `false`, sending `categories` is rejected.
* `categories`: up to 20 entries, each with a `type` and a `description`. Names are yours to choose and must be unique.
* `remainder`: for `fixed_pages`, `keep` (the default) keeps a shorter last group and `error` rejects the request when the pages do not divide evenly.
* `segment_ranges`: 1-based, inclusive ranges, such as `[{"start": 1, "end": 5}, {"start": 6, "end": 10}]`. They must cover the selected pages exactly once, in order.
* `pages`: add `"pages": [{"start": 1, "end": 2}]` to the `request` to split only those pages.

## Split at markers

With `triggers`, a new document starts at each page that matches a marker in `split_on`:

```json
{"config": {"method": "triggers", "split_on": [{"type": "text", "match": {"prefix": "Cover:"}}], "classify_segments": false}}
```

* `type`: `blank_page`, `text`, `qr_code`, `barcode`, or `photo`. A `photo` marker needs a `description` of the image.
* `match`: for `text`, `qr_code`, and `barcode`, exactly one of `equals`, `prefix`, or `regex`. These go **inside** `match`, not on the marker itself.
* Optional: `where` (`anywhere` or `top_of_page`), `trigger_page` (`first_page`, `separator_exclude`, or `separator_keep`), `split_when` (`every_match` or `value_changes`, for codes only), and `formats` for barcodes.

A blank separator page is left out of the segments (listed under `coverage.excluded_pages`). Pages before the first marker form a segment flagged `LEADING_PAGES_WITHOUT_TRIGGER`, and a file with no marker comes back as one segment flagged the same way. To label the segments, add `categories` and leave `classify_segments` on; with `classify_segments: false`, `categories` is rejected.

## Split results

```json
{
  "job_id": "01a17998-07ef-7040-800d-745a95140cf5",
  "type": "split",
  "status": "completed",
  "result": {
    "summary": {"documents": 2, "ready": 2, "needs_review": 0, "excluded_pages": 0, "unresolved_pages": 0},
    "review_required": false,
    "review_reasons": [],
    "coverage": {"complete": true, "excluded_pages": [], "unresolved_pages": []},
    "segments": [
      {
        "id": "seg_80d6fa828cfad12c7c13446b16bf81a0",
        "document_ref": "doc_80d6fa82...",
        "pages": [1, 2],
        "role": "document",
        "type": null,
        "classification_status": "not_requested",
        "review_required": false,
        "review_reasons": []
      }
    ]
  },
  "usage": {"pages_processed": 4, "pages_source": 4}
}
```

* Each segment is a child document. `pages` refers to its original page numbers, and `document_ref` lets you reuse it in another request.
* With `classify_segments: true`, `type` is the matching category and `classification_status` is `matched`, or `unknown` with `unknown_reason: "no_matching_type"` when none fits.
* `status: "completed"` means processing finished, not that every segment passed review. Check `review_required` on each segment before accepting the result automatically.

## Request behaviours

Split and [Classify](/api/api-guides-tutorials/document-intelligence/how-to/classify) share these request behaviours:

* **Inputs:** PDF, PNG, JPEG, or TIFF. Repeat the `file` field for up to 10 documents with the same config; you get a parent job and a `documents` list of individual jobs, each with its own `status_url` and `results_url`. The parent's results include each document's result under `documents`. You can also upload first with `POST /doc-ai/v1/job/upload` and send the id as `upload_ids`. URL input is not available.
* **Retries:** add an `Idempotency-Key` header to retry the same submission without creating another job. A changed request under the same key returns `409`.
* **Waiting:** send `"execution": "auto"` in `request` to wait briefly for a single document. A finished answer returns with HTTP `200`; otherwise you get the job to poll.
* **Capabilities:** `GET /doc-ai/v1/capabilities` lists the operations, input formats, split methods, and limits enabled on your deployment.

## Next steps

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

Label each child document by its `document_ref`.

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

Job states, polling, and error handling.