> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.datalab.to/llms.txt
> Use this file to discover all available pages before exploring further.

# Document Segmentation

> Automatically split multi-document PDFs into separate segments.

Automatically identify and split PDFs that contain multiple documents (like batch-scanned files) into their component parts.

**Before you begin**, make sure you have:

1. A [Datalab account](https://www.datalab.to/auth/sign_up) with an [API key](https://www.datalab.to/app/keys) (new accounts include \$5 in free credits)
2. Python 3.10+ installed
3. The Datalab SDK: `pip install datalab-python-sdk`
4. Your `DATALAB_API_KEY` environment variable set

<Info>
  EU requests return a signed `result_url`; the REST examples below download it before reading the output. See [Regional Result Downloads](/docs/recipes/conversion/conversion-api-overview#regional-result-downloads), including the Python SDK 0.5.0 limitation.
</Info>

<Info>
  **Building for production?** Use [Pipelines](/docs/recipes/pipelines/pipeline-overview) to chain processors, version your configuration, and deploy with a single API call.
</Info>

## Choose Page or Block Boundaries

| Request | Output | Standard base rate |
| - | - | - |
| `document_boundary` with `granularity="page"` | Original, 0-indexed page ranges without parsed text | \$0.50 / 1K pages |
| `granularity="block"` | Boundaries at OCR blocks, plus parsed document output | Convert rate + \$0.50 / 1K pages |
| Granularity omitted | Page boundaries and parsed document output | Convert rate + \$0.50 / 1K pages |

With fast or balanced parsing, the standard block-level base rate is \$4.50 / 1K pages. Accurate parsing costs more. See [Billing](/platform/billing) for regional and account pricing.

<Warning>
  Set `granularity="page"` explicitly to skip parsing. `segmentation_strategy="document_boundary"` alone still produces parsed output and incurs conversion charges. Page-only mode does not accept named `segments` or a custom `prompt`.
</Warning>

## Quick Start

This example requests page boundaries without parsing the text.

<CodeGroup>
  ```python Python SDK theme={null}
  import json
  from datalab_sdk import DatalabClient, SegmentOptions

  client = DatalabClient()

  # Define segmentation schema
  segmentation_schema = {
      "segmentation_strategy": "document_boundary",
      "granularity": "page"
  }

  options = SegmentOptions(
      segmentation_schema=json.dumps(segmentation_schema)
  )

  result = client.segment("combined_documents.pdf", options=options)

  # Access segmentation results
  for segment in result.segmentation_results["segments"]:
      print(f"{segment['name']}: pages {segment['pages']}")
  ```

  ```bash cURL theme={null}
  curl -X POST https://www.datalab.to/api/v1/segment \
    -H "X-API-Key: YOUR_API_KEY" \
    -F "file=@combined_documents.pdf" \
    -F 'segmentation_schema={"segmentation_strategy":"document_boundary","granularity":"page"}'
  ```

  ```python Python (requests) theme={null}
  import requests
  import json
  import time

  API_KEY = "YOUR_API_KEY"
  headers = {"X-API-Key": API_KEY}

  # Submit segmentation request
  with open("combined.pdf", "rb") as f:
      response = requests.post(
          "https://www.datalab.to/api/v1/segment",
          files={"file": ("combined.pdf", f, "application/pdf")},
          data={
              "segmentation_schema": json.dumps({"segmentation_strategy": "document_boundary", "granularity": "page"})
          },
          headers=headers
      )

  check_url = response.json()["request_check_url"]

  # Poll for results
  while True:
      result = requests.get(check_url, headers=headers).json()

      if result.get("success") is False or result["status"] == "failed":
          raise RuntimeError(result.get("error", "Segmentation failed"))
      if result["status"] == "complete":
          if result.get("result_url"):
              download = requests.get(result["result_url"], timeout=60)
              download.raise_for_status()
              result = {**download.json(), **{k: v for k, v in result.items() if v is not None}}
          if result.get("success") is False:
              raise RuntimeError(result.get("error") or "Document processing failed")
          segments = result["segmentation_results"]["segments"]
          for seg in segments:
              print(f"{seg['name']}: pages {seg['pages']}")
          break

      time.sleep(2)
  ```
</CodeGroup>

## When to Use

Segmentation is useful when:

* Batch-scanned documents are combined into a single PDF
* Multiple document types are stapled together
* You need to apply different processing to different sections

## Block-Level Segmentation

Use block boundaries when multiple documents share a page. The parsed output belongs to the whole document; use each segment's `start_block`, `end_block`, and `blocks` references to locate its content. Splitting only by `pages` can include content from neighboring segments.

<CodeGroup>
  ```python Python SDK theme={null}
  import json
  from datalab_sdk import DatalabClient, SegmentOptions

  client = DatalabClient()
  options = SegmentOptions(
      segmentation_schema=json.dumps({
          "granularity": "block",
          "prompt": "Start a new segment at each invoice",
      }),
      mode="balanced",
  )
  result = client.segment("combined.pdf", options=options)
  for segment in result.segmentation_results["segments"]:
      print(segment["name"], segment["start_block"], segment["end_block"])
  ```

  ```bash cURL theme={null}
  curl -X POST https://www.datalab.to/api/v1/segment \
    -H "X-API-Key: $DATALAB_API_KEY" \
    -F "file=@combined.pdf" \
    -F "mode=balanced" \
    -F 'segmentation_schema={"granularity":"block","prompt":"Start a new segment at each invoice"}'

  # Poll request_check_url from the response until status is complete or failed.
  ```

  ```python Python (requests) theme={null}
  import json
  import os
  import time
  import requests

  headers = {"X-API-Key": os.environ["DATALAB_API_KEY"]}
  with open("combined.pdf", "rb") as file:
      response = requests.post(
          "https://www.datalab.to/api/v1/segment",
          headers=headers,
          files={"file": ("combined.pdf", file, "application/pdf")},
          data={"mode": "balanced", "segmentation_schema": json.dumps({
              "granularity": "block", "prompt": "Start a new segment at each invoice",
          })},
      )
  response.raise_for_status()
  check_url = response.json()["request_check_url"]
  while True:
      response = requests.get(check_url, headers=headers)
      response.raise_for_status()
      result = response.json()
      if result.get("success") is False or result.get("status") == "failed":
          raise RuntimeError(result.get("error", "Segmentation failed"))
      if result.get("status") == "complete":
          if result.get("result_url"):
              download = requests.get(result["result_url"], timeout=60)
              download.raise_for_status()
              result = {**download.json(), **{k: v for k, v in result.items() if v is not None}}
          if result.get("success") is False:
              raise RuntimeError(result.get("error") or "Document processing failed")
          print(result["segmentation_results"])
          break
      time.sleep(2)
  ```
</CodeGroup>

## Segmentation Schema

Pass a JSON-encoded object in `segmentation_schema`:

| Field | Type | Description |
| - | - | - |
| `segmentation_strategy` | string | `"document_boundary"` for automatic detection, or `"custom"` for named segments |
| `granularity` | string | `"page"` explicitly skips parsing; `"block"` permits boundaries within a page. Omitting it retains parsed output. |
| `segments` | array | Non-empty list of `{name, description}` objects for `"custom"`; omit for `"document_boundary"` |
| `prompt` | string | Boundary instructions for block segmentation; cannot be combined with `granularity="page"` |

## Response Format

Example page-boundary result:

```json theme={null}
{
  "segmentation_results": {
    "segments": [
      {
        "name": "Research Paper",
        "pages": [0, 1, 2],
        "confidence": "medium"
      },
      {
        "name": "Invoice",
        "pages": [3, 4],
        "confidence": "medium"
      }
    ],
    "metadata": {
      "total_pages": 5,
      "segmentation_method": "document_boundary"
    }
  }
}
```

## Process Each Segment

After page-level segmentation, process each segment separately. The example below handles names matching `Invoice` or `Contract`; adapt the routing to the names in your results.

```python theme={null}
import json
from datalab_sdk import DatalabClient, SegmentOptions, ExtractOptions

client = DatalabClient()

# First, get segments
seg_options = SegmentOptions(
    segmentation_schema=json.dumps({"segmentation_strategy": "document_boundary", "granularity": "page"})
)
result = client.segment("combined.pdf", options=seg_options)

# Process each segment with appropriate schema using the Extract API
extraction_schemas = {
    "Invoice": {
        "type": "object",
        "properties": {
            "invoice_number": {"type": "string"},
            "total": {"type": "number"}
        }
    },
    "Contract": {
        "type": "object",
        "properties": {
            "parties": {"type": "array", "items": {"type": "string"}},
            "effective_date": {"type": "string"}
        }
    }
}

extracted_data = []

for segment in result.segmentation_results["segments"]:
    segment_name = segment["name"]
    pages = segment["pages"]

    schema = extraction_schemas.get(segment_name)
    if schema:
        # Build page range string
        page_range = ",".join(str(p) for p in pages)

        options = ExtractOptions(
            page_schema=json.dumps(schema),
            page_range=page_range,
            mode="balanced"
        )

        seg_result = client.extract("combined.pdf", options=options)
        extracted_data.append({
            "name": segment_name,
            "pages": pages,
            "data": json.loads(seg_result.extraction_schema_json),
        })

print(extracted_data)
```

## Using Checkpoints

If you already converted a document with `save_checkpoint=True` using the [Convert API](/docs/recipes/conversion/conversion-api-overview), pass the `checkpoint_id` to `SegmentOptions` to skip re-parsing. This saves time and cost when running segmentation on a previously converted document.

```python theme={null}
from datalab_sdk import DatalabClient, ConvertOptions, SegmentOptions
import json

client = DatalabClient()

# Step 1: Convert and save checkpoint
convert_result = client.convert("combined.pdf", options=ConvertOptions(save_checkpoint=True))
checkpoint_id = convert_result.checkpoint_id

# Step 2: Segment using checkpoint (no re-parsing needed)
options = SegmentOptions(
    segmentation_schema=json.dumps({"segmentation_strategy": "document_boundary"}),
    checkpoint_id=checkpoint_id
)
result = client.segment(options=options)
```

## Custom Segmentation Schema

Define expected segment types with `name` and `description`. Named segmentation requires parsed content. Automatic segment names are generated from the document and are not guaranteed to match your application labels.

```python theme={null}
segmentation_schema = {
    "segmentation_strategy": "custom",
    "segments": [
        {"name": "invoice", "description": "Invoice or billing document"},
        {"name": "contract", "description": "Legal contract or agreement"},
        {"name": "receipt", "description": "Payment receipt"}
    ]
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Structured Extraction" icon="table" href="/docs/recipes/structured-extraction/api-overview">
    Extract structured data from document segments using JSON schemas.
  </Card>

  <Card title="Handling Long Documents" icon="file-lines" href="/docs/recipes/structured-extraction/handling-long-documents">
    Tips for TOC-based segmentation on documents with 50+ pages.
  </Card>

  <Card title="Document Conversion" icon="file-export" href="/docs/recipes/conversion/conversion-api-overview">
    Convert documents to Markdown, HTML, JSON, or chunks.
  </Card>

  <Card title="Pipelines" icon="workflow" href="/docs/recipes/pipelines/pipeline-overview">
    Chain processors into versioned, reusable pipelines.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.