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

# Classification

> Build deterministic classifiers with enums and bounded explanation fields.

Classification is one of the simplest structured output tasks, but the details matter. An unconstrained classifier might return `"Billing"`, `"billing"`, `"billing issue"`, or `"BILLING"`, all meaning the same thing but breaking your routing rules, analytics aggregations, and dashboard filters. An enum constraint eliminates this by restricting the output to exactly the values your system recognizes.

Adding an `evidence` field turns a bare label into an actionable decision. Evidence gives reviewers the specific input observations that drove the classification, so they can verify quickly instead of re-reading the full ticket.

## Goal

Classify support tickets into a routing queue with a label, priority, and grounded evidence.

## Schema contract

<CodeGroup>
  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "oneOf": [
      {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "enum": ["billing", "technical", "account", "shipping"]
          },
          "priority": { "type": "string", "enum": ["low", "medium", "high", "urgent"] },
          "evidence": {
            "type": "array",
            "items": { "type": "string", "minLength": 10, "maxLength": 160 },
            "minItems": 1,
            "maxItems": 3
          }
        },
        "required": ["label", "priority", "evidence"],
        "additionalProperties": false
      },
      {
        "type": "object",
        "properties": {
          "label": { "type": "string", "const": "other" },
          "other_label": { "type": "string", "minLength": 3, "maxLength": 60 },
          "priority": { "type": "string", "enum": ["low", "medium", "high", "urgent"] },
          "evidence": {
            "type": "array",
            "items": { "type": "string", "minLength": 10, "maxLength": 160 },
            "minItems": 1,
            "maxItems": 3
          }
        },
        "required": ["label", "other_label", "priority", "evidence"],
        "additionalProperties": false
      }
    ]
  }
  ```

  ```python Pydantic theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from typing import Annotated, Literal
  from pydantic import BaseModel, ConfigDict, Field

  EvidenceItem = Annotated[str, Field(min_length=10, max_length=160)]

  class BaseClassification(BaseModel):
      model_config = ConfigDict(extra="forbid")
      priority: Literal["low", "medium", "high", "urgent"]
      evidence: list[EvidenceItem] = Field(..., min_length=1, max_length=3)

  class KnownClassification(BaseClassification):
      label: Literal["billing", "technical", "account", "shipping"]

  class OtherClassification(BaseClassification):
      label: Literal["other"]
      other_label: str = Field(..., min_length=3, max_length=60)

  Classification = Annotated[KnownClassification | OtherClassification, Field(discriminator="label")]
  ```

  ```typescript Zod theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  import { z } from "zod";

  const common = {
    priority: z.enum(["low", "medium", "high", "urgent"]),
    evidence: z.array(z.string().min(10).max(160)).min(1).max(3),
  };

  const classificationSchema = z.discriminatedUnion("label", [
    z.object({ label: z.enum(["billing", "technical", "account", "shipping"]), ...common }).strict(),
    z.object({ label: z.literal("other"), other_label: z.string().min(3).max(60), ...common }).strict(),
  ]);
  ```

  ```bash curl theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  curl https://api.dottxt.ai/v1/chat/completions \
    -H "Authorization: Bearer $DOTTXT_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "openai/gpt-oss-20b",
      "messages": [{ "role": "user", "content": "My card was charged twice for order ORD-9842. Need refund today." }],
      "response_format": {
        "type": "json_schema",
        "json_schema": {
          "name": "ticket_classification",
          "schema": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "label": { "type": "string", "enum": ["billing", "technical", "account", "shipping"] },
                  "priority": { "type": "string", "enum": ["low", "medium", "high", "urgent"] },
                  "evidence": { "type": "array", "items": { "type": "string", "minLength": 10, "maxLength": 160 }, "minItems": 1, "maxItems": 3 }
                },
                "required": ["label", "priority", "evidence"],
                "additionalProperties": false
              },
              {
                "type": "object",
                "properties": {
                  "label": { "type": "string", "const": "other" },
                  "other_label": { "type": "string", "minLength": 3, "maxLength": 60 },
                  "priority": { "type": "string", "enum": ["low", "medium", "high", "urgent"] },
                  "evidence": { "type": "array", "items": { "type": "string", "minLength": 10, "maxLength": 160 }, "minItems": 1, "maxItems": 3 }
                },
                "required": ["label", "other_label", "priority", "evidence"],
                "additionalProperties": false
              }
            ]
          }
        }
      }
    }'
  ```
</CodeGroup>

## Example input

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
My card was charged twice for order ORD-9842. Need refund today.
```

## Example output

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "label": "billing",
  "priority": "high",
  "evidence": [
    "User reports duplicate card charge.",
    "Explicit refund request indicates financial impact."
  ]
}
```

## Implementation tips

* **Enum labels are non-negotiable.** Every label value should map directly to a routing queue, a dashboard filter, or a database category. If the label isn't in the enum, it can't reach your system.
* **Evidence grounds the decision.** The bounded evidence array (1-3 items, 10-160 characters each) forces the model to cite specific observations from the input. This makes quality audits fast: reviewers check evidence against the ticket text instead of guessing why the model chose a label.
* **Fallback for taxonomy drift.** The second `oneOf` branch requires `other_label` when `label` is `"other"`. This captures novel categories without polluting your main enum. Review `other_label` values periodically and promote frequent ones into the enum. See [Enum with Fallback](/json-schema/enum-with-fallback).

## Related docs

* [Enum with fallback](/json-schema/enum-with-fallback): handle novel categories without polluting your main enum
* [Unions of objects](/json-schema/union-of-objects): discriminated unions for routing to different output shapes
* [Bounded arrays](/json-schema/bounded-arrays): control the size of evidence and tag arrays
* [String reference](/json-schema/reference/string) | [Conditionals reference](/json-schema/reference/conditionals)
