> This page is for Gateway.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.merge.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.merge.dev/_mcp/server.

# Structured outputs

> Request JSON output on Gateway with response_format, control strict mode, find capable routes, and understand emulation, failover, and error behavior.

Structured outputs constrain a response to any JSON object (`json_object`) or JSON matching your schema (`json_schema`), using each vendor's native mechanism. Use `json_schema` when you deserialize into typed structures.

## Request JSON

Pass `response_format` on `/v1/responses` or OpenAI Chat Completions, or `text.format` on `/v1/openai/responses`. In the TypeScript SDK, build it with `zodResponseFormat` from `merge-gateway-sdk/zod`.

**`cURL`**

```bash title="cURL"
curl https://api-gateway.merge.dev/v1/responses \
  -H "Authorization: Bearer $MERGE_GATEWAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.1",
    "input": [{"type": "message", "role": "user", "content": "Invent a person."}],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "person",
        "schema": {
          "type": "object",
          "properties": {"name": {"type": "string"}, "age": {"type": "integer"}},
          "required": ["name", "age"],
          "additionalProperties": false
        },
        "strict": true
      }
    }
  }'
```

**`Python`**

```python title="Python"
import os
from typing import Optional
from pydantic import BaseModel
from merge_gateway import MergeGateway, response_format_from_model

class Person(BaseModel):
    name: str
    age: Optional[int] = None

client = MergeGateway(api_key=os.environ["MERGE_GATEWAY_API_KEY"])

response = client.responses.create(
    model="openai/gpt-5.1",
    input=[{"type": "message", "role": "user", "content": "Invent a person."}],
    # Optional fields break OpenAI strict mode, so turn it off
    response_format=response_format_from_model("person", Person, strict=False),
)
```

**`OpenAI SDK`**

```python title="OpenAI SDK"
import os
from openai import OpenAI
from pydantic import BaseModel

class Person(BaseModel):
    name: str
    age: int

client = OpenAI(
    api_key=os.environ["MERGE_GATEWAY_API_KEY"],
    base_url="https://api-gateway.merge.dev/v1/openai",
)

response = client.responses.parse(
    model="openai/gpt-5.1",
    input="Invent a person.",
    text_format=Person,
)
print(response.output_parsed)
```

**`AI SDK`**

```typescript title="AI SDK"
import { createMergeGateway } from "merge-gateway-ai-sdk-provider";
import { generateObject } from "ai";
import { z } from "zod";

const gateway = createMergeGateway({ apiKey: process.env.MERGE_GATEWAY_API_KEY });

const { object } = await generateObject({
  model: gateway("openai/gpt-5.1"),
  schema: z.object({ name: z.string(), age: z.number().optional() }),
  prompt: "Invent a person.",
  providerOptions: { mergeGateway: { strictJsonSchema: false } },
});
```

Your schema passes to the vendor as written, including `$ref`/`$defs` and `anyOf`, rewriting dialect (such as `definitions` to `$defs`) only where a vendor needs it.

## Strict mode

`strict` is tri-state on the native API:

| `strict` | Behavior                                                                                                                                                                                     |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Omitted  | The vendor's default. Gateway sends no value.                                                                                                                                                |
| `true`   | Vendors with a strict mode, such as OpenAI, guarantee conformance but require every property in `required` and `additionalProperties: false`. Gateway also fails over on a schema violation. |
| `false`  | The schema guides generation without strict constraints. Use it for optional fields and open objects.                                                                                        |

The AI SDK provider defaults `strictJsonSchema` to `true`; set it to `false` for schemas with optional fields.

## Routing and failover

Gateway checks support on the exact route before calling the vendor: routing policies drop candidates that can't serve the request, and fallbacks re-check.

On a route with forced tool choice but no native structured outputs, Gateway serves `json_schema` by forcing one tool whose parameters are your schema, returning its arguments as the message text. This doesn't apply to `json_object`, to routes that can't force a tool, or to requests with their own `tools` or `tool_choice`, which return `400 unsupported_params`.

With `strict: true`, a `200` whose body isn't valid JSON or doesn't match your schema is a failed attempt, and a Priority or Intelligent policy moves to the next candidate. If every candidate fails, Gateway serves the last body, so validate it. Streaming, tool-call turns, and output-token-limit stops are exempt. Each discarded attempt bills at the route that ran it. Vendor strict modes don't enforce value keywords such as `minimum`.

With `stream: true`, JSON arrives as text deltas, and a mid-stream fallback sends a `{"fallback_restart": true, "model": "...", "vendor": "..."}` data frame and restarts generation, so discard anything parsed before it.

## Reference

| Field                     | Notes                          |
| ------------------------- | ------------------------------ |
| `type`                    | `json_object` or `json_schema` |
| `json_schema.schema`      | Required for `json_schema`     |
| `json_schema.name`        | Defaults to `response`         |
| `json_schema.description` | Optional                       |
| `json_schema.strict`      | `true`, `false`, or omitted    |

#### Unsupported formats and validation errors

| Case                                                                         | Result                                                             |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Route can't serve the format                                                 | `400 unsupported_params` naming the model, vendor, and format type |
| `json_object` on Bedrock                                                     | `400 unsupported_params`. Send `json_schema` instead.              |
| `json_object` on `anthropic/claude-fable-5-1` or `anthropic/claude-opus-5-5` | `400 unsupported_params`. Send `json_schema` with a `schema`.      |
| Malformed `text.format`, or malformed `response_format` on a compat surface  | `400 invalid_response_format`                                      |
| Malformed `response_format` on native `/v1/responses`                        | `422` validation error                                             |

`supports_structured_outputs` on `GET /v1/models` is `true` for routes Gateway serves natively or by emulation. [Errors](/merge-gateway/errors) has the error shapes.

## Next steps

#### [Tool calling](/merge-gateway/capabilities/tool-calling)

Add tool calls to model requests

#### [Model catalog](/merge-gateway/models/catalog)

Check model capabilities