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

# Categorize

> Classify document segments and spreadsheet sheets against a taxonomy

Categorize splits a document into logical segments (or a spreadsheet into sheets) and classifies each against a taxonomy. Use it to route mixed uploads ("is this a bank statement, an invoice, or a lease?"), to split combined PDFs into their constituent documents, or to identify which sheets in a workbook contain what.

You can bring your own taxonomy or use NDI's default. Each classified segment also comes with a short summary and a markdown rendition of just that segment.

| Endpoint                 | Behavior                                                                |
| ------------------------ | ----------------------------------------------------------------------- |
| `POST /categorize`       | Starts the job and waits for the result (bounded by `timeout_seconds`). |
| `POST /categorize_async` | Starts the job and returns `202 {"job_id": …}` immediately.             |

## Request

<ParamField query="timeout_seconds" type="number" default="120">
  **Sync endpoint only.** Max seconds to wait, between 1 and 300.
</ParamField>

<ParamField body="file" type="object" required>
  The document to categorize. Same shape as everywhere else — `source_url`
  (+ `file_name` for `s3://`/`https://`). See [Files & sources](/concepts/files).
  Supported extensions are the same as [parse](/api-reference/parse).
</ParamField>

<ParamField body="taxonomy" type="object">
  The category vocabulary to classify against. **Omit to use NDI's default
  taxonomy** (general business-document categories).

  <Expandable title="taxonomy properties">
    <ParamField body="categories" type="array" required>
      At least one category.

      <Expandable title="category properties">
        <ParamField body="key" type="string" required>
          Stable category key you will branch on, e.g. `"general_ledger"`.
        </ParamField>

        <ParamField body="description" type="string">
          Shown to the classifier — a one-line description measurably improves
          accuracy. Write it like you would explain the category to a new
          colleague.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="markdown_url" type="string">
  `markdown_url` from a **prior parse of the same file** (an `s3://` URL
  returned by NDI). When present, categorize skips its internal parse and runs
  directly on the markdown — faster and cheaper if you already parsed the
  document.
</ParamField>

<ParamField body="idempotency_key" type="string">
  Up to 128 characters. See [Idempotency](/concepts/jobs#idempotency).
</ParamField>

## Result

<ResponseField name="result_type" type="&#x22;categorize&#x22;">
  Discriminator — always `"categorize"`.
</ResponseField>

<ResponseField name="status" type="&#x22;success&#x22; | &#x22;failed&#x22;">
  Outcome of the classification run; `error` carries detail on failure.
</ResponseField>

<ResponseField name="items" type="array">
  Classified units, tagged by `kind`. Documents yield `document_segment`
  entries (contiguous page ranges); spreadsheets yield `sheet` entries.

  <Expandable title="kind: &#x22;document_segment&#x22;">
    <ResponseField name="file_name" type="string">Source file name.</ResponseField>
    <ResponseField name="page_start" type="integer">First page of the segment (1-indexed).</ResponseField>
    <ResponseField name="page_end" type="integer">Last page of the segment (inclusive).</ResponseField>

    <ResponseField name="category" type="string">
      The assigned category key — one of your taxonomy keys, unless the
      classifier minted a free-form label (see `is_freeform_category`).
    </ResponseField>

    <ResponseField name="is_freeform_category" type="boolean">
      `true` when no taxonomy category fit and the classifier proposed its own
      label. Treat these as "unclassified" if you only accept your own keys.
    </ResponseField>

    <ResponseField name="summary" type="string">One-paragraph summary of the segment.</ResponseField>

    <ResponseField name="markdown_url" type="string | null">
      Markdown of just this segment (time-limited download URL).
    </ResponseField>
  </Expandable>

  <Expandable title="kind: &#x22;sheet&#x22;">
    <ResponseField name="file_name" type="string">Source file name.</ResponseField>
    <ResponseField name="sheet_name" type="string">Sheet name.</ResponseField>

    <ResponseField name="categories" type="string[]">
      Assigned category keys — a sheet can match several.
    </ResponseField>

    <ResponseField name="has_freeform_categories" type="boolean">
      `true` when any entry in `categories` is a free-form label rather than
      one of your taxonomy keys.
    </ResponseField>

    <ResponseField name="summary" type="string">One-paragraph summary of the sheet.</ResponseField>

    <ResponseField name="markdown_url" type="string | null">
      Markdown of the sheet (time-limited download URL).
    </ResponseField>
  </Expandable>
</ResponseField>

## Examples

<CodeGroup>
  ```bash Custom taxonomy theme={"dark"}
  curl -s "$NDI_BASE/categorize" \
    -H "X-API-Key: $NDI_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "file": { "source_url": "ndi://file/7f4d2a10-3b7e-4c25-9f61-8f0f4f0a1b2c" },
      "taxonomy": {
        "categories": [
          { "key": "bank_statement", "description": "Monthly statement from a bank listing transactions and balances." },
          { "key": "invoice", "description": "A bill issued to or by the company for goods or services." },
          { "key": "lease_agreement", "description": "A contract granting use of property or equipment for a period." },
          { "key": "general_ledger", "description": "Full ledger of journal entries by account." }
        ]
      },
      "idempotency_key": "categorize-batch-42"
    }'
  ```

  ```bash Default taxonomy (async) theme={"dark"}
  curl -s "$NDI_BASE/categorize_async" \
    -H "X-API-Key: $NDI_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "file": {
        "source_url": "https://example.com/mixed-upload.pdf",
        "file_name": "mixed-upload.pdf"
      }
    }'
  ```
</CodeGroup>

```json Result theme={"dark"}
{
  "job_id": "e2f3a4b5-6c7d-4e8f-9a0b-1c2d3e4f5a6b",
  "action": "categorize",
  "status": "succeeded",
  "result": {
    "result_type": "categorize",
    "status": "success",
    "error": null,
    "items": [
      {
        "kind": "document_segment",
        "file_name": "mixed-upload.pdf",
        "page_start": 1,
        "page_end": 3,
        "category": "bank_statement",
        "is_freeform_category": false,
        "summary": "Checking-account statement for December 2025 from First National Bank, listing 84 transactions and an ending balance of $1,204,331.",
        "markdown_url": "https://…(time-limited)…/segment-0.md"
      },
      {
        "kind": "document_segment",
        "file_name": "mixed-upload.pdf",
        "page_start": 4,
        "page_end": 4,
        "category": "invoice",
        "is_freeform_category": false,
        "summary": "Invoice #2025-1187 from Acme Facilities for Q4 maintenance services, $18,500 due January 15, 2026.",
        "markdown_url": "https://…(time-limited)…/segment-1.md"
      }
    ]
  },
  "error": null,
  "created_at": "2026-07-12T21:20:05Z",
  "started_at": "2026-07-12T21:20:06Z",
  "completed_at": "2026-07-12T21:20:38Z"
}
```
