# OpenAI Structured Outputs: JSON Schema

> Constrain Responses API output with text.format json_schema, parse straight into Pydantic or Zod, and use the same strict schemas for function calling.

- Canonical: https://guides-ai.pages.dev/guides/openai-api-structured-outputs/
- Plate 15.07 · Topic: Building with the API (https://guides-ai.pages.dev/topics/api/)
- Published: 06 Sept 2026 · 4 min read
- Source site: guides-ai — https://guides-ai.pages.dev/

Structured Outputs make the model's reply conform to a JSON schema you supply. It is enforcement, not prompting — so you can drop the retry-on-bad-JSON wrapper.

## Raw JSON schema

```bash
curl https://api.openai.com/v1/responses \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-6-astra",
    "input": "Extract: John Smith (john@example.com) wants the Enterprise plan.",
    "text": {
      "format": {
        "type": "json_schema",
        "name": "contact",
        "strict": true,
        "schema": {
          "type": "object",
          "properties": {
            "name": {"type": "string"},
            "email": {"type": "string"},
            "plan": {"type": "string"}
          },
          "required": ["name", "email", "plan"],
          "additionalProperties": false
        }
      }
    }
  }'
```

The format object needs all four fields: `type`, a `name` for the schema, the `schema` itself, and `strict: true`. Under `strict`, every property must be listed in `required` and `additionalProperties` must be `false`.

## Python with Pydantic

```python
from pydantic import BaseModel
from openai import OpenAI

class Contact(BaseModel):
    name: str
    email: str
    plan: str

client = OpenAI()  # reads OPENAI_API_KEY

response = client.responses.parse(
    model="gpt-6-astra",
    input="Extract: Jane Doe (jane@co.com) wants Enterprise.",
    text_format=Contact,
)

contact = response.output_parsed
print(contact.email)  # jane@co.com
```

`responses.parse()` builds the schema from the Pydantic model, sends it, and returns a validated `Contact` on `output_parsed`. No `json.loads`, no schema written twice.

## JavaScript with Zod

```javascript
import OpenAI from "openai";
import { zodTextFormat } from "openai/helpers/zod";
import { z } from "zod";

const Contact = z.object({
  name: z.string(),
  email: z.string(),
  plan: z.string(),
});

const client = new OpenAI(); // reads OPENAI_API_KEY

const response = await client.responses.parse({
  model: "gpt-6-astra",
  input: "Extract: Jane Doe (jane@co.com) wants Enterprise.",
  text: { format: zodTextFormat(Contact, "contact") },
});

console.log(response.output_parsed.email);
```

`zodTextFormat(schema, name)` converts the Zod object into the wire format and gives `output_parsed` its type.

## The same schemas as function tools

```python
tools = [{
    "type": "function",
    "name": "get_weather",
    "description": "Retrieves current weather for the given location.",
    "strict": True,
    "parameters": {
        "type": "object",
        "properties": {"location": {"type": "string"}},
        "required": ["location"],
        "additionalProperties": False,
    },
}]

response = client.responses.create(model="gpt-6-astra", input="Weather in Paris?", tools=tools)

for item in response.output:
    if item.type == "function_call":
        print(item.name, item.arguments)  # get_weather {"location":"Paris, France"}
```

Function tools sit flat on the tool object (`type`, `name`, `description`, `parameters`, `strict`) — there is no nested `function` wrapper in the Responses API. `arguments` arrives as a **JSON string**, so parse it, then send the result back as `{"type": "function_call_output", "call_id": item.call_id, "output": "..."}` in the next `input`.

## Refusals

```python
for item in response.output:
    if item.type == "refusal":
        print("Model declined:", item.refusal)
```

A safety refusal is a distinct output item, not malformed JSON — check for it before assuming your parse failed.

---

Next: [your first OpenAI request](/guides/openai-api-first-request/) · [reliable JSON from Claude](/guides/claude-api-structured-output/).
