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

# Classify Document

> Lightweight routing step that runs **before** `/extract`. Given a raw document
(file upload or `file_url`) and a set of caller-defined classifications,
it returns which classification the document belongs to — plus that classification's
`pipeline_id`, so you can send the document to the right pipeline next.

By default, it evaluates the first five pages. For PDFs and images,
billing is based on the effective `page_range`. For Office and HTML
files, billing applies to every page.

Billed at **0.5 credits per page** — half the `/extract` rate.

Accepted document types are identical to `/extract`, including the same
size limits and URL validation.

Set `async: true` to return immediately with a job_id for polling via
GET /job/{jobId}. Otherwise processes synchronously.

## Overview

<Info>
  **Routing step (optional, runs before extraction)** — Classify takes a **raw document**, not a saved extraction. Use it to decide which pipeline (and therefore which [`/extract`](/api-reference/endpoint/extract) settings) a document should be routed to.
</Info>

`POST /classify` is a lightweight routing step that runs **before** `/extract`. Given a raw document and a set of caller-defined classifications, it returns which classification the document belongs to — plus that classification's `pipeline_id`, so you can send the document to the right pipeline next.

Why classify *before* extract: extraction settings (chunking, schema, tables, …) differ per document type, so you need to know the type before you extract. By default, `/classify` evaluates the first five pages and costs **0.5 credits per page** — half the `/extract` rate. See [Credits](#credits).

### Accepted Document Types

`/classify` accepts the same document formats and upload methods as [`/extract`](/api-reference/endpoint/extract), including direct file uploads and `file_url`. See [Supported File Types](/api-reference/supported-file-types).

| Category | Extensions                                 |
| -------- | ------------------------------------------ |
| PDF      | `.pdf`                                     |
| Images   | `.jpg`, `.jpeg`, `.png`, `.webp`           |
| Office   | `.docx`, `.pptx`, `.xlsx`, `.xlsm`, `.xls` |
| Web      | `.html`, `.htm`                            |

### Async Mode

Set `async: true` to return immediately with a job ID for polling. See [Polling for Results](/api-reference/endpoint/poll). On completion, the job's result carries the same body as the sync response.

***

## Request

Provide **either** `file` (multipart upload) **or** `file_url` (JSON body) — not both.

### Request Body

| Field             | Type    | Required | Description                                                                                                   |
| ----------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `file`            | file    | XOR      | The document to classify (multipart/form-data upload).                                                        |
| `file_url`        | string  | XOR      | URL of the document to classify (application/json body).                                                      |
| `classify_config` | object  | Yes      | Candidate classifications (JSON **string** when sent as a multipart form field).                              |
| `page_range`      | string  | No       | Pages to inspect, e.g. `"1-5"` or `"1,3,5-7"`. Default: first 5 pages, clamped to the document length.        |
| `async`           | boolean | No       | If `true`, returns immediately with a `job_id` for [polling](/api-reference/endpoint/poll). Default: `false`. |
| `parent_job_id`   | uuid    | No       | Optional parent job ID for tracking.                                                                          |

### Classify Config (`classify_config`)

```jsonc theme={null}
{
  "classifications": {
    "<name>": {
      "description": "string, required — what documents belong to this class",
      "pipeline_id": "string, optional — pipeline to route matches to"
    }
    // ... one entry per candidate classification
  }
}
```

* `<name>` is any label you choose; Pulse returns exactly one of these names.
* `description` tells Pulse what belongs in each class — make it specific and mutually distinct.
* `pipeline_id` is **optional routing metadata**. When provided, it must reference a pipeline your organization owns (otherwise the request is rejected with `PIPELINE_003`). The matched classification's `pipeline_id` is echoed back so you can route the document next.

***

## Response

### Synchronous Response (200)

| Field                            | Type         | Description                                                                  |
| -------------------------------- | ------------ | ---------------------------------------------------------------------------- |
| `classification`                 | string       | The winning classification name — one of your `classifications` keys         |
| `classify_output.classification` | string       | Same as `classification`                                                     |
| `classify_output.pipeline_id`    | uuid \| null | The matched classification's `pipeline_id`, or `null` if it had none         |
| `classify_output.page_range`     | string       | The effective page range used for classification (after defaulting/clamping) |
| `job_id`                         | uuid         | Identifier of the request                                                    |
| `credits_used`                   | number       | Credits deducted for this call                                               |

### Async Response (202)

| Field     | Type   | Description                                        |
| --------- | ------ | -------------------------------------------------- |
| `job_id`  | string | Job ID for [polling](/api-reference/endpoint/poll) |
| `status`  | string | `"pending"`                                        |
| `message` | string | Human-readable description                         |

***

## Example Usage

<CodeGroup>
  ```bash curl (file upload) theme={null}
  curl -X POST https://api.runpulse.com/classify \
    -H "x-api-key: YOUR_API_KEY" \
    -F "file=@document.pdf" \
    -F 'classify_config={
      "classifications": {
        "bank_statement": {
          "description": "Bank or account statements: balances, transaction lists, deposits and withdrawals.",
          "pipeline_id": "b1a2c3d4-..."
        },
        "invoice": {
          "description": "Invoices or bills requesting payment: line items, amounts due, payment terms.",
          "pipeline_id": "e5f6a7b8-..."
        },
        "other": {
          "description": "Any document that does not fit the other classifications."
        }
      }
    }'
  ```

  ```bash curl (file URL) theme={null}
  curl -X POST https://api.runpulse.com/classify \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "file_url": "https://example.com/document.pdf",
      "page_range": "1-5",
      "classify_config": {
        "classifications": {
          "bank_statement": {
            "description": "Bank or account statements: balances, transaction lists.",
            "pipeline_id": "b1a2c3d4-..."
          },
          "invoice": {
            "description": "Invoices or bills requesting payment.",
            "pipeline_id": "e5f6a7b8-..."
          },
          "other": {
            "description": "Any document that does not fit the other classifications."
          }
        }
      }
    }'
  ```

  ```python Python theme={null}
  import json
  import requests

  resp = requests.post(
      "https://api.runpulse.com/classify",
      headers={"x-api-key": "YOUR_API_KEY"},
      files={"file": open("document.pdf", "rb")},
      data={
          "classify_config": json.dumps({
              "classifications": {
                  "bank_statement": {
                      "description": "Bank or account statements: balances, transaction lists.",
                      "pipeline_id": "b1a2c3d4-...",
                  },
                  "invoice": {
                      "description": "Invoices or bills requesting payment.",
                      "pipeline_id": "e5f6a7b8-...",
                  },
                  "other": {
                      "description": "Any document that does not fit the other classifications."
                  },
              }
          })
      },
  )
  result = resp.json()
  print(result["classification"], result["classify_output"]["pipeline_id"])
  ```
</CodeGroup>

### Example Response

```json theme={null}
{
  "classification": "bank_statement",
  "classify_output": {
    "classification": "bank_statement",
    "pipeline_id": "b1a2c3d4-...",
    "page_range": "1-5"
  },
  "job_id": "e3b0c442-...",
  "credits_used": 2.5
}
```

***

## Routing to a Pipeline

`/classify` only tells you *which* pipeline to use — it does not execute it. Take the returned `pipeline_id` and run that pipeline with the same document; its own extraction settings then apply.

`classify` can also be used as the **first step of an ad-hoc pipeline**, where it classifies the raw document before the downstream steps run. It cannot be combined with `batch_extract` (classify needs a single document), and it is currently supported with inline config only.

***

## Credits

`/classify` is billed at **0.5 credits per page** — half the `/extract` rate (1 credit/page).

* **PDFs / images:** billed for the pages in the (defaulted/clamped) `page_range`.
* **Office / HTML:** billed for every page, even when `page_range` is smaller.

See the full rate table in [Credit Usage](/api-reference/introduction#credit-usage).

***

## Error Responses

| Status | Error               | Description                                                                    |
| ------ | ------------------- | ------------------------------------------------------------------------------ |
| 400    | `REQ_004`           | Missing `classify_config` or empty `classifications`                           |
| 400    | `REQ_002`           | `classify_config` is not valid JSON                                            |
| 400    | `REQ_006`           | Invalid or out-of-range `page_range`                                           |
| 400    | `PIPELINE_003`      | A classification's `pipeline_id` doesn't exist for your organization           |
| 400    | `FILE_*`            | Unsupported file type, file too large, or bad/blocked URL (same as `/extract`) |
| 401    | Unauthorized        | Invalid or missing API key                                                     |
| 429    | Rate limit exceeded | Too many requests                                                              |

***

## Best Practices

<AccordionGroup>
  <Accordion title="Include a catch-all classification">
    Add an entry like `"other"` with a description such as "Any document that does not fit the other classifications" so unexpected documents don't get force-fitted into a wrong class.
  </Accordion>

  <Accordion title="Write specific, mutually distinct descriptions">
    The description tells Pulse what belongs in each class. Describe what the documents contain, not just their name, and make classes clearly distinguishable from each other.
  </Accordion>

  <Accordion title="Keep the default page range unless you have a reason not to">
    The first 5 pages are usually enough to identify a document type. An explicit `page_range` has no hard cap, but large ranges scale credits linearly.
  </Accordion>

  <Accordion title="Treat the result as routing guidance">
    Classification is a probabilistic hint, not a guarantee. Validate downstream where correctness matters. Nothing from `/classify` is persisted — the routed pipeline's `/extract` produces the durable output.
  </Accordion>
</AccordionGroup>


## OpenAPI

````yaml POST /classify
openapi: 3.1.0
info:
  title: Pulse API Structure
  version: 0.1.0
  description: >-
    Canonical contract for the Pulse extraction APIs. This specification is the
    single source of truth for shared request/response models that client and
    server packages consume.
servers:
  - url: https://api.runpulse.com
    description: Default Pulse API base URL
security:
  - ApiKey: []
paths:
  /classify:
    post:
      tags:
        - Classify
      summary: Classify a document to route it to a pipeline
      description: >-
        Lightweight routing step that runs **before** `/extract`. Given a raw
        document

        (file upload or `file_url`) and a set of caller-defined classifications,

        it returns which classification the document belongs to — plus that
        classification's

        `pipeline_id`, so you can send the document to the right pipeline next.


        By default, it evaluates the first five pages. For PDFs and images,

        billing is based on the effective `page_range`. For Office and HTML

        files, billing applies to every page.


        Billed at **0.5 credits per page** — half the `/extract` rate.


        Accepted document types are identical to `/extract`, including the same

        size limits and URL validation.


        Set `async: true` to return immediately with a job_id for polling via

        GET /job/{jobId}. Otherwise processes synchronously.
      operationId: classifyDocument
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/ClassifyInput'
            encoding:
              classify_config:
                contentType: application/json
          application/json:
            schema:
              $ref: '#/components/schemas/ClassifyInput'
      responses:
        '200':
          description: Classification result (when async=false or omitted)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClassifyResponse'
        '202':
          description: Classify job accepted (when async=true)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AsyncSubmissionResponse'
        '400':
          description: >-
            Invalid request parameters, unsupported file type, or a
            classification references a pipeline_id that does not exist for your
            organization
        '401':
          description: Authentication failed or missing API key
        '429':
          description: Rate limit exceeded
components:
  schemas:
    ClassifyInput:
      type: object
      description: |-
        Request body for classifying a document. Provide exactly one of
        `file` (multipart upload) or `file_url` (JSON body). In
        multipart/form-data requests, `classify_config` is sent as a JSON
        string form field.
      required:
        - classify_config
      properties:
        file:
          type: string
          format: binary
          description: >-
            Document to upload directly (multipart/form-data only). Required
            unless file_url is provided.
        file_url:
          type: string
          format: uri
          description: >-
            Public or pre-signed URL that Pulse will download and classify.
            Required unless file is provided.
        classify_config:
          allOf:
            - $ref: '#/components/schemas/ClassifyConfig'
          description: Candidate classifications the document is matched against.
        page_range:
          type: string
          description: >-
            Pages to inspect, e.g. "1-5" or "1,3,5-7". Defaults to the first 5
            pages, clamped to the document length. For Office/HTML formats,
            every page is billed.
        async:
          type: boolean
          default: false
          description: >-
            If true, returns immediately with a job_id for polling via GET
            /job/{jobId}. Otherwise processes synchronously.
        parent_job_id:
          type: string
          format: uuid
          description: Optional parent job id for tracking.
    ClassifyResponse:
      type: object
      description: Result of document classification.
      required:
        - classification
        - classify_output
      properties:
        classification:
          type: string
          description: >-
            The winning classification name — one of the keys from the request's
            classifications object.
        classify_output:
          description: Details of the winning classification.
          allOf:
            - $ref: '#/components/schemas/ClassifyOutput'
        job_id:
          type: string
          format: uuid
          description: Identifier of the request for tracking and support.
        credits_used:
          type: number
          format: float
          nullable: true
          description: Number of credits consumed by this request.
    AsyncSubmissionResponse:
      type: object
      description: >-
        Acknowledgement returned when a request is submitted for asynchronous
        processing. Poll `GET /job/{job_id}` to check status and retrieve
        results.
      required:
        - job_id
        - status
      properties:
        job_id:
          type: string
          description: Identifier assigned to the asynchronous job.
        status:
          type: string
          description: Initial status reported by the server.
          enum:
            - pending
            - processing
            - completed
            - failed
            - canceled
        message:
          type: string
          description: Human-readable description of the accepted job.
        queuedAt:
          type: string
          format: date-time
          deprecated: true
          description: >-
            **Deprecated** — Timestamp indicating when the job was accepted.
            Retained for backward compatibility. Use `GET /job/{jobId}` for
            timing details.
        credits_used:
          type: number
          format: float
          nullable: true
          description: >-
            Number of credits consumed by this request. Only present when the
            organization has the credit billing system enabled.
    ClassifyConfig:
      type: object
      description: >-
        Candidate classifications, keyed by a caller-chosen name. Pulse returns
        exactly one of these names.
      required:
        - classifications
      properties:
        classifications:
          type: object
          minProperties: 1
          description: >-
            Mapping of classification names to their definitions. Include a
            catch-all entry (e.g. "other") for documents that fit nothing else.
          additionalProperties:
            $ref: '#/components/schemas/ClassificationDefinition'
    ClassifyOutput:
      type: object
      description: Details of the winning classification.
      properties:
        classification:
          type: string
          description: >-
            The winning classification name — one of the keys from the request's
            classifications object.
        pipeline_id:
          type: string
          format: uuid
          nullable: true
          description: >-
            The matched classification's pipeline_id, or null if it had none.
            Run this pipeline with the same document as the next step.
        page_range:
          type: string
          description: >-
            The effective page range used for classification, after defaulting
            and clamping to the document length.
    ClassificationDefinition:
      type: object
      description: A candidate classification a document may belong to.
      required:
        - description
      properties:
        description:
          type: string
          description: >-
            What documents belong to this classification. Make it specific and
            mutually distinct from the other classifications.
        pipeline_id:
          type: string
          format: uuid
          description: >-
            Optional routing metadata. When provided, it must reference a
            pipeline your organization owns. The matched classification's
            pipeline_id is echoed back in the response so you can route the
            document next.
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: x-api-key
      x-fern-header:
        name: apiKey
        env: PULSE_API_KEY

````