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

# Structured Outputs

> Get guaranteed JSON responses that match your exact schema — with type-safe parsing via Pydantic and Zod

You tell Mavera the exact shape you want. It guarantees the response matches. Every field, every type, every time.

No validation code. No retries. No "I hope this parses." You define a schema, and the API returns JSON that conforms to it — or tells you why it can't.

<Info>
  Structured Outputs work with the same OpenAI SDK you already use. If you've used structured outputs with OpenAI, you already know how this works. Just point at Mavera.
</Info>

***

## Why Use Structured Outputs

* **Guaranteed schema adherence** — the response always matches your schema. Not "usually." Always.
* **Type-safe parsing** — use Pydantic (Python) or Zod (JavaScript) to parse directly into typed objects. No `json.loads()` + pray.
* **Programmatic refusals** — when the model can't comply, you get a structured refusal instead of malformed JSON.
* **Simpler prompts** — the schema does the heavy lifting. You don't need to say "return JSON with these fields" in your prompt anymore.
* **No validation boilerplate** — delete your schema validation code. The API handles it.

***

## How It Works

```mermaid theme={"dark"}
flowchart LR
    A["Your Schema\n(Pydantic / Zod / JSON Schema)"] --> B["Mavera API"]
    B --> C["Guaranteed JSON\nmatching your schema"]
```

You pass a schema via `text.format`. Mavera constrains the model's output to match that schema exactly. The response comes back as valid JSON that you can parse directly into your typed objects.

There are two ways to get structured output from the API:

| Method               | How                                        | Best for                                                                              |
| -------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------- |
| **`text.format`**    | Pass a schema in the Responses API request | Extracting data, scoring, classification, any time you want typed JSON back           |
| **Function calling** | Define tools with JSON Schema parameters   | When the model should decide *which* action to take, then return structured arguments |

This guide covers `text.format`. For function calling, see [Responses API — Tool Calling](/features/responses#tool-calling-function-calling).

***

## Quick Example

Score a product review. Get back a number, a sentiment label, and an explanation. Every time.

<CodeGroup>
  ```python Python theme={"dark"}
  import os
  from pydantic import BaseModel
  from openai import OpenAI

  class ReviewScore(BaseModel):
      score: int
      sentiment: str
      explanation: str

  client = OpenAI(
      api_key=os.environ["MAVERA_API_KEY"],
      base_url="https://app.mavera.io/api/v1",
  )

  completion = client.responses.parse(
      model="mavera-1",
      input=[
          {"role": "system", "content": "Score this product review."},
          {"role": "user", "content": "The battery life is incredible but the screen is dim."},
      ],
      text_format=ReviewScore,
      extra_body={"persona_id": os.environ.get("PERSONA_ID")},
  )

  result = completion.output_parsed
  print(f"Score: {result.score}, Sentiment: {result.sentiment}")
  print(f"Explanation: {result.explanation}")
  ```

  ```javascript JavaScript theme={"dark"}
  import { z } from "zod";
  import OpenAI from "openai";
  import { zodResponseFormat } from "openai/helpers/zod";

  const ReviewScore = z.object({
    score: z.number(),
    sentiment: z.string(),
    explanation: z.string(),
  });

  const client = new OpenAI({
    apiKey: process.env.MAVERA_API_KEY,
    baseURL: "https://app.mavera.io/api/v1",
  });

  const completion = await client.responses.parse({
    model: "mavera-1",
    input: [
      { role: "system", content: "Score this product review." },
      { role: "user", content: "The battery life is incredible but the screen is dim." },
    ],
    text_format: zodResponseFormat(ReviewScore, "review_score"),
    persona_id: process.env.PERSONA_ID,
  });

  const result = completion.output_parsed;
  console.log(`Score: ${result.score}, Sentiment: ${result.sentiment}`);
  console.log(`Explanation: ${result.explanation}`);
  ```
</CodeGroup>

Example output:

```json theme={"dark"}
{
  "score": 7,
  "sentiment": "mixed",
  "explanation": "Strong praise for battery life offset by a notable complaint about screen brightness. Overall positive but with a clear reservation."
}
```

<Tip>
  The `.parse()` method automatically converts the JSON response into your Pydantic model (Python) or Zod schema (JavaScript). You get a fully typed object — not a string, not a dict.
</Tip>

***

## Defining Schemas

You can define your output schema three ways. Pick whichever fits your stack.

### Pydantic Models (Python)

The most Pythonic approach. Define your schema as a class, and the SDK handles the rest.

```python theme={"dark"}
from pydantic import BaseModel, Field
from typing import Optional
from enum import Enum

class Sentiment(str, Enum):
    positive = "positive"
    negative = "negative"
    neutral = "neutral"
    mixed = "mixed"

class Aspect(BaseModel):
    name: str = Field(description="What feature or quality is being discussed")
    sentiment: Sentiment
    quote: str = Field(description="The exact phrase from the review")

class ReviewAnalysis(BaseModel):
    overall_sentiment: Sentiment
    score: int = Field(description="1-10 rating")
    aspects: list[Aspect] = Field(description="Individual aspects mentioned")
    summary: str
    would_recommend: bool
    confidence: Optional[float] = Field(
        default=None,
        description="How confident the model is in this analysis, 0-1"
    )
```

Then use it:

```python theme={"dark"}
completion = client.responses.parse(
    model="mavera-1",
    input=[...],
    text_format=ReviewAnalysis,
    extra_body={"persona_id": os.environ.get("PERSONA_ID")},
)

review = completion.output_parsed
for aspect in review.aspects:
    print(f"  {aspect.name}: {aspect.sentiment.value} — \"{aspect.quote}\"")
```

### Zod Schemas (JavaScript)

Same idea, JavaScript flavor. Zod gives you runtime validation and TypeScript types in one shot.

```javascript theme={"dark"}
import { z } from "zod";

const Sentiment = z.enum(["positive", "negative", "neutral", "mixed"]);

const Aspect = z.object({
  name: z.string().describe("What feature or quality is being discussed"),
  sentiment: Sentiment,
  quote: z.string().describe("The exact phrase from the review"),
});

const ReviewAnalysis = z.object({
  overall_sentiment: Sentiment,
  score: z.number().describe("1-10 rating"),
  aspects: z.array(Aspect).describe("Individual aspects mentioned"),
  summary: z.string(),
  would_recommend: z.boolean(),
  confidence: z.number().optional().describe("How confident the model is, 0-1"),
});
```

Then use it:

```javascript theme={"dark"}
import { zodResponseFormat } from "openai/helpers/zod";

const completion = await client.responses.parse({
  model: "mavera-1",
  input: [...],
  text_format: zodResponseFormat(ReviewAnalysis, "review_analysis"),
  persona_id: process.env.PERSONA_ID,
});

const review = completion.output_parsed;
review.aspects.forEach((a) => {
  console.log(`  ${a.name}: ${a.sentiment} — "${a.quote}"`);
});
```

### Raw JSON Schema

For when you need direct control, aren't using the SDK's `.parse()` method, or are working in a language without a schema library.

```python theme={"dark"}
response = client.responses.create(
    model="mavera-1",
    input=[
        {"role": "user", "content": "Analyze this review: Great battery, dim screen."}
    ],
    extra_body={
        "persona_id": os.environ.get("PERSONA_ID"),
        "text": {
            "format": {
                "type": "json_schema",
                "json_schema": {
                    "name": "review_analysis",
                    "strict": True,
                    "schema": {
                        "type": "object",
                        "properties": {
                            "score": {"type": "integer", "description": "1-10 rating"},
                            "sentiment": {
                                "type": "string",
                                "enum": ["positive", "negative", "neutral", "mixed"]
                            },
                            "summary": {"type": "string"},
                            "tags": {
                                "type": "array",
                                "items": {"type": "string"}
                            }
                        },
                        "required": ["score", "sentiment", "summary", "tags"],
                        "additionalProperties": False
                    }
                }
            }
        }
    },
)

import json
result = json.loads(response.output[0].content[0].text)
```

<Warning>
  When using raw JSON Schema with `strict: true`, you must set `"additionalProperties": false` on every object in your schema — including nested ones. This is a JSON Schema requirement for strict mode.
</Warning>

### Supported JSON Schema Features

| Feature                                  | Supported | Notes                                                                    |
| ---------------------------------------- | --------- | ------------------------------------------------------------------------ |
| `string`, `number`, `integer`, `boolean` | Yes       | All primitive types                                                      |
| `object` with `properties`               | Yes       | Must include `required` and `additionalProperties: false` in strict mode |
| `array` with `items`                     | Yes       | Typed arrays                                                             |
| `enum`                                   | Yes       | Constrain to specific values                                             |
| `anyOf`                                  | Yes       | Union types (e.g., string or null)                                       |
| `$ref` and `$defs`                       | Yes       | Reusable definitions                                                     |
| Nested objects                           | Yes       | As deep as you need                                                      |
| `description`                            | Yes       | Helps the model understand your intent — use it                          |
| `default`                                | No        | Not used for generation                                                  |
| `minimum` / `maximum`                    | No        | Use descriptions or enums to constrain values instead                    |
| `pattern` (regex)                        | No        | Describe the format in `description`                                     |

***

## Advanced Examples

<AccordionGroup>
  <Accordion title="Chain of Thought">
    Make the model show its work. Useful when you need to audit reasoning or debug unexpected results.

    <CodeGroup>
      ```python Python theme={"dark"}
      class Step(BaseModel):
          reasoning: str = Field(description="One step of reasoning")
          evidence: str = Field(description="What supports this step")

      class ChainOfThought(BaseModel):
          steps: list[Step] = Field(description="Reasoning steps, in order")
          final_answer: str
          confidence: float = Field(description="0-1 confidence in the final answer")

      completion = client.responses.parse(
          model="mavera-1",
          input=[
              {"role": "system", "content": "Think step by step. Show your reasoning."},
              {"role": "user", "content": "Should a DTC brand expand into wholesale retail in 2025?"},
          ],
          text_format=ChainOfThought,
          extra_body={"persona_id": os.environ.get("PERSONA_ID")},
      )

      result = completion.output_parsed
      for i, step in enumerate(result.steps, 1):
          print(f"Step {i}: {step.reasoning}")
          print(f"  Evidence: {step.evidence}")
      print(f"\nAnswer: {result.final_answer} (confidence: {result.confidence})")
      ```

      ```javascript JavaScript theme={"dark"}
      const Step = z.object({
        reasoning: z.string().describe("One step of reasoning"),
        evidence: z.string().describe("What supports this step"),
      });

      const ChainOfThought = z.object({
        steps: z.array(Step).describe("Reasoning steps, in order"),
        final_answer: z.string(),
        confidence: z.number().describe("0-1 confidence in the final answer"),
      });

      const completion = await client.responses.parse({
        model: "mavera-1",
        input: [
          { role: "system", content: "Think step by step. Show your reasoning." },
          { role: "user", content: "Should a DTC brand expand into wholesale retail in 2025?" },
        ],
        text_format: zodResponseFormat(ChainOfThought, "chain_of_thought"),
        persona_id: process.env.PERSONA_ID,
      });

      const result = completion.output_parsed;
      result.steps.forEach((step, i) => {
        console.log(`Step ${i + 1}: ${step.reasoning}`);
        console.log(`  Evidence: ${step.evidence}`);
      });
      console.log(`\nAnswer: ${result.final_answer} (confidence: ${result.confidence})`);
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Multi-label Classification">
    Classify content into multiple categories, each with a confidence score. Great for content tagging, support ticket routing, or lead scoring.

    <CodeGroup>
      ```python Python theme={"dark"}
      class Category(BaseModel):
          label: str = Field(description="Category name")
          confidence: float = Field(description="0-1 confidence this category applies")
          reason: str = Field(description="Why this category was assigned")

      class Classification(BaseModel):
          categories: list[Category]
          primary_category: str = Field(description="The single most relevant category")
          content_summary: str

      completion = client.responses.parse(
          model="mavera-1",
          input=[
              {
                  "role": "system",
                  "content": "Classify this support ticket. Categories: billing, technical, account, feature_request, bug_report."
              },
              {
                  "role": "user",
                  "content": "I was charged twice for my subscription and now I can't log in to my account."
              },
          ],
          text_format=Classification,
          extra_body={"persona_id": os.environ.get("PERSONA_ID")},
      )

      result = completion.output_parsed
      print(f"Primary: {result.primary_category}")
      for cat in result.categories:
          print(f"  {cat.label}: {cat.confidence:.0%} — {cat.reason}")
      ```

      ```javascript JavaScript theme={"dark"}
      const Category = z.object({
        label: z.string().describe("Category name"),
        confidence: z.number().describe("0-1 confidence this category applies"),
        reason: z.string().describe("Why this category was assigned"),
      });

      const Classification = z.object({
        categories: z.array(Category),
        primary_category: z.string().describe("The single most relevant category"),
        content_summary: z.string(),
      });

      const completion = await client.responses.parse({
        model: "mavera-1",
        input: [
          {
            role: "system",
            content: "Classify this support ticket. Categories: billing, technical, account, feature_request, bug_report.",
          },
          {
            role: "user",
            content: "I was charged twice for my subscription and now I can't log in to my account.",
          },
        ],
        text_format: zodResponseFormat(Classification, "classification"),
        persona_id: process.env.PERSONA_ID,
      });

      const result = completion.output_parsed;
      console.log(`Primary: ${result.primary_category}`);
      result.categories.forEach((cat) => {
        console.log(`  ${cat.label}: ${(cat.confidence * 100).toFixed(0)}% — ${cat.reason}`);
      });
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Nested Objects — Structured Report">
    Extract a full structured report with sections, each containing multiple findings. Perfect for research analysis, audit reports, or content reviews.

    <CodeGroup>
      ```python Python theme={"dark"}
      class Finding(BaseModel):
          title: str
          detail: str
          severity: str = Field(description="low, medium, or high")
          recommendation: str

      class Section(BaseModel):
          heading: str
          summary: str
          findings: list[Finding]

      class Report(BaseModel):
          title: str
          executive_summary: str
          sections: list[Section]
          overall_risk_level: str = Field(description="low, medium, high, or critical")
          next_steps: list[str]

      completion = client.responses.parse(
          model="mavera-1",
          input=[
              {"role": "system", "content": "Produce a structured brand health report."},
              {"role": "user", "content": "Analyze brand perception for a fintech startup targeting millennials. Recent news: launched budgeting feature, had a 2-hour outage last week."},
          ],
          text_format=Report,
          extra_body={"persona_id": os.environ.get("PERSONA_ID")},
      )

      report = completion.output_parsed
      print(f"# {report.title}")
      print(f"\n{report.executive_summary}")
      for section in report.sections:
          print(f"\n## {section.heading}")
          for finding in section.findings:
              print(f"  [{finding.severity.upper()}] {finding.title}: {finding.recommendation}")
      ```

      ```javascript JavaScript theme={"dark"}
      const Finding = z.object({
        title: z.string(),
        detail: z.string(),
        severity: z.string().describe("low, medium, or high"),
        recommendation: z.string(),
      });

      const Section = z.object({
        heading: z.string(),
        summary: z.string(),
        findings: z.array(Finding),
      });

      const Report = z.object({
        title: z.string(),
        executive_summary: z.string(),
        sections: z.array(Section),
        overall_risk_level: z.string().describe("low, medium, high, or critical"),
        next_steps: z.array(z.string()),
      });

      const completion = await client.responses.parse({
        model: "mavera-1",
        input: [
          { role: "system", content: "Produce a structured brand health report." },
          { role: "user", content: "Analyze brand perception for a fintech startup targeting millennials. Recent news: launched budgeting feature, had a 2-hour outage last week." },
        ],
        text_format: zodResponseFormat(Report, "report"),
        persona_id: process.env.PERSONA_ID,
      });

      const report = completion.output_parsed;
      console.log(`# ${report.title}\n\n${report.executive_summary}`);
      report.sections.forEach((section) => {
        console.log(`\n## ${section.heading}`);
        section.findings.forEach((f) => {
          console.log(`  [${f.severity.toUpperCase()}] ${f.title}: ${f.recommendation}`);
        });
      });
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Enum Constraints">
    Force the model to pick from a fixed set of options. Eliminates the "creative" responses you didn't ask for.

    <CodeGroup>
      ```python Python theme={"dark"}
      from enum import Enum

      class Priority(str, Enum):
          critical = "critical"
          high = "high"
          medium = "medium"
          low = "low"

      class Impact(str, Enum):
          revenue = "revenue"
          user_experience = "user_experience"
          security = "security"
          operations = "operations"

      class TriageResult(BaseModel):
          priority: Priority
          impact_area: Impact
          estimated_effort_hours: int
          should_escalate: bool
          reasoning: str

      completion = client.responses.parse(
          model="mavera-1",
          input=[
              {"role": "system", "content": "Triage this engineering issue."},
              {"role": "user", "content": "Users in the EU are seeing 500 errors on the checkout page since the last deploy."},
          ],
          text_format=TriageResult,
          extra_body={"persona_id": os.environ.get("PERSONA_ID")},
      )

      result = completion.output_parsed
      print(f"Priority: {result.priority.value}")
      print(f"Impact: {result.impact_area.value}")
      print(f"Escalate: {result.should_escalate}")
      ```

      ```javascript JavaScript theme={"dark"}
      const TriageResult = z.object({
        priority: z.enum(["critical", "high", "medium", "low"]),
        impact_area: z.enum(["revenue", "user_experience", "security", "operations"]),
        estimated_effort_hours: z.number(),
        should_escalate: z.boolean(),
        reasoning: z.string(),
      });

      const completion = await client.responses.parse({
        model: "mavera-1",
        input: [
          { role: "system", content: "Triage this engineering issue." },
          { role: "user", content: "Users in the EU are seeing 500 errors on the checkout page since the last deploy." },
        ],
        text_format: zodResponseFormat(TriageResult, "triage_result"),
        persona_id: process.env.PERSONA_ID,
      });

      const result = completion.output_parsed;
      console.log(`Priority: ${result.priority}`);
      console.log(`Impact: ${result.impact_area}`);
      console.log(`Escalate: ${result.should_escalate}`);
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

***

## Using with Personas

This is where Mavera shines. Structured outputs give you typed data. Personas give you perspective. Combine them and you get quantified audience insights you can pipe directly into your code.

Here's a concrete example: score an ad headline from a Gen Z persona's perspective.

<CodeGroup>
  ```python Python theme={"dark"}
  import os
  from pydantic import BaseModel, Field
  from openai import OpenAI

  class HeadlineScore(BaseModel):
      relevance_score: int = Field(description="1-10 how relevant this feels to the persona")
      would_click: bool = Field(description="Would this persona actually click?")
      reasoning: str = Field(description="Why or why not, in the persona's voice")
      suggested_edit: str = Field(description="How the persona would improve it")

  client = OpenAI(
      api_key=os.environ["MAVERA_API_KEY"],
      base_url="https://app.mavera.io/api/v1",
  )

  completion = client.responses.parse(
      model="mavera-1",
      input=[
          {
              "role": "system",
              "content": "You are evaluating an ad headline. Score it honestly from your perspective."
          },
          {
              "role": "user",
              "content": "Headline: 'Invest in Your Future — Open a Savings Account Today'"
          },
      ],
      text_format=HeadlineScore,
      extra_body={"persona_id": os.environ["GEN_Z_PERSONA_ID"]},
  )

  result = completion.output_parsed
  print(f"Relevance: {result.relevance_score}/10")
  print(f"Would click: {result.would_click}")
  print(f"Reasoning: {result.reasoning}")
  print(f"Suggested edit: {result.suggested_edit}")
  ```

  ```javascript JavaScript theme={"dark"}
  import { z } from "zod";
  import OpenAI from "openai";
  import { zodResponseFormat } from "openai/helpers/zod";

  const HeadlineScore = z.object({
    relevance_score: z.number().describe("1-10 how relevant this feels to the persona"),
    would_click: z.boolean().describe("Would this persona actually click?"),
    reasoning: z.string().describe("Why or why not, in the persona's voice"),
    suggested_edit: z.string().describe("How the persona would improve it"),
  });

  const client = new OpenAI({
    apiKey: process.env.MAVERA_API_KEY,
    baseURL: "https://app.mavera.io/api/v1",
  });

  const completion = await client.responses.parse({
    model: "mavera-1",
    input: [
      {
        role: "system",
        content: "You are evaluating an ad headline. Score it honestly from your perspective.",
      },
      {
        role: "user",
        content: "Headline: 'Invest in Your Future — Open a Savings Account Today'",
      },
    ],
    text_format: zodResponseFormat(HeadlineScore, "headline_score"),
    persona_id: process.env.GEN_Z_PERSONA_ID,
  });

  const result = completion.output_parsed;
  console.log(`Relevance: ${result.relevance_score}/10`);
  console.log(`Would click: ${result.would_click}`);
  console.log(`Reasoning: ${result.reasoning}`);
  console.log(`Suggested edit: ${result.suggested_edit}`);
  ```
</CodeGroup>

Example output from a Gen Z persona:

```json theme={"dark"}
{
  "relevance_score": 3,
  "would_click": false,
  "reasoning": "This feels like something my parents would say. 'Invest in your future' is vague and preachy. I'd scroll right past it.",
  "suggested_edit": "Start Saving Without Thinking About It — Auto-Round-Up Is Here"
}
```

<Tip>
  Run the same prompt across multiple personas to compare how different demographics react. Loop through persona IDs and collect the results into a comparison table.
</Tip>

***

## Structured Outputs vs JSON Mode vs Plain Text

| Feature               | Structured Outputs                              | JSON Mode                           | Plain Text                      |
| --------------------- | ----------------------------------------------- | ----------------------------------- | ------------------------------- |
| **Schema adherence**  | Guaranteed                                      | No                                  | No                              |
| **Valid JSON**        | Yes                                             | Yes                                 | No                              |
| **Type-safe parsing** | Yes (Pydantic/Zod)                              | Manual `json.loads()`               | Manual string parsing           |
| **Refusal handling**  | Structured (`response.refusal`)                 | Unstructured                        | Unstructured                    |
| **Schema complexity** | Nested objects, arrays, enums                   | Flat JSON only                      | N/A                             |
| **When to use**       | Programmatic consumption, pipelines, typed APIs | Flexible JSON when structure varies | Human-readable responses, prose |

<Info>
  **When in doubt, use Structured Outputs.** JSON mode (`{"type": "json_object"}`) gives you valid JSON but no schema guarantees — the model might return any shape. Structured Outputs guarantee the shape matches your schema.
</Info>

### How to set each mode

<CodeGroup>
  ```python Structured Outputs (Pydantic) theme={"dark"}
  completion = client.responses.parse(
      model="mavera-1",
      input=[...],
      text_format=YourPydanticModel,
      extra_body={"persona_id": "..."},
  )
  result = completion.output_parsed
  ```

  ```python JSON Mode theme={"dark"}
  response = client.responses.create(
      model="mavera-1",
      input=[...],
      extra_body={
          "persona_id": "...",
          "text": {"format": {"type": "json_object"}}
      },
  )
  data = json.loads(response.output[0].content[0].text)
  ```

  ```python Plain Text (default) theme={"dark"}
  response = client.responses.create(
      model="mavera-1",
      input=[...],
      extra_body={"persona_id": "..."},
  )
  text = response.output[0].content[0].text
  ```
</CodeGroup>

***

## Tips & Best Practices

<AccordionGroup>
  <Accordion title="Keep schemas simple and flat when possible">
    Deeply nested schemas work, but simpler schemas produce better results. If you can flatten a three-level object into two levels, do it. The model has an easier time conforming to simple structures, and your parsing code stays clean.

    ```python theme={"dark"}
    class SimpleReview(BaseModel):
        score: int
        sentiment: str
        pros: list[str]
        cons: list[str]
    ```

    This is almost always better than wrapping `pros` and `cons` inside a nested `analysis` object.
  </Accordion>

  <Accordion title="Use descriptions on every field">
    The `description` parameter on your schema fields acts like a mini-prompt for each field. It tells the model what you expect, without cluttering your system prompt.

    ```python theme={"dark"}
    class Score(BaseModel):
        relevance: int = Field(description="1-10, where 10 means perfectly relevant to the target audience")
        tone_match: int = Field(description="1-10, where 10 means the tone perfectly matches brand guidelines")
    ```

    Without descriptions, the model guesses what "relevance" and "tone\_match" mean. With descriptions, it knows.
  </Accordion>

  <Accordion title="Handle refusals">
    Sometimes the model can't — or shouldn't — produce output matching your schema. Maybe the input is harmful, or the request doesn't make sense. When this happens, `output_parsed` will be `None` and `refusal` will contain an explanation.

    Always check for refusals before accessing `.output_parsed`:

    ```python theme={"dark"}
    if completion.refusal:
        print(f"Model refused: {completion.refusal}")
    else:
        print(f"Score: {completion.output_parsed.score}")
    ```

    ```javascript theme={"dark"}
    if (completion.refusal) {
      console.log(`Model refused: ${completion.refusal}`);
    } else {
      console.log(`Score: ${completion.output_parsed.score}`);
    }
    ```
  </Accordion>

  <Accordion title="Use enums to constrain choices">
    If a field should only have a few possible values, use an enum. This eliminates creative-but-wrong answers like "kinda positive" when you wanted `"positive"`, `"negative"`, or `"neutral"`.

    ```python theme={"dark"}
    from enum import Enum

    class Sentiment(str, Enum):
        positive = "positive"
        negative = "negative"
        neutral = "neutral"
    ```

    The model will pick from exactly these values. No variations, no surprises.
  </Accordion>

  <Accordion title="Test with edge cases">
    Your schema will handle the happy path. But what about empty input? Contradictory information? Extremely long text? Test these:

    * **Empty or minimal input** — does the model still produce valid output?
    * **Ambiguous input** — does it pick reasonable defaults for enum fields?
    * **Long input** — does the model still conform to the schema, or does it get confused?
    * **Adversarial input** — does the model refuse gracefully?

    Build a small test suite with 5–10 edge cases and run them before shipping.
  </Accordion>
</AccordionGroup>

***

## Error Handling

### Invalid Schema

If your JSON Schema is malformed or uses unsupported features, the API returns a `400` error:

```json theme={"dark"}
{
  "error": {
    "message": "Invalid schema: 'additionalProperties' must be false in strict mode",
    "type": "invalid_request_error",
    "code": "invalid_schema",
    "param": "text.format"
  }
}
```

**Fix:** Check that your schema uses only [supported features](#supported-json-schema-features) and includes `additionalProperties: false` on every object when using strict mode.

### Model Can't Conform

Rarely, the model might not be able to produce output matching your schema — usually because the schema contradicts the prompt, or the input is too ambiguous. When this happens with `.parse()`:

<CodeGroup>
  ```python Python theme={"dark"}
  completion = client.responses.parse(
      model="mavera-1",
      input=[...],
      text_format=YourModel,
      extra_body={"persona_id": "..."},
  )

  if completion.refusal:
      print(f"Refusal: {completion.refusal}")
  elif completion.output_parsed:
      print(f"Result: {completion.output_parsed}")
  else:
      print(f"Raw content: {completion.output[0].content[0].text}")
  ```

  ```javascript JavaScript theme={"dark"}
  const completion = await client.responses.parse({
    model: "mavera-1",
    input: [...],
    text_format: zodResponseFormat(YourSchema, "your_schema"),
    persona_id: process.env.PERSONA_ID,
  });

  if (completion.refusal) {
    console.log(`Refusal: ${completion.refusal}`);
  } else if (completion.output_parsed) {
    console.log("Result:", completion.output_parsed);
  } else {
    console.log(`Raw content: ${completion.output[0].content[0].text}`);
  }
  ```
</CodeGroup>

### `status: "incomplete"`

If the response is cut off because it hit `max_tokens`, the JSON will be incomplete and parsing will fail. Either:

* Increase `max_tokens` for complex schemas
* Simplify your schema to produce shorter output
* Check `response.status` before parsing

```python theme={"dark"}
if completion.status != "completed":
    print("Response was truncated. Increase max_tokens or simplify the schema.")
elif completion.output_parsed:
    print(f"Result: {completion.output_parsed}")
```

<Warning>
  `text.format` and `analysis_mode` cannot be used together. Use `text.format` for your own custom schemas, or `analysis_mode` for Mavera's built-in analysis structure. If you need both, run two separate requests.
</Warning>

***

## Limitations

A few things to keep in mind:

* **No streaming with `.parse()`** — the `.parse()` convenience method waits for the full response. If you need streaming, use `.create()` with a raw JSON Schema in `text.format` and parse the complete response yourself.
* **Schema size** — extremely large schemas (50+ fields, deep nesting) may increase latency. Break large schemas into smaller, focused ones when possible.
* **`additionalProperties: false`** — required on every object level in strict mode. This is the most common schema error.
* **No regex patterns** — use `description` to explain expected formats (e.g., "ISO 8601 date string") instead of JSON Schema `pattern`.

***

## See Also

<CardGroup cols={2}>
  <Card title="Responses API" icon="message" href="/features/responses">
    Full API reference with streaming, analysis mode, and tool calling
  </Card>

  <Card title="Tool Calling" icon="wrench" href="/features/responses#tool-calling-function-calling">
    Define functions the model can call with structured arguments
  </Card>

  <Card title="Personas" icon="user" href="/features/personas">
    Explore 50+ pre-built personas and create custom ones
  </Card>

  <Card title="Focus Groups" icon="users" href="/features/focus-groups">
    Run structured outputs at scale with simulated audience panels
  </Card>
</CardGroup>
