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

# Optional Fields

> Use optional fields intentionally so required data stays strict while extras remain flexible.

Not every field can always be extracted. Some information may be absent from the source text, or may only be available after a secondary enrichment step. Making all fields required forces the model to hallucinate values for missing data; making all fields optional means your downstream code can never trust that anything is present.

The right approach is to split fields into a required core (fields your application cannot function without) and optional enrichments (fields that add value when present but don't block processing when absent). Both groups stay typed and bounded; optional does not mean unconstrained.

## Use case

Lead qualification output where `lead_id`, `segment`, and `priority` are always needed for routing, but `company_size`, `tech_stack`, and `notes` are only available when the source data mentions them.

## Schema pattern

<CodeGroup>
  ```json JSON Schema theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "type": "object",
    "properties": {
      "lead_id": { "type": "string", "pattern": "^LEAD-[0-9]{4,10}$" },
      "segment": { "type": "string", "enum": ["smb", "mid_market", "enterprise"] },
      "priority": { "type": "string", "enum": ["low", "medium", "high"] },
      "company_size": { "type": "string", "enum": ["1-10", "11-50", "51-200", "201+"] },
      "tech_stack": {
        "type": "array",
        "items": { "type": "string", "minLength": 1, "maxLength": 40 },
        "maxItems": 10
      },
      "notes": { "type": "string", "maxLength": 300 }
    },
    "required": ["lead_id", "segment", "priority"],
    "additionalProperties": false
  }
  ```

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

  TechItem = Annotated[str, Field(min_length=1, max_length=40)]

  class LeadPayload(BaseModel):
      model_config = ConfigDict(extra="forbid")
      lead_id: str = Field(..., pattern=r"^LEAD-[0-9]{4,10}$")
      segment: Literal["smb", "mid_market", "enterprise"]
      priority: Literal["low", "medium", "high"]
      company_size: Literal["1-10", "11-50", "51-200", "201+"] | None = None
      tech_stack: list[TechItem] | None = Field(None, max_length=10)
      notes: str | None = Field(None, max_length=300)
  ```

  Pydantic note: using `None` defaults makes these fields optional and nullable in the emitted schema. If you need omission without `null`, author the JSON Schema directly or use a schema representation that distinguishes those cases more directly.

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

  const leadPayloadSchema = z.object({
    lead_id: z.string().regex(/^LEAD-[0-9]{4,10}$/),
    segment: z.enum(["smb", "mid_market", "enterprise"]),
    priority: z.enum(["low", "medium", "high"]),
    company_size: z.enum(["1-10", "11-50", "51-200", "201+"]).optional(),
    tech_stack: z.array(z.string().min(1).max(40)).max(10).optional(),
    notes: z.string().max(300).optional(),
  }).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": "Qualify lead LEAD-9821: mid-market company, 51-200 employees, uses Salesforce and HubSpot, high priority for Q2 migration." }],
      "response_format": {
        "type": "json_schema",
        "json_schema": {
          "name": "lead_qualification",
          "schema": {
            "type": "object",
            "properties": {
              "lead_id": { "type": "string", "pattern": "^LEAD-[0-9]{4,10}$" },
              "segment": { "type": "string", "enum": ["smb", "mid_market", "enterprise"] },
              "priority": { "type": "string", "enum": ["low", "medium", "high"] },
              "company_size": { "type": "string", "enum": ["1-10", "11-50", "51-200", "201+"] },
              "tech_stack": {
                "type": "array",
                "items": { "type": "string", "minLength": 1, "maxLength": 40 },
                "maxItems": 10
              },
              "notes": { "type": "string", "maxLength": 300 }
            },
            "required": ["lead_id", "segment", "priority"],
            "additionalProperties": false
          }
        }
      }
    }'
  ```
</CodeGroup>

## Example outputs

Core-only output:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "lead_id": "LEAD-9821",
  "segment": "mid_market",
  "priority": "high"
}
```

Enriched output:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "lead_id": "LEAD-9821",
  "segment": "mid_market",
  "priority": "high",
  "company_size": "51-200",
  "tech_stack": ["salesforce", "hubspot"],
  "notes": "Team requested migration support in Q2."
}
```

## Why this works

The `required` array in the JSON schema guarantees that `lead_id`, `segment`, and `priority` are always present, so your routing logic never hits a missing key. The optional fields (`company_size`, `tech_stack`, `notes`) are still fully typed with enums, bounds, and `maxItems`, so when they do appear, they conform to the same quality standards as the required fields.

This also makes the schema forward-compatible. When you add a new enrichment field later, existing consumers continue working because they only depend on the required core. The new field appears in outputs that have the data, and is absent from those that don't.

## Related docs

* [Object reference](/json-schema/reference/object)
* [Optional vs Null cookbook](/json-schema/optional-vs-null)
