> ## 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.

# Enum With Fallback

> Use controlled categories with an explicit `other` escape hatch.

Enums are the strongest constraint for categorical fields: the output must match one of the listed values exactly. But real-world taxonomies drift. A new product launches, a new issue type appears, and suddenly a significant fraction of inputs don't fit any existing category. If your enum has no escape hatch, the model is forced to pick the closest match, which introduces silent misclassification.

The fallback pattern adds an `"other"` value and models it as a separate union branch that requires a freeform description. This preserves the analytical benefits of a closed enum for known categories while capturing novel ones accurately.

## Use case

Issue triage where most tickets fit known categories (`billing`, `technical`, `account`, `shipping`), but some do not, and you need to capture what they actually are instead of forcing a bad fit.

## Schema pattern

<CodeGroup>
  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "oneOf": [
      {
        "type": "object",
        "properties": {
          "issue_type": {
            "type": "string",
            "enum": ["billing", "technical", "account", "shipping"]
          }
        },
        "required": ["issue_type"],
        "additionalProperties": false
      },
      {
        "type": "object",
        "properties": {
          "issue_type": { "type": "string", "const": "other" },
          "other_issue_type": { "type": "string", "minLength": 3, "maxLength": 60 }
        },
        "required": ["issue_type", "other_issue_type"],
        "additionalProperties": false
      }
    ]
  }
  ```

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

  class KnownIssue(BaseModel):
      model_config = ConfigDict(extra="forbid")
      issue_type: Literal["billing", "technical", "account", "shipping"]

  class OtherIssue(BaseModel):
      model_config = ConfigDict(extra="forbid")
      issue_type: Literal["other"]
      other_issue_type: str = Field(..., min_length=3, max_length=60)

  IssuePayload = Annotated[KnownIssue | OtherIssue, Field(discriminator="issue_type")]
  ```

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

  const issuePayloadSchema = z.discriminatedUnion("issue_type", [
    z.object({
      issue_type: z.enum(["billing", "technical", "account", "shipping"]),
    }).strict(),
    z.object({
      issue_type: z.literal("other"),
      other_issue_type: z.string().min(3).max(60),
    }).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 login stopped working after the latest app update." }],
      "response_format": {
        "type": "json_schema",
        "json_schema": {
          "name": "issue_triage",
          "schema": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "issue_type": { "type": "string", "enum": ["billing", "technical", "account", "shipping"] }
                },
                "required": ["issue_type"],
                "additionalProperties": false
              },
              {
                "type": "object",
                "properties": {
                  "issue_type": { "type": "string", "const": "other" },
                  "other_issue_type": { "type": "string", "minLength": 3, "maxLength": 60 }
                },
                "required": ["issue_type", "other_issue_type"],
                "additionalProperties": false
              }
            ]
          }
        }
      }
    }'
  ```
</CodeGroup>

## Example outputs

Known category:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "issue_type": "technical"
}
```

Fallback category:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "issue_type": "other",
  "other_issue_type": "partner-api-timeout"
}
```

## Why this works

For the vast majority of inputs, `issue_type` is one of the four known values. Your analytics dashboards, routing rules, and reports all work unchanged. When a genuinely novel category appears, the model selects `"other"` and the second `oneOf` branch requires `other_issue_type`, a bounded freeform string that captures what the issue actually is.

This gives you the best of both worlds: a closed enum for known categories (fast aggregation, reliable routing) and a structured escape hatch for new ones (no data loss, easy to review). Over time, you can promote frequently-seen `other_issue_type` values into the main enum.

## Related docs

* [Unions of objects](/json-schema/union-of-objects)
* [Object reference](/json-schema/reference/object)
* [Classification guide](/json-schema/classification)
