Ensure text responses from the model adhere to a JSON schema you define.
JSON is one of the most widely used formats in the world for applications to exchange data.
Structured Outputs is a feature that ensures the model will always generate responses that adhere to your supplied JSON Schema, so you don’t need to worry about the model omitting a required key, or hallucinating an invalid enum value.
Some benefits of Structured Outputs include:
Reliable type-safety: No need to validate or retry incorrectly formatted responses
Explicit refusals: Safety-based model refusals are now programmatically detectable
Simpler prompting: No need for strongly worded prompts to achieve consistent formatting
In addition to supporting JSON Schema in the REST API, the OpenAI SDKs for Python and JavaScript also make it easy to define object schemas using Pydantic and Zod respectively. Below, you can see how to extract information from unstructured text that conforms to a schema defined in code.
Getting a structured response
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import OpenAI from "openai";import { zodResponseFormat } from "openai/helpers/zod";import { z } from "zod";const openai = new OpenAI();const CalendarEvent = z.object({ name: z.string(), date: z.string(), participants: z.array(z.string()),});const completion = await openai.chat.completions.parse({ model: "gpt-5.6", messages: [ { role: "system", content: "Extract the event information." }, { role: "user", content: "Alice and Bob are going to a science fair on Friday.", }, ], response_format: zodResponseFormat(CalendarEvent, "event"),});const event = completion.choices[0].message.parsed;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25from pydantic import BaseModelfrom openai import OpenAIclient = OpenAI()classCalendarEvent(BaseModel): name: str date: str participants: list[str]completion = client.chat.completions.parse(model="gpt-5.6",messages=[ {"role": "system", "content": "Extract the event information."}, {"role": "user","content": "Alice and Bob are going to a science fair on Friday.", }, ],response_format=CalendarEvent,)event = completion.choices[0].message.parsed
Structured Outputs is available in our latest large language models, starting with GPT-4o. For new projects, start with gpt-5.6. Older models like gpt-4-turbo and earlier may use JSON mode instead.
When to use Structured Outputs via function calling vs via
response_format
When to use Structured Outputs via function calling vs via
text.format
Structured Outputs is available in two forms in the OpenAI API:
Function calling is useful when you are building an application that bridges the models and functionality of your application.
For example, you can give the model access to functions that query a database in order to build an AI assistant that can help users with their orders, or functions that can interact with the UI.
Conversely, Structured Outputs via response_format are more suitable when you want to indicate a structured schema for use when the model responds to the user, rather than when the model calls a tool.
For example, if you are building a math tutoring application, you might want the assistant to respond to your user using a specific JSON Schema so that you can generate a UI that displays different parts of the model’s output in distinct ways.
Put simply:
If you are connecting the model to tools, functions, data, etc. in your
system, then you should use function calling - If you want to structure the
model’s output when it responds to the user, then you should use a structured
response_format
If you are connecting the model to tools, functions, data, etc. in your
system, then you should use function calling - If you want to structure the
model’s output when it responds to the user, then you should use a structured
text.format
The remainder of this guide will focus on non-function calling use cases in
the Chat Completions API. To learn more about how to use Structured Outputs
with function calling, check out the
The remainder of this guide will focus on non-function calling use cases in
the Responses API. To learn more about how to use Structured Outputs with
function calling, check out the
Structured Outputs is the evolution of JSON mode. While both ensure valid JSON is produced, only Structured Outputs ensure schema adherence. Both Structured Outputs and JSON mode are supported in the Responses API, Chat Completions API, Assistants API, Fine-tuning API and Batch API.
We recommend always using Structured Outputs instead of JSON mode when possible.
However, Structured Outputs with response_format: {type: "json_schema", ...} is only supported with the gpt-4o-mini, gpt-4o-mini-2024-07-18, and gpt-4o-2024-08-06 model snapshots and later.
{ "steps": [ { "explanation": "Start with the equation 8x + 7 = -23.", "output": "8x + 7 = -23" }, { "explanation": "Subtract 7 from both sides to isolate the term with the variable.", "output": "8x = -23 - 7" }, { "explanation": "Simplify the right side of the equation.", "output": "8x = -30" }, { "explanation": "Divide both sides by 8 to solve for x.", "output": "x = -30 / 8" }, { "explanation": "Simplify the fraction.", "output": "x = -15 / 4" } ], "final_answer": "x = -15 / 4"}
Structured data extraction
Structured data extraction
You can define structured fields to extract from unstructured input data, such as research papers.
Extracting data from research papers using Structured Outputs
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30import OpenAI from "openai";import { z } from "zod";import { zodResponseFormat } from "openai/helpers/zod";const openai = new OpenAI();const ResearchPaperExtraction = z.object({ title: z.string(), authors: z.array(z.string()), abstract: z.string(), keywords: z.array(z.string()),});const completion = await openai.chat.completions.parse({ model: "gpt-5.6", messages: [ { role: "system", content: "You are an expert at structured data extraction. You will be given unstructured text from a research paper and should convert it into the given structure.", }, { role: "user", content: "..." }, ], response_format: zodResponseFormat( ResearchPaperExtraction, "research_paper_extraction" ),});const research_paper = completion.choices[0].message.parsed;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36from pydantic import BaseModelfrom openai import OpenAIclient = OpenAI()classResearchPaperExtraction(BaseModel): title: str authors: list[str] abstract: str keywords: list[str]completion = client.chat.completions.parse(model="gpt-5.6",messages=[ {"role": "system","content": "You are an expert at structured data extraction. You will be given unstructured text from a research paper and should convert it into the given structure.", }, {"role": "user","content": ("Attention Is All You Need by Ashish Vaswani, Noam Shazeer, ""Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, ""Łukasz Kaiser, and Illia Polosukhin. We propose the ""Transformer, a sequence transduction architecture based ""entirely on attention. Keywords: transformers, attention, ""sequence transduction." ), }, ],response_format=ResearchPaperExtraction,)research_paper = completion.choices[0].message.parsed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48package mainimport ( "context" "fmt" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/shared")const researchPaperText = "Attention Is All You Need by Ashish Vaswani, Noam Shazeer, " + "Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, " + "Łukasz Kaiser, and Illia Polosukhin. We propose the Transformer, " + "a sequence transduction architecture based entirely on attention. " + "Keywords: transformers, attention, sequence transduction."func main() { client := openai.NewClient() schema := map[string]any{ "type": "object", "properties": map[string]any{ "title": map[string]any{"type": "string"}, "authors": map[string]any{"type": "array", "items": map[string]any{"type": "string"}}, "abstract": map[string]any{"type": "string"}, "keywords": map[string]any{"type": "array", "items": map[string]any{"type": "string"}}, }, "required": []string{"title", "authors", "abstract", "keywords"}, "additionalProperties": false, } completion, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{ Model: "gpt-5.6", Messages: []openai.ChatCompletionMessageParamUnion{ openai.SystemMessage("You are an expert at structured data extraction. You will be given unstructured text from a research paper and should convert it into the given structure."), openai.UserMessage(researchPaperText), }, ResponseFormat: openai.ChatCompletionNewParamsResponseFormatUnion{ OfJSONSchema: &shared.ResponseFormatJSONSchemaParam{JSONSchema: shared.ResponseFormatJSONSchemaJSONSchemaParam{ Name: "research_paper_extraction", Schema: schema, Strict: openai.Bool(true), }}, }, }) if err != nil { panic(err) } fmt.Println(completion.Choices[0].Message.Content)}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40curl https://api.openai.com/v1/chat/completions \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5.6", "messages": [ { "role": "system", "content": "You are an expert at structured data extraction. You will be given unstructured text from a research paper and should convert it into the given structure." }, { "role": "user", "content": "..." } ], "response_format": { "type": "json_schema", "json_schema": { "name": "research_paper_extraction", "schema": { "type": "object", "properties": { "title": { "type": "string" }, "authors": { "type": "array", "items": { "type": "string" } }, "abstract": { "type": "string" }, "keywords": { "type": "array", "items": { "type": "string" } } }, "required": ["title", "authors", "abstract", "keywords"], "additionalProperties": false }, "strict": true } } }'
Extracting data from research papers using Structured Outputs
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import OpenAI from "openai";import { zodTextFormat } from "openai/helpers/zod";import { z } from "zod";const openai = new OpenAI();const ResearchPaperExtraction = z.object({ title: z.string(), authors: z.array(z.string()), abstract: z.string(), keywords: z.array(z.string()),});const response = await openai.responses.parse({ model: "gpt-5.6", input: [ { role: "system", content: "You are an expert at structured data extraction. You will be given unstructured text from a research paper and should convert it into the given structure.", }, { role: "user", content: "..." }, ], text: { format: zodTextFormat(ResearchPaperExtraction, "research_paper_extraction"), },});const research_paper = response.output_parsed;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36from openai import OpenAIfrom pydantic import BaseModelclient = OpenAI()classResearchPaperExtraction(BaseModel): title: str authors: list[str] abstract: str keywords: list[str]response = client.responses.parse(model="gpt-5.6",input=[ {"role": "system","content": "You are an expert at structured data extraction. You will be given unstructured text from a research paper and should convert it into the given structure.", }, {"role": "user","content": ("Attention Is All You Need by Ashish Vaswani, Noam Shazeer, ""Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, ""Łukasz Kaiser, and Illia Polosukhin. We propose the ""Transformer, a sequence transduction architecture based ""entirely on attention. Keywords: transformers, attention, ""sequence transduction." ), }, ],text_format=ResearchPaperExtraction,)research_paper = response.output_parsed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52package mainimport ( "context" "fmt" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/responses")const researchPaperText = "Attention Is All You Need by Ashish Vaswani, Noam Shazeer, " + "Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, " + "Łukasz Kaiser, and Illia Polosukhin. We propose the Transformer, " + "a sequence transduction architecture based entirely on attention. " + "Keywords: transformers, attention, sequence transduction."func main() { client := openai.NewClient() schema := map[string]any{ "type": "object", "properties": map[string]any{ "title": map[string]any{"type": "string"}, "authors": map[string]any{"type": "array", "items": map[string]any{"type": "string"}}, "abstract": map[string]any{"type": "string"}, "keywords": map[string]any{"type": "array", "items": map[string]any{"type": "string"}}, }, "required": []string{"title", "authors", "abstract", "keywords"}, "additionalProperties": false, } response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", Input: responses.ResponseNewParamsInputUnion{OfInputItemList: responses.ResponseInputParam{ responses.ResponseInputItemParamOfMessage( responses.ResponseInputMessageContentListParam{responses.ResponseInputContentParamOfInputText("You are an expert at structured data extraction. You will be given unstructured text from a research paper and should convert it into the given structure.")}, responses.EasyInputMessageRoleSystem, ), responses.ResponseInputItemParamOfMessage( responses.ResponseInputMessageContentListParam{responses.ResponseInputContentParamOfInputText(researchPaperText)}, responses.EasyInputMessageRoleUser, ), }}, Text: responses.ResponseTextConfigParam{Format: responses.ResponseFormatTextConfigUnionParam{ OfJSONSchema: &responses.ResponseFormatTextJSONSchemaConfigParam{Name: "research_paper_extraction", Schema: schema, Strict: openai.Bool(true)}, }}, }) if err != nil { panic(err) } fmt.Println(response.OutputText())}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40curl https://api.openai.com/v1/responses \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5.6", "input": [ { "role": "system", "content": "You are an expert at structured data extraction. You will be given unstructured text from a research paper and should convert it into the given structure." }, { "role": "user", "content": "..." } ], "text": { "format": { "type": "json_schema", "name": "research_paper_extraction", "schema": { "type": "object", "properties": { "title": { "type": "string" }, "authors": { "type": "array", "items": { "type": "string" } }, "abstract": { "type": "string" }, "keywords": { "type": "array", "items": { "type": "string" } } }, "required": ["title", "authors", "abstract", "keywords"], "additionalProperties": false }, "strict": true } } }'
Example response
{ "title": "Application of Quantum Algorithms in Interstellar Navigation: A New Frontier", "authors": ["Dr. Stella Voyager", "Dr. Nova Star", "Dr. Lyra Hunter"], "abstract": "This paper investigates the utilization of quantum algorithms to improve interstellar navigation systems. By leveraging quantum superposition and entanglement, our proposed navigation system can calculate optimal travel paths through space-time anomalies more efficiently than classical methods. Experimental simulations suggest a significant reduction in travel time and fuel consumption for interstellar missions.", "keywords": [ "Quantum algorithms", "interstellar navigation", "space-time anomalies", "quantum superposition", "quantum entanglement", "space travel" ]}
UI generation
UI Generation
You can generate valid HTML by representing it as recursive data structures with constraints, like enums.
Generating HTML using Structured Outputs
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33import OpenAI from "openai";import { z } from "zod";import { zodResponseFormat } from "openai/helpers/zod";const openai = new OpenAI();const UI = z.lazy(() => z.object({ type: z.enum(["div", "button", "header", "section", "field", "form"]), label: z.string(), children: z.array(UI), attributes: z.array( z.object({ name: z.string(), value: z.string(), }) ), }));const completion = await openai.chat.completions.parse({ model: "gpt-5.6", messages: [ { role: "system", content: "You are a UI generator AI. Convert the user input into a UI.", }, { role: "user", content: "Make a User Profile Form" }, ], response_format: zodResponseFormat(UI, "ui"),});const ui = completion.choices[0].message.parsed;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50from enum import Enumfrom typing import Listfrom pydantic import BaseModelfrom openai import OpenAIclient = OpenAI()classUIType(str, Enum): div ="div" button ="button" header ="header" section ="section" field ="field" form ="form"classAttribute(BaseModel): name: str value: strclassUI(BaseModel):type: UIType label: str children: List["UI"] attributes: List[Attribute]UI.model_rebuild() # This is required to enable recursive typesclassResponse(BaseModel): ui: UIcompletion = client.chat.completions.parse(model="gpt-5.6",messages=[ {"role": "system","content": "You are a UI generator AI. Convert the user input into a UI.", }, {"role": "user", "content": "Make a User Profile Form"}, ],response_format=Response,)ui = completion.choices[0].message.parsedprint(ui)
How to use Structured Outputs with response_format
You can use Structured Outputs with the new SDK helper to parse the model’s output into your desired format, or you can specify the JSON schema directly.
Note: for fine tuned models, the first request you make with any schema
will have additional latency as our API processes the schema, but subsequent
requests with the same schema will not have additional latency. Other models
do not have this limitation.
SDK objects
Step 1: Define your object
First you must define an object or data structure to represent the JSON Schema that the model should be constrained to follow. See the examples at the top of this guide for reference.
While Structured Outputs supports much of JSON Schema, some features are unavailable either for performance or technical reasons. See here for more details.
To maximize the quality of model generations, we recommend the following:
Name keys clearly and intuitively
Create clear titles and descriptions for important keys in your structure
Create and use evals to determine the structure that works best for your use case
Step 2: Supply your object in the API call
You can use the parse method to automatically parse the JSON response into the object you defined.
Under the hood, the SDK takes care of supplying the JSON schema corresponding to your data structure, and then parsing the response as an object.
Python
1
2
3
4
5
6
7
8
9
10
11
12const completion = await openai.chat.completions.parse({ model: "gpt-5.6", messages: [ { role: "system", content: "You are a helpful math tutor. Guide the user through the solution step by step.", }, { role: "user", content: "how can I solve 8x + 7 = -23" }, ], response_format: zodResponseFormat(MathResponse, "math_response"),});
1
2
3
4
5
6
7
8
9
10
11completion = client.chat.completions.parse(model="gpt-5.6",messages=[ {"role": "system","content": "You are a helpful math tutor. Guide the user through the solution step by step.", }, {"role": "user", "content": "how can I solve 8x + 7 = -23"}, ],response_format=MathResponse,)
Step 3: Handle edge cases
In some cases, the model might not generate a valid response that matches the provided JSON schema.
This can happen in the case of a refusal, if the model refuses to answer for safety reasons, or if for example you reach a max tokens limit and the response is incomplete.
First you must design the JSON Schema that the model should be constrained to follow. See the examples at the top of this guide for reference.
While Structured Outputs supports much of JSON Schema, some features are unavailable either for performance or technical reasons. See here for more details.
Tips for your JSON Schema
To maximize the quality of model generations, we recommend the following:
Name keys clearly and intuitively
Create clear titles and descriptions for important keys in your structure
Create and use evals to determine the structure that works best for your use case
Note: the first request you make with any schema will have additional latency as our API processes the schema, but subsequent requests with the same schema will not have additional latency.
Step 3: Handle edge cases
In some cases, the model might not generate a valid response that matches the provided JSON schema.
This can happen in the case of a refusal, if the model refuses to answer for safety reasons, or if for example you reach a max tokens limit and the response is incomplete.
Step 4: Use the generated structured data in a type-safe way
Typically, when using Structured Outputs you will have a type or class in the type-system of your programming language representing the JSON Schema as an object.
Once you have confirmed that you have received the JSON guaranteed to match the schema you requested, you can now safely parse it to the corresponding type.
For example:
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// Here we specify types in TypeScript that exactly match the JSON Schema we provided when calling the OpenAI API. Note that these _must_ be kept in sync.type Step = { explanation: string; output: string;};type Solution = { steps: Step[]; final_answer: string;};// The request that produces `response` appears earlier in this guide.// Now so long as the JSON Schema we created was exactly equivalent to our TypeScript types, this is type-safeconst content = response.choices[0].message.content;if (!content) throw new Error("The response did not contain JSON output.");const solution = JSON.parse(content) as Solution;
First you must design the JSON Schema that the model should be constrained to follow. See the examples at the top of this guide for reference.
While Structured Outputs supports much of JSON Schema, some features are unavailable either for performance or technical reasons. See here for more details.
Tips for your JSON Schema
To maximize the quality of model generations, we recommend the following:
Name keys clearly and intuitively
Create clear titles and descriptions for important keys in your structure
Create and use evals to determine the structure that works best for your use case
Note: the first request you make with any schema will have additional latency as our API processes the schema, but subsequent requests with the same schema will not have additional latency.
Step 3: Handle edge cases
In some cases, the model might not generate a valid response that matches the provided JSON schema.
This can happen in the case of a refusal, if the model refuses to answer for safety reasons, or if for example you reach a max tokens limit and the response is incomplete.
Step 4: Use the generated structured data in a type-safe way
Typically, when using Structured Outputs you will have a type or class in the type-system of your programming language representing the JSON Schema as an object.
Once you have confirmed that you have received the JSON guaranteed to match the schema you requested, you can now safely parse it to the corresponding type.
For example:
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// Here we specify types in TypeScript that exactly match the JSON Schema we provided when calling the OpenAI API. Note that these _must_ be kept in sync.type Step = { explanation: string; output: string;};type Solution = { steps: Step[]; final_answer: string;};// The request that produces `response` appears earlier in this guide.// Now so long as the JSON Schema we created was exactly equivalent to our TypeScript types, this is type-safeconst content = response.choices[0].message.content;if (!content) throw new Error("The response did not contain JSON output.");const solution = JSON.parse(content) as Solution;
When using Structured Outputs with user-generated input, OpenAI models may occasionally refuse to fulfill the request for safety reasons. Since a refusal does not necessarily follow the schema you have supplied in response_format, the API response will include a new field called refusal to indicate that the model refused to fulfill the request.
When the refusal property appears in your output object, you might present the refusal in your UI, or include conditional logic in code that consumes the response to handle the case of a refused request.
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31const Step = z.object({ explanation: z.string(), output: z.string(),});const MathReasoning = z.object({ steps: z.array(Step), final_answer: z.string(),});const completion = await openai.chat.completions.parse({ model: "gpt-5.6", messages: [ { role: "system", content: "You are a helpful math tutor. Guide the user through the solution step by step.", }, { role: "user", content: "how can I solve 8x + 7 = -23" }, ], response_format: zodResponseFormat(MathReasoning, "math_reasoning"),});const math_reasoning = completion.choices[0].message;// If the model refuses to respond, you will get a refusal messageif (math_reasoning.refusal) { console.log(math_reasoning.refusal);} else { console.log(math_reasoning.parsed);}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30classStep(BaseModel): explanation: str output: strclassMathReasoning(BaseModel): steps: list[Step] final_answer: strcompletion = client.chat.completions.parse(model="gpt-5.6",messages=[ {"role": "system","content": "You are a helpful math tutor. Guide the user through the solution step by step.", }, {"role": "user", "content": "how can I solve 8x + 7 = -23"}, ],response_format=MathReasoning,)math_reasoning = completion.choices[0].message# If the model refuses to respond, you will get a refusal messageif math_reasoning.refusal:print(math_reasoning.refusal)else:print(math_reasoning.parsed)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44const Step = z.object({ explanation: z.string(), output: z.string(),});const MathReasoning = z.object({ steps: z.array(Step), final_answer: z.string(),});const response = await openai.responses.parse({ model: "gpt-5.6", input: [ { role: "system", content: "You are a helpful math tutor. Guide the user through the solution step by step.", }, { role: "user", content: "how can I solve 8x + 7 = -23" }, ], text: { format: zodTextFormat(MathReasoning, "math_response"), },});for (const output of response.output) { if (output.type !== "message") { continue; } for (const item of output.content) { if (item.type == "refusal") { // If the model refuses to respond, you will get a refusal message console.log(item.refusal); continue; } if (!item.parsed) { throw new Error("Could not parse response"); } console.log(item.parsed); }}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36classStep(BaseModel): explanation: str output: strclassMathReasoning(BaseModel): steps: list[Step] final_answer: strresponse = client.responses.parse(model="gpt-5.6",input=[ {"role": "system","content": "You are a helpful math tutor. Guide the user through the solution step by step.", }, {"role": "user", "content": "how can I solve 8x + 7 = -23"}, ],text_format=MathReasoning,)for output in response.output:if output.type !="message":continuefor item in output.content:if item.type =="refusal":# If the model refuses to respond, you will get a refusal messageprint(item.refusal)continueifnot item.parsed:raiseException("Could not parse response")print(item.parsed)
If your application is using user-generated input, make sure your prompt includes instructions on how to handle situations where the input cannot result in a valid response.
The model will always try to adhere to the provided schema, which can result in hallucinations if the input is completely unrelated to the schema.
You could include language in your prompt to specify that you want to return empty parameters, or a specific sentence, if the model detects that the input is incompatible with the task.
Handling mistakes
Structured Outputs can still contain mistakes. If you see mistakes, try adjusting your instructions, providing examples in the system instructions, or splitting tasks into simpler subtasks. Refer to the prompt engineering guide for more guidance on how to tweak your inputs.
Avoid JSON schema divergence
To prevent your JSON Schema and corresponding types in your programming language from diverging, we strongly recommend using the native Pydantic/zod sdk support.
If you prefer to specify the JSON schema directly, you could add CI rules that flag when either the JSON schema or underlying data objects are edited, or add a CI step that auto-generates the JSON Schema from type definitions (or vice-versa).
Streaming
You can use streaming to process model responses or function call arguments as they are being generated, and parse them as structured data.
That way, you don’t have to wait for the entire response to complete before handling it.
This is particularly useful if you would like to display JSON fields one by one, or handle function call arguments as soon as they are available.
We recommend relying on the SDKs to handle streaming with Structured Outputs.
You can find an example of how to stream function call arguments without the SDK stream helper in the function calling guide.
Here is how you can stream a model response with the stream helper:
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40import OpenAI from "openai";import { zodResponseFormat } from "openai/helpers/zod";import { z } from "zod";const openai = new OpenAI();const EntitiesSchema = z.object({ attributes: z.array(z.string()), colors: z.array(z.string()), animals: z.array(z.string()),});const stream = openai.chat.completions .stream({ model: "gpt-5.6", messages: [ { role: "system", content: "Extract entities from the input text" }, { role: "user", content: "The quick brown fox jumps over the lazy dog with piercing blue eyes", }, ], response_format: zodResponseFormat(EntitiesSchema, "entities"), }) .on("refusal.done", () => console.log("request refused")) .on("content.delta", ({ snapshot, parsed }) => { console.log("content:", snapshot); console.log("parsed:", parsed); console.log(); }) .on("content.done", (props) => { console.log(props); });await stream.done();const finalCompletion = await stream.finalChatCompletion();console.log(finalCompletion);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35from typing import Listfrom pydantic import BaseModelfrom openai import OpenAIclassEntitiesModel(BaseModel): attributes: List[str] colors: List[str] animals: List[str]client = OpenAI()with client.beta.chat.completions.stream(model="gpt-5.6",messages=[ {"role": "system", "content": "Extract entities from the input text"}, {"role": "user","content": "The quick brown fox jumps over the lazy dog with piercing blue eyes", }, ],response_format=EntitiesModel,) as stream:for event in stream:if event.type =="content.delta":if event.parsed isnotNone: # Print the parsed data as JSONprint("content.delta parsed:", event.parsed)elif event.type =="content.done":print("content.done")elif event.type =="error":print("Error in stream:", event.error)final_completion = stream.get_final_completion()print("Final completion:", final_completion)
You can also use the stream helper to parse function call arguments:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37from typing import Listfrom openai import OpenAIfrom pydantic import BaseModelclassEntitiesModel(BaseModel): attributes: List[str] colors: List[str] animals: List[str]client = OpenAI()with client.responses.stream(model="gpt-5.6",input=[ {"role": "system", "content": "Extract entities from the input text"}, {"role": "user","content": "The quick brown fox jumps over the lazy dog with piercing blue eyes", }, ],text_format=EntitiesModel,) as stream:for event in stream:if event.type =="response.refusal.delta":print(event.delta, end="")elif event.type =="response.output_text.delta":print(event.delta, end="")elif event.type =="response.error":print(event.error, end="")elif event.type =="response.completed":print("Completed") # print(event.response.output) final_response = stream.get_final_response()print(final_response)
Supported schemas
Structured Outputs supports a subset of the JSON Schema language.
Supported types
The following types are supported for Structured Outputs:
String
Number
Boolean
Integer
Object
Array
Enum
anyOf
Supported properties
In addition to specifying the type of a property, you can specify a selection of additional constraints:
Supported string properties:
pattern — A regular expression that the string must match.
format — Predefined formats for strings. Currently supported:
date-time
time
date
duration
email
hostname
ipv4
ipv6
uuid
Supported number properties:
multipleOf — The number must be a multiple of this value.
maximum — The number must be less than or equal to this value.
exclusiveMaximum — The number must be less than this value.
minimum — The number must be greater than or equal to this value.
exclusiveMinimum — The number must be greater than this value.
Supported array properties:
minItems — The array must have at least this many items.
maxItems — The array must have at most this many items.
Here are some examples on how you can use these type restrictions:
String Restrictions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27{"name": "user_data","strict": true,"schema": {"type": "object","properties": {"name": {"type": "string","description": "The name of the user" },"username": {"type": "string","description": "The username of the user. Must start with @","pattern": "^@[a-zA-Z0-9_]+$" },"email": {"type": "string","description": "The email of the user","format": "email" } },"additionalProperties": false,"required": ["name", "username", "email" ] }}
Number Restrictions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28{"name": "weather_data","strict": true,"schema": {"type": "object","properties": {"location": {"type": "string","description": "The location to get the weather for" },"unit": {"type": ["string", "null"],"description": "The unit to return the temperature in","enum": ["F", "C"] },"value": {"type": "number","description": "The actual temperature value in the location","minimum": -130,"maximum": 130 } },"additionalProperties": false,"required": ["location", "unit", "value" ] }}
Root objects must not be anyOf and must be an object
Note that the root level object of a schema must be an object, and not use anyOf. A pattern that appears in Zod (as one example) is using a discriminated union, which produces an anyOf at the top level. So code such as the following won’t work:
To use Structured Outputs, all fields or function parameters must be specified as required.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21{"name": "get_weather","description": "Fetches the weather in the given location","strict": true,"parameters": {"type": "object","properties": {"location": {"type": "string","description": "The location to get the weather for" },"unit": {"type": "string","description": "The unit to return the temperature in","enum": ["F", "C"] } },"additionalProperties": false,"required": ["location", "unit"] }}
Although all fields must be required (and the model will return a value for each parameter), it is possible to emulate an optional parameter by using a union type with null.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23{"name": "get_weather","description": "Fetches the weather in the given location","strict": true,"parameters": {"type": "object","properties": {"location": {"type": "string","description": "The location to get the weather for" },"unit": {"type": ["string", "null"],"description": "The unit to return the temperature in","enum": ["F", "C"] } },"additionalProperties": false,"required": ["location", "unit" ] }}
Objects have limitations on nesting depth and size
A schema may have up to 5000 object properties total, with up to 10 levels of nesting.
Limitations on total string size
In a schema, total string length of all property names, definition names, enum values, and const values cannot exceed 120,000 characters.
Limitations on enum size
A schema may have up to 1000 enum values across all enum properties.
For a single enum property with string values, the total string length of all enum values cannot exceed 15,000 characters when there are more than 250 enum values.
additionalProperties: false must always be set in objects
additionalProperties controls whether it is allowable for an object to contain additional keys / values that were not defined in the JSON Schema.
Structured Outputs only supports generating specified keys / values, so we require developers to set additionalProperties: false to opt into Structured Outputs.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23{"name": "get_weather","description": "Fetches the weather in the given location","strict": true,"schema": {"type": "object","properties": {"location": {"type": "string","description": "The location to get the weather for" },"unit": {"type": "string","description": "The unit to return the temperature in","enum": ["F", "C"] } },"additionalProperties": false,"required": ["location", "unit" ] }}
Key ordering
When using Structured Outputs, outputs will be produced in the same order as the ordering of keys in the schema.
For fine-tuned models, we additionally do not support the following:
For strings:minLength, maxLength, pattern, format
For numbers:minimum, maximum, multipleOf
For objects:patternProperties
For arrays:minItems, maxItems
If you turn on Structured Outputs by supplying strict: true and call the API with an unsupported JSON Schema, you will receive an error.
For anyOf, the nested schemas must each be a valid JSON Schema per this subset
Here’s an example supported anyOf schema:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56{"type": "object","properties": {"item": {"anyOf": [ {"type": "object","description": "The user object to insert into the database","properties": {"name": {"type": "string","description": "The name of the user" },"age": {"type": "number","description": "The age of the user" } },"additionalProperties": false,"required": ["name","age" ] }, {"type": "object","description": "The address object to insert into the database","properties": {"number": {"type": "string","description": "The number of the address. Eg. for 123 main st, this would be 123" },"street": {"type": "string","description": "The street name. Eg. for 123 main st, this would be main st" },"city": {"type": "string","description": "The city of the address" } },"additionalProperties": false,"required": ["number","street","city" ] } ] } },"additionalProperties": false,"required": ["item" ]}
Definitions are supported
You can use definitions to define subschemas which are referenced throughout your schema. The following is a simple example.
JSON mode is a more basic version of the Structured Outputs feature. While
JSON mode ensures that model output is valid JSON, Structured Outputs reliably
matches the model’s output to the schema you specify. We recommend you use
Structured Outputs if it is supported for your use case.
When JSON mode is turned on, the model’s output is ensured to be valid JSON, except for in some edge cases that you should detect and handle appropriately.
To turn on JSON mode with the Chat Completions, set the response_format to { "type": "json_object" }. If you are using function calling, JSON mode is always turned on.
To turn on JSON mode with the Responses API you can set the text.format to { "type": "json_object" }. If you are using function calling, JSON mode is always turned on.
Important notes:
When using JSON mode, you must always instruct the model to produce JSON via some message in the conversation, for example via your system message. If you don’t include an explicit instruction to generate JSON, the model may generate an unending stream of whitespace and the request may run continually until it reaches the token limit. To help ensure you don’t forget, the API will throw an error if the string “JSON” does not appear somewhere in the context.
JSON mode will not guarantee the output matches any specific schema, only that it is valid and parses without errors. You should use Structured Outputs to ensure it matches your schema, or if that is not possible, you should use a validation library and potentially retries to ensure that the output matches your desired schema.
Your application must detect and handle the edge cases that can result in the model output not being a complete JSON object (see below)
Handling edge cases
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52const we_did_not_specify_stop_tokens = true;try { const response = await openai.chat.completions.create({ model: "gpt-5.6", messages: [ { role: "system", content: "You are a helpful assistant designed to output JSON.", }, { role: "user", content: "Who won the world series in 2020? Please respond in the format {winner: ...}", }, ], store: true, response_format: { type: "json_object" }, }); // Check if the conversation was too long for the context window, resulting in incomplete JSON if (response.choices[0].finish_reason === "length") { // your code should handle this error case } // Check if the OpenAI safety system refused the request and generated a refusal instead if (response.choices[0].message.refusal) { // your code should handle this error case // In this case, the .content field will contain the explanation (if any) that the model generated for why it is refusing console.log(response.choices[0].message.refusal); } // Check if the model's output included restricted content, so the generation of JSON was halted and may be partial if (response.choices[0].finish_reason === "content_filter") { // your code should handle this error case } if (response.choices[0].finish_reason === "stop") { // In this case the model has either successfully finished generating the JSON object according to your schema, or the model generated one of the tokens you provided as a "stop token" if (we_did_not_specify_stop_tokens) { // If you didn't specify any stop tokens, then the generation is complete and the content key will contain the serialized JSON object // This will parse successfully and should now contain {"winner": "Los Angeles Dodgers"} console.log(JSON.parse(response.choices[0].message.content)); } else { // Check if the response.choices[0].message.content ends with one of your stop tokens and handle appropriately } }} catch (e) { // Your code should handle errors here, for example a network error calling the API console.error(e);}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42we_did_not_specify_stop_tokens =Truetry: response = client.chat.completions.create(model="gpt-5.6",messages=[ {"role": "system","content": "You are a helpful assistant designed to output JSON.", }, {"role": "user","content": 'Who won the World Series in 2020? Respond as {"winner": "team name"}.', }, ],response_format={"type": "json_object"}, )# Check if the conversation was too long for the context window, resulting in incomplete JSONif response.choices[0].finish_reason =="length":raiseRuntimeError("The response was truncated before the JSON completed.")# Check if the OpenAI safety system refused the request and generated a refusal insteadif response.choices[0].message.refusal:# your code should handle this error case# In this case, the .content field will contain the explanation (if any) that the model generated for why it is refusingprint(response.choices[0].message.refusal)# Check if the model's output included restricted content, so the generation of JSON was halted and may be partialif response.choices[0].finish_reason =="content_filter":raiseRuntimeError("The response was interrupted by the content filter.")if response.choices[0].finish_reason =="stop":# In this case the model has either successfully finished generating the JSON object according to your schema, or the model generated one of the tokens you provided as a "stop token"if we_did_not_specify_stop_tokens:# If you didn't specify any stop tokens, then the generation is complete and the content key will contain the serialized JSON object# This will parse successfully and should now contain "{"winner": "Los Angeles Dodgers"}"print(response.choices[0].message.content)exceptExceptionas e:# Your code should handle errors here, for example a network error calling the APIprint(e)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44package mainimport ( "context" "encoding/json" "fmt" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/shared")func main() { client := openai.NewClient() completion, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{ Model: "gpt-5.6", Messages: []openai.ChatCompletionMessageParamUnion{ openai.SystemMessage("You are a helpful assistant designed to output JSON."), openai.UserMessage("Who won the world series in 2020? Please respond in the format {winner: ...}"), }, ResponseFormat: openai.ChatCompletionNewParamsResponseFormatUnion{ OfJSONObject: &shared.ResponseFormatJSONObjectParam{}, }, }) if err != nil { panic(err) } choice := completion.Choices[0] if choice.FinishReason == "length" || choice.FinishReason == "content_filter" { fmt.Println("The JSON response is incomplete.") return } if choice.Message.Refusal != "" { fmt.Println(choice.Message.Refusal) return } if choice.FinishReason == "stop" { var value map[string]any if err := json.Unmarshal([]byte(choice.Message.Content), &value); err != nil { panic(err) } fmt.Println(value) }}
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60const we_did_not_specify_stop_tokens = true;try { const response = await openai.responses.create({ model: "gpt-5.6", input: [ { role: "system", content: "You are a helpful assistant designed to output JSON.", }, { role: "user", content: "Who won the world series in 2020? Please respond in the format {winner: ...}", }, ], text: { format: { type: "json_object" } }, }); const message = response.output.find((item) => item.type === "message"); const messageContent = message?.content[0]; // Check if the conversation was too long for the context window, resulting in incomplete JSON if ( response.status === "incomplete" && response.incomplete_details.reason === "max_output_tokens" ) { // your code should handle this error case } // Check if the OpenAI safety system refused the request and generated a refusal instead if (messageContent?.type === "refusal") { // your code should handle this error case // In this case, the .content field will contain the explanation (if any) that the model generated for why it is refusing console.log(messageContent.refusal); } // Check if the model's output included restricted content, so the generation of JSON was halted and may be partial if ( response.status === "incomplete" && response.incomplete_details.reason === "content_filter" ) { // your code should handle this error case } if (response.status === "completed") { // In this case the model has either successfully finished generating the JSON object according to your schema, or the model generated one of the tokens you provided as a "stop token" if (we_did_not_specify_stop_tokens) { // If you didn't specify any stop tokens, then the generation is complete and the content key will contain the serialized JSON object // This will parse successfully and should now contain {"winner": "Los Angeles Dodgers"} console.log(JSON.parse(response.output_text)); } else { // Check if the response.output_text ends with one of your stop tokens and handle appropriately } }} catch (e) { // Your code should handle errors here, for example a network error calling the API console.error(e);}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51we_did_not_specify_stop_tokens =Truetry: response = client.responses.create(model="gpt-5.6",input=[ {"role": "system","content": "You are a helpful assistant designed to output JSON.", }, {"role": "user","content": 'Who won the World Series in 2020? Respond as {"winner": "team name"}.', }, ],text={"format": {"type": "json_object"}}, ) message =next((item for item in response.output if item.type =="message"), None) message_content = message.content[0] if message and message.content elseNone# Check if the conversation was too long for the context window, resulting in incomplete JSONif ( response.status =="incomplete"and response.incomplete_details.reason =="max_output_tokens" ):raiseRuntimeError("The response was truncated before the JSON completed.")# Check if the OpenAI safety system refused the request and generated a refusal insteadif message_content and message_content.type =="refusal":# your code should handle this error case# In this case, the .content field will contain the explanation (if any) that the model generated for why it is refusingprint(message_content.refusal)# Check if the model's output included restricted content, so the generation of JSON was halted and may be partialif ( response.status =="incomplete"and response.incomplete_details.reason =="content_filter" ):raiseRuntimeError("The response was interrupted by the content filter.")if response.status =="completed":# In this case the model has either successfully finished generating the JSON object according to your schema, or the model generated one of the tokens you provided as a "stop token"if we_did_not_specify_stop_tokens:# If you didn't specify any stop tokens, then the generation is complete and the content key will contain the serialized JSON object# This will parse successfully and should now contain "{"winner": "Los Angeles Dodgers"}"print(response.output_text)exceptExceptionas e:# Your code should handle errors here, for example a network error calling the APIprint(e)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57package mainimport ( "context" "encoding/json" "fmt" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/responses" "github.com/openai/openai-go/v3/shared")func main() { client := openai.NewClient() response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{ Model: "gpt-5.6", Input: responses.ResponseNewParamsInputUnion{OfInputItemList: responses.ResponseInputParam{ responses.ResponseInputItemParamOfMessage( responses.ResponseInputMessageContentListParam{responses.ResponseInputContentParamOfInputText("You are a helpful assistant designed to output JSON.")}, responses.EasyInputMessageRoleSystem, ), responses.ResponseInputItemParamOfMessage( responses.ResponseInputMessageContentListParam{responses.ResponseInputContentParamOfInputText("Who won the world series in 2020? Please respond in the format {winner: ...}")}, responses.EasyInputMessageRoleUser, ), }}, Text: responses.ResponseTextConfigParam{Format: responses.ResponseFormatTextConfigUnionParam{ OfJSONObject: &shared.ResponseFormatJSONObjectParam{}, }}, }) if err != nil { panic(err) } if response.Status == "incomplete" { fmt.Println("The JSON response is incomplete.") return } for _, output := range response.Output { if output.Type != "message" { continue } for _, content := range output.AsMessage().Content { if content.Type == "refusal" { fmt.Println(content.AsRefusal().Refusal) return } } } if response.Status == "completed" { var value map[string]any if err := json.Unmarshal([]byte(response.OutputText()), &value); err != nil { panic(err) } fmt.Println(value) }}
Resources
To learn more about Structured Outputs, we recommend browsing the following resources: