# Structured output

> Get JSON that matches your schema. response_format with JSON Schema, which models honour it, and the two methods that work for Claude models.

Source: https://oneaiapikey.com/docs/structured-output

Structured output means the model answers with JSON in a shape you define, so your code can parse it without guessing. There are three ways to ask for it; which one works depends on the model.

## Which method works where

| Model id | `json_schema` | `json_object` | Forced tool |
| --- | --- | --- | --- |
| [claude-opus-5-5](https://oneaiapikey.com/models/claude-opus-5-5) | No (dropped) | No (dropped) | Yes |
| [claude-sonnet-5](https://oneaiapikey.com/models/claude-sonnet-5) | No (dropped) | No (dropped) | Yes |
| [claude-haiku-4-5](https://oneaiapikey.com/models/claude-haiku-4-5) | No (dropped) | No (dropped) | Yes |
| [gpt-6.1-sol](https://oneaiapikey.com/models/gpt-6-1-sol) | Yes | Yes | Yes |
| [gpt-5.6-sol](https://oneaiapikey.com/models/gpt-5-6-sol) | Yes | Yes | Yes |
| [gpt-5.6-terra](https://oneaiapikey.com/models/gpt-5-6-terra) | Yes | Yes | Yes |
| [grok-4.6](https://oneaiapikey.com/models/grok-4-6) | Yes | Use json_schema | Yes |
| [claude-sonnet-5-5](https://oneaiapikey.com/models/claude-sonnet-5-5) | No (dropped) | No (dropped) | Yes |
| [grok-4.7](https://oneaiapikey.com/models/grok-4-7) | Yes | Use json_schema | Yes |
| [deepseek-v4-flash](https://oneaiapikey.com/models/deepseek-v4-flash) | No | Yes | Yes |
| [deepseek-v4-pro](https://oneaiapikey.com/models/deepseek-v4-pro) | No | Yes | Yes |

`json_schema` and `json_object` are values of `response_format` on [chat completions](https://oneaiapikey.com/docs/chat-completions). “Yes” means the provider documents support for it; “No (dropped)” is explained [below](https://oneaiapikey.com/docs/structured-output#claude-models).

## JSON Schema (recommended)

`response_format: {"type": "json_schema", ...}` with `strict: true` makes supporting models follow the schema exactly. With strict mode, list every property in `required` (use a `null` type for optional values) and set `additionalProperties: false`.

Python:

```python
import json, os
from openai import OpenAI

client = OpenAI(base_url="https://api.oneaiapikey.com/v1", api_key=os.environ["RELAYKIT_API_KEY"])

schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "email": {"type": ["string", "null"]},
        "topics": {"type": "array", "items": {"type": "string"}},
    },
    "required": ["name", "email", "topics"],
    "additionalProperties": False,
}

reply = client.chat.completions.create(
    model="gpt-5.6-terra",
    messages=[{"role": "user", "content": "Extract the contact: 'Hi, I'm Dana Lee (dana@example.com), asking about invoices and refunds.'"}],
    response_format={"type": "json_schema", "json_schema": {"name": "contact", "strict": True, "schema": schema}},
)
contact = json.loads(reply.choices[0].message.content)
print(contact)
```

Node:

```js
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api.oneaiapikey.com/v1", apiKey: process.env.RELAYKIT_API_KEY });

const schema = {
  type: "object",
  properties: {
    name: { type: "string" },
    email: { type: ["string", "null"] },
    topics: { type: "array", items: { type: "string" } },
  },
  required: ["name", "email", "topics"],
  additionalProperties: false,
};

const reply = await client.chat.completions.create({
  model: "gpt-5.6-terra",
  messages: [{ role: "user", content: "Extract the contact: 'Hi, I'm Dana Lee (dana@example.com), asking about invoices and refunds.'" }],
  response_format: { type: "json_schema", json_schema: { name: "contact", strict: true, schema } },
});
console.log(JSON.parse(reply.choices[0].message.content));
```

In the [Responses API](https://oneaiapikey.com/docs/responses) the same schema goes in `text.format` instead of `response_format`.

## JSON mode

`response_format: {"type": "json_object"}` only promises valid JSON, not a particular shape. Describe the shape in the prompt, and include the word “JSON” in it (some providers reject the request otherwise). Prefer JSON Schema where the model supports it.

## Claude models

> **response_format is not forwarded to Claude:** When you call a Claude model through `/v1/chat/completions`, the gateway translates the request to Anthropic’s format, and `response_format` has no counterpart there, so it is **dropped without an error**. The model then answers in free text, which may or may not be JSON. Use one of the two methods below instead.

**Forced tool call (OpenAI format).** Define one tool whose parameters are your schema and force it with `tool_choice`. The arguments of the tool call are your JSON. This works for every model with tool support, so it is also the most portable method.

Forced tool, works for Claude and others:

```python
reply = client.chat.completions.create(
    model="claude-sonnet-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Extract the contact: 'Hi, I'm Dana Lee (dana@example.com), asking about invoices and refunds.'"}],
    tools=[{"type": "function", "function": {
        "name": "save_contact",
        "description": "Save the extracted contact.",
        "parameters": schema,           # the same JSON Schema as above
    }}],
    tool_choice={"type": "function", "function": {"name": "save_contact"}},
)
contact = json.loads(reply.choices[0].message.tool_calls[0].function.arguments)
```

**Native structured output (Anthropic format).** On [`/v1/messages`](https://oneaiapikey.com/docs/anthropic-messages), Anthropic’s `output_config.format` with a JSON Schema is passed through to Claude, and the reply text is JSON that matches the schema.

Anthropic output_config:

```python
import anthropic, json, os

client = anthropic.Anthropic(base_url="https://api.oneaiapikey.com", api_key=os.environ["RELAYKIT_API_KEY"])

message = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Extract the contact: 'Hi, I'm Dana Lee (dana@example.com), asking about invoices and refunds.'"}],
    extra_body={"output_config": {"format": {"type": "json_schema", "schema": schema}}},
)
contact = json.loads(message.content[0].text)
```

## Always validate

Even with strict schemas, check the result before you use it: a reply cut off by `max_tokens` (`finish_reason: "length"`) is incomplete JSON, and a model can refuse instead of answering. Parse inside a `try`, validate with your schema library (Pydantic, Zod, JSON Schema), and retry or fall back when it fails.

## Related

- [Tool calling](https://oneaiapikey.com/docs/tool-calling): the loop behind forced tools.
- [Models](https://oneaiapikey.com/docs/models#current-models): capabilities of each model.
