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

# Structured output

> Constrain a response to a JSON Schema. Get back validated, typed JSON instead of free text.

When you want a response you can parse rather than a sentence, set `response_format` to a JSON Schema. The model produces JSON that matches it, and the gateway validates the response against the schema before returning it. This is the standard OpenAI-compatible parameter, so it works from any SDK you point at Opper.

Reach for structured output to parse a receipt, extract fields from a contract, classify a support ticket, or any job where you describe the shape you want back.

## A working example

<CodeGroup>
  ```python Python theme={null}
  import os, json
  from openai import OpenAI

  client = OpenAI(
      base_url="https://api.opper.ai/v3/compat",
      api_key=os.environ["OPPER_API_KEY"],
  )

  r = client.chat.completions.create(
      model="openai/gpt-5-mini",
      messages=[
          {"role": "user", "content": "Pick a category for: 'My reset link is broken.'"}
      ],
      response_format={
          "type": "json_schema",
          "json_schema": {
              "name": "ticket",
              "schema": {
                  "type": "object",
                  "properties": {
                      "category":   {"type": "string"},
                      "priority":   {"type": "string"},
                      "confidence": {"type": "number"},
                  },
                  "required": ["category", "priority", "confidence"],
              },
          },
      },
  )

  result = json.loads(r.choices[0].message.content)
  print(result["category"], result["priority"])
  ```

  ```typescript TypeScript theme={null}
  import OpenAI from "openai";

  const client = new OpenAI({
      baseURL: "https://api.opper.ai/v3/compat",
      apiKey: process.env.OPPER_API_KEY!,
  });

  const r = await client.chat.completions.create({
      model: "openai/gpt-5-mini",
      messages: [
          { role: "user", content: "Pick a category for: 'My reset link is broken.'" },
      ],
      response_format: {
          type: "json_schema",
          json_schema: {
              name: "ticket",
              schema: {
                  type: "object",
                  properties: {
                      category:   { type: "string" },
                      priority:   { type: "string" },
                      confidence: { type: "number" },
                  },
                  required: ["category", "priority", "confidence"],
              },
          },
      },
  });

  const result = JSON.parse(r.choices[0].message.content!);
  console.log(result.category, result.priority);
  ```

  ```bash cURL theme={null}
  curl https://api.opper.ai/v3/compat/chat/completions \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $OPPER_API_KEY" \
    -d '{
      "model": "openai/gpt-5-mini",
      "messages": [{"role": "user", "content": "Pick a category for: '\''My reset link is broken.'\''"}],
      "response_format": {
        "type": "json_schema",
        "json_schema": {
          "name": "ticket",
          "schema": {
            "type": "object",
            "properties": {
              "category":   {"type": "string"},
              "priority":   {"type": "string"},
              "confidence": {"type": "number"}
            },
            "required": ["category", "priority", "confidence"]
          }
        }
      }
    }'
  ```
</CodeGroup>

The `content` field comes back as a JSON string. Parse it once to get a typed object. If the model can't produce something that matches the schema, the call fails with a validation error rather than silently returning malformed JSON.

<Tip>
  The Anthropic and Google AI SDKs reach the same behaviour through their own structured-output options. Whichever SDK you use, the gateway validates the result against your schema. See [Drop-in SDKs](/build/gateway/drop-in-sdks).
</Tip>

## Writing the schema

The schema is plain JSON Schema. A handful of patterns cover almost everything you'll need.

### Enums (one of a fixed set)

When the answer is one of a fixed set of values, use an enum. The model is far more reliable on these than on free strings.

```json theme={null}
"priority": { "type": "string", "enum": ["low", "medium", "high"] }
```

### Optional fields

List required fields in the `required` array. Anything left out can come back as `null`.

```json theme={null}
{
  "type": "object",
  "properties": {
    "name":  { "type": "string" },
    "email": { "type": "string" }
  },
  "required": ["name"]
}
```

### Arrays

```json theme={null}
"items": { "type": "array", "items": { "type": "string" } }
```

### Nested objects

```json theme={null}
{
  "type": "object",
  "properties": {
    "merchant": { "type": "string" },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "description": { "type": "string" },
          "price":       { "type": "number" }
        },
        "required": ["description", "price"]
      }
    },
    "total": { "type": "number" }
  },
  "required": ["merchant", "items", "total"]
}
```

### Field descriptions

Describe ambiguous fields. The model reads descriptions as guidance.

```json theme={null}
"priority": {
  "type": "string",
  "enum": ["low", "medium", "high"],
  "description": "Use 'high' when the user can't access their account."
}
```

## Structured output from images, PDFs, and audio

Structured output composes with multimodal input. Put the image, PDF, or audio in the message content and set `response_format` to the shape you want back. See [Vision & PDFs](/build/multimodal/vision-pdfs) for how to attach media.

## When to use what

| Need                                        | Reach for                                                          |
| ------------------------------------------- | ------------------------------------------------------------------ |
| Typed JSON from a model                     | `response_format: json_schema` (this page)                         |
| Model that calls your code mid-conversation | [Tool calling](/build/gateway/tools)                               |
| Multimodal input with structured output     | [Vision & PDFs](/build/multimodal/vision-pdfs) + `response_format` |

## What's next

<CardGroup cols={2}>
  <Card title="Tool calling" icon="screwdriver-wrench" href="/build/gateway/tools">
    When the model should call your code, not just answer.
  </Card>

  <Card title="Vision & PDFs" icon="image" href="/build/multimodal/vision-pdfs">
    Send images and documents, get structure back.
  </Card>

  <Card title="Streaming" icon="bolt-lightning" href="/build/gateway/streaming">
    Stream the response as it's generated.
  </Card>

  <Card title="Control Plane" icon="shield-check" href="/control-plane/overview">
    Govern, score, and improve every call.
  </Card>
</CardGroup>
