1# Structured model outputs1# Structured model outputs
2 2
3> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.
4
3JSON is one of the most widely used formats in the world for applications to exchange data.5JSON is one of the most widely used formats in the world for applications to exchange data.
4 6
5Structured Outputs is a feature that ensures the model will always generate responses that adhere to your supplied [JSON Schema](https://json-schema.org/overview/what-is-jsonschema), so you don't need to worry about the model omitting a required key, or hallucinating an invalid enum value.7Structured Outputs is a feature that ensures the model will always generate responses that adhere to your supplied [JSON Schema](https://json-schema.org/overview/what-is-jsonschema), so you don't need to worry about the model omitting a required key, or hallucinating an invalid enum value.
12 14
13In addition to supporting JSON Schema in the REST API, the OpenAI SDKs for [Python](https://github.com/openai/openai-python/blob/main/helpers.md#structured-outputs-parsing-helpers) and [JavaScript](https://github.com/openai/openai-node/blob/master/helpers.md#structured-outputs-parsing-helpers) also make it easy to define object schemas using [Pydantic](https://docs.pydantic.dev/latest/) and [Zod](https://zod.dev/) respectively. Below, you can see how to extract information from unstructured text that conforms to a schema defined in code.15In addition to supporting JSON Schema in the REST API, the OpenAI SDKs for [Python](https://github.com/openai/openai-python/blob/main/helpers.md#structured-outputs-parsing-helpers) and [JavaScript](https://github.com/openai/openai-node/blob/master/helpers.md#structured-outputs-parsing-helpers) also make it easy to define object schemas using [Pydantic](https://docs.pydantic.dev/latest/) and [Zod](https://zod.dev/) respectively. Below, you can see how to extract information from unstructured text that conforms to a schema defined in code.
14 16
17
18
19Getting a structured response
20
21```javascript
22import OpenAI from "openai";
23import { zodTextFormat } from "openai/helpers/zod";
24import { z } from "zod";
25
26const openai = new OpenAI();
27
28const CalendarEvent = z.object({
29 name: z.string(),
30 date: z.string(),
31 participants: z.array(z.string()),
32});
33
34const response = await openai.responses.parse({
35 model: "gpt-5.6",
36 input: [
37 { role: "system", content: "Extract the event information." },
38 {
39 role: "user",
40 content: "Alice and Bob are going to a science fair on Friday.",
41 },
42 ],
43 text: {
44 format: zodTextFormat(CalendarEvent, "event"),
45 },
46});
47
48const event = response.output_parsed;
49```
50
51```python
52from openai import OpenAI
53from pydantic import BaseModel
54
55client = OpenAI()
56
57
58class CalendarEvent(BaseModel):
59 name: str
60 date: str
61 participants: list[str]
62
63
64response = client.responses.parse(
65 model="gpt-5.6",
66 input=[
67 {"role": "system", "content": "Extract the event information."},
68 {
69 "role": "user",
70 "content": "Alice and Bob are going to a science fair on Friday.",
71 },
72 ],
73 text_format=CalendarEvent,
74)
75
76event = response.output_parsed
77```
78
79
80
15### Supported models81### Supported models
16 82
17Structured Outputs is available in our [latest large language models](https://developers.openai.com/api/docs/models), starting with GPT-4o. For new projects, start with [`gpt-5.6`](https://developers.openai.com/api/docs/models/gpt-5.6-sol). Older models like `gpt-4-turbo` and earlier may use [JSON mode](#json-mode) instead.83Structured Outputs is available in our [latest large language models](https://developers.openai.com/api/docs/models), starting with GPT-4o. For new projects, start with [`gpt-5.6`](https://developers.openai.com/api/docs/models/gpt-5.6-sol). Older models like `gpt-4-turbo` and earlier may use [JSON mode](#json-mode) instead.
22 88
23 89
24When to use Structured Outputs via function calling vs via 90When to use Structured Outputs via function calling vs via
25 <span className="monospace">text.format</span>91
92text.format
26 93
27 94
28 95
84 151
85 152
86 153
87<div data-content-switcher-pane data-value="chain-of-thought">154Chain of thought
88 <div class="hidden">Chain of thought</div>
89 </div>
90 <div data-content-switcher-pane data-value="structured-data" hidden>
91 <div class="hidden">Structured data extraction</div>
92 </div>
93 <div data-content-switcher-pane data-value="ui-generation" hidden>
94 <div class="hidden">UI generation</div>
95 </div>
96 <div data-content-switcher-pane data-value="moderation" hidden>
97 <div class="hidden">Moderation</div>
98 </div>
99 155
100 156
101 157
158### Chain of thought
102 159
160You can ask the model to output an answer in a structured, step-by-step way, to guide the user through the solution.
103 161
104 162
105 163
106 164
107How to use Structured Outputs with <span className="monospace">text.format</span>165 Structured Outputs for chain-of-thought math tutoring
108 166
167```javascript
168import OpenAI from "openai";
169import { zodTextFormat } from "openai/helpers/zod";
170import { z } from "zod";
109 171
110Refusals with Structured Outputs172const openai = new OpenAI();
111 173
174const Step = z.object({
175 explanation: z.string(),
176 output: z.string(),
177});
112 178
179const MathReasoning = z.object({
180 steps: z.array(Step),
181 final_answer: z.string(),
182});
113 183
114When 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.184const response = await openai.responses.parse({
185 model: "gpt-5.6",
186 input: [
187 {
188 role: "system",
189 content:
190 "You are a helpful math tutor. Guide the user through the solution step by step.",
191 },
192 { role: "user", content: "how can I solve 8x + 7 = -23" },
193 ],
194 text: {
195 format: zodTextFormat(MathReasoning, "math_reasoning"),
196 },
197});
115 198
116When 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.199const math_reasoning = response.output_parsed;
200```
117 201
202```python
203from openai import OpenAI
204from pydantic import BaseModel
118 205
206client = OpenAI()
119 207
120 208
121 ```python
122class Step(BaseModel):209class Step(BaseModel):
123 explanation: str210 explanation: str
124 output: str211 output: str
141 text_format=MathReasoning,228 text_format=MathReasoning,
142)229)
143 230
144for output in response.output:231math_reasoning = response.output_parsed
145 if output.type != "message":232```
146 continue
147 233
148 for item in output.content:234```bash
149 if item.type == "refusal":235curl https://api.openai.com/v1/responses \
150 # If the model refuses to respond, you will get a refusal message236 -H "Authorization: Bearer $OPENAI_API_KEY" \
151 print(item.refusal)237 -H "Content-Type: application/json" \
152 continue238 -d '{
239 "model": "gpt-5.6",
240 "input": [
241 {
242 "role": "system",
243 "content": "You are a helpful math tutor. Guide the user through the solution step by step."
244 },
245 {
246 "role": "user",
247 "content": "how can I solve 8x + 7 = -23"
248 }
249 ],
250 "text": {
251 "format": {
252 "type": "json_schema",
253 "name": "math_reasoning",
254 "schema": {
255 "type": "object",
256 "properties": {
257 "steps": {
258 "type": "array",
259 "items": {
260 "type": "object",
261 "properties": {
262 "explanation": { "type": "string" },
263 "output": { "type": "string" }
264 },
265 "required": ["explanation", "output"],
266 "additionalProperties": false
267 }
268 },
269 "final_answer": { "type": "string" }
270 },
271 "required": ["steps", "final_answer"],
272 "additionalProperties": false
273 },
274 "strict": true
275 }
276 }
277 }'
278```
153 279
154 if not item.parsed:
155 raise Exception("Could not parse response")
156 280
157 print(item.parsed)281
282#### Example response
283
284```json
285{
286 "steps": [
287 {
288 "explanation": "Start with the equation 8x + 7 = -23.",
289 "output": "8x + 7 = -23"
290 },
291 {
292 "explanation": "Subtract 7 from both sides to isolate the term with the variable.",
293 "output": "8x = -23 - 7"
294 },
295 {
296 "explanation": "Simplify the right side of the equation.",
297 "output": "8x = -30"
298 },
299 {
300 "explanation": "Divide both sides by 8 to solve for x.",
301 "output": "x = -30 / 8"
302 },
303 {
304 "explanation": "Simplify the fraction.",
305 "output": "x = -15 / 4"
306 }
307 ],
308 "final_answer": "x = -15 / 4"
309}
158```310```
159 311
312
313
314
315
316
317
318Structured data extraction
319
320
321
322### Structured data extraction
323
324You can define structured fields to extract from unstructured input data, such as research papers.
325
326
327
328 Extracting data from research papers using Structured Outputs
329
160```javascript330```javascript
161const Step = z.object({331import OpenAI from "openai";
162 explanation: z.string(),332import { zodTextFormat } from "openai/helpers/zod";
163 output: z.string(),333import { z } from "zod";
164});
165 334
166const MathReasoning = z.object({335const openai = new OpenAI();
167 steps: z.array(Step),336
168 final_answer: z.string(),337const ResearchPaperExtraction = z.object({
338 title: z.string(),
339 authors: z.array(z.string()),
340 abstract: z.string(),
341 keywords: z.array(z.string()),
169});342});
170 343
171const response = await openai.responses.parse({344const response = await openai.responses.parse({
174 {347 {
175 role: "system",348 role: "system",
176 content:349 content:
177 "You are a helpful math tutor. Guide the user through the solution step by step.",350 "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.",
178 },351 },
179 { role: "user", content: "how can I solve 8x + 7 = -23" },352 { role: "user", content: "..." },
180 ],353 ],
181 text: {354 text: {
182 format: zodTextFormat(MathReasoning, "math_response"),355 format: zodTextFormat(ResearchPaperExtraction, "research_paper_extraction"),
183 },356 },
184});357});
185 358
186for (const output of response.output) {359const research_paper = response.output_parsed;
187 if (output.type !== "message") {
188 continue;
189 }
190
191 for (const item of output.content) {
192 if (item.type == "refusal") {
193 // If the model refuses to respond, you will get a refusal message
194 console.log(item.refusal);
195 continue;
196 }
197
198 if (!item.parsed) {
199 throw new Error("Could not parse response");
200 }
201
202 console.log(item.parsed);
203 }
204}
205```360```
206 361
362```python
363from openai import OpenAI
364from pydantic import BaseModel
207 365
366client = OpenAI()
208 367
209The API response from a refusal will look something like this:368
369class ResearchPaperExtraction(BaseModel):
370 title: str
371 authors: list[str]
372 abstract: str
373 keywords: list[str]
210 374
211 375
376response = client.responses.parse(
377 model="gpt-5.6",
378 input=[
379 {
380 "role": "system",
381 "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.",
382 },
383 {
384 "role": "user",
385 "content": (
386 "Attention Is All You Need by Ashish Vaswani, Noam Shazeer, "
387 "Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, "
388 "Łukasz Kaiser, and Illia Polosukhin. We propose the "
389 "Transformer, a sequence transduction architecture based "
390 "entirely on attention. Keywords: transformers, attention, "
391 "sequence transduction."
392 ),
393 },
394 ],
395 text_format=ResearchPaperExtraction,
396)
212 397
398research_paper = response.output_parsed
399```
213 400
214 ```json401```bash
215{402curl https://api.openai.com/v1/responses \
216 "id": "resp_1234567890",403 -H "Authorization: Bearer $OPENAI_API_KEY" \
217 "object": "response",404 -H "Content-Type: application/json" \
218 "created_at": 1721596428,405 -d '{
219 "status": "completed",406 "model": "gpt-5.6",
220 "completed_at": 1721596429,407 "input": [
221 "error": null,
222 "incomplete_details": null,
223 "input": [],
224 "instructions": null,
225 "max_output_tokens": null,
226 "model": "gpt-4o-2024-08-06",
227 "output": [{
228 "id": "msg_1234567890",
229 "type": "message",
230 "role": "assistant",
231 "content": [
232 // highlight-start
233 {408 {
234 "type": "refusal",409 "role": "system",
235 "refusal": "I'm sorry, I cannot assist with that request."410 "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."
411 },
412 {
413 "role": "user",
414 "content": "..."
236 }415 }
237 // highlight-end416 ],
238 ]417 "text": {
239 }],418 "format": {
240 "usage": {419 "type": "json_schema",
241 "input_tokens": 81,420 "name": "research_paper_extraction",
242 "output_tokens": 11,421 "schema": {
243 "total_tokens": 92,422 "type": "object",
244 "output_tokens_details": {423 "properties": {
245 "reasoning_tokens": 0,424 "title": { "type": "string" },
425 "authors": {
426 "type": "array",
427 "items": { "type": "string" }
428 },
429 "abstract": { "type": "string" },
430 "keywords": {
431 "type": "array",
432 "items": { "type": "string" }
246 }433 }
247 },434 },
435 "required": ["title", "authors", "abstract", "keywords"],
436 "additionalProperties": false
437 },
438 "strict": true
439 }
440 }
441 }'
442```
443
444
445
446#### Example response
447
448```json
449{
450 "title": "Application of Quantum Algorithms in Interstellar Navigation: A New Frontier",
451 "authors": ["Dr. Stella Voyager", "Dr. Nova Star", "Dr. Lyra Hunter"],
452 "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.",
453 "keywords": [
454 "Quantum algorithms",
455 "interstellar navigation",
456 "space-time anomalies",
457 "quantum superposition",
458 "quantum entanglement",
459 "space travel"
460 ]
248}461}
249```462```
250 463
251 464
252 465
253 466
254Tips and best practices
255 467
256 468
257 469
258#### Handling user-generated input470UI generation
259 471
260If 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.
261 472
262The model will always try to adhere to the provided schema, which can result in hallucinations if the input is completely unrelated to the schema.
263 473
264You 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.474### UI Generation
265 475
266#### Handling mistakes476You can generate valid HTML by representing it as recursive data structures with constraints, like enums.
267 477
268Structured 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](https://developers.openai.com/api/docs/guides/prompt-engineering) for more guidance on how to tweak your inputs.
269 478
270#### Avoid JSON schema divergence
271 479
272To prevent your JSON Schema and corresponding types in your programming language from diverging, we strongly recommend using the native Pydantic/zod sdk support.
273 480
274If 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).481 Generating HTML using Structured Outputs
275 482
276## Streaming483```javascript
484import OpenAI from "openai";
485import { zodTextFormat } from "openai/helpers/zod";
486import { z } from "zod";
487
488const openai = new OpenAI();
489
490const UI = z.lazy(() =>
491 z.object({
492 type: z.enum(["div", "button", "header", "section", "field", "form"]),
493 label: z.string(),
494 children: z.array(UI),
495 attributes: z.array(
496 z.object({
497 name: z.string(),
498 value: z.string(),
499 })
500 ),
501 })
502);
277 503
278## Supported schemas504const response = await openai.responses.parse({
505 model: "gpt-5.6",
506 input: [
507 {
508 role: "system",
509 content: "You are a UI generator AI. Convert the user input into a UI.",
510 },
511 {
512 role: "user",
513 content: "Make a User Profile Form",
514 },
515 ],
516 text: {
517 format: zodTextFormat(UI, "ui"),
518 },
519});
279 520
280## JSON mode521const ui = response.output_parsed;
522```
281 523
282JSON mode is a more basic version of the Structured Outputs feature. While524```python
283 JSON mode ensures that model output is valid JSON, Structured Outputs reliably525from enum import Enum
284 matches the model's output to the schema you specify. We recommend you use526from typing import List
285 Structured Outputs if it is supported for your use case.
286 527
287When 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.528from openai import OpenAI
529from pydantic import BaseModel
288 530
531client = OpenAI()
289 532
290 533
534class UIType(str, Enum):
535 div = "div"
536 button = "button"
537 header = "header"
538 section = "section"
539 field = "field"
540 form = "form"
291 541
292To 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.
293 542
543class Attribute(BaseModel):
544 name: str
545 value: str
294 546
295Important notes:
296 547
297- 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.548class UI(BaseModel):
298- 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.549 type: UIType
299- Your application must detect and handle the edge cases that can result in the model output not being a complete JSON object (see below)550 label: str
551 children: List["UI"]
552 attributes: List[Attribute]
300 553
301Handling edge cases554
555UI.model_rebuild() # This is required to enable recursive types
556
557
558class Response(BaseModel):
559 ui: UI
560
561
562response = client.responses.parse(
563 model="gpt-5.6",
564 input=[
565 {
566 "role": "system",
567 "content": "You are a UI generator AI. Convert the user input into a UI.",
568 },
569 {"role": "user", "content": "Make a User Profile Form"},
570 ],
571 text_format=Response,
572)
573
574ui = response.output_parsed
575```
576
577```bash
578curl https://api.openai.com/v1/responses \
579 -H "Authorization: Bearer $OPENAI_API_KEY" \
580 -H "Content-Type: application/json" \
581 -d '{
582 "model": "gpt-5.6",
583 "input": [
584 {
585 "role": "system",
586 "content": "You are a UI generator AI. Convert the user input into a UI."
587 },
588 {
589 "role": "user",
590 "content": "Make a User Profile Form"
591 }
592 ],
593 "text": {
594 "format": {
595 "type": "json_schema",
596 "name": "ui",
597 "description": "Dynamically generated UI",
598 "schema": {
599 "type": "object",
600 "properties": {
601 "type": {
602 "type": "string",
603 "description": "The type of the UI component",
604 "enum": ["div", "button", "header", "section", "field", "form"]
605 },
606 "label": {
607 "type": "string",
608 "description": "The label of the UI component, used for buttons or form fields"
609 },
610 "children": {
611 "type": "array",
612 "description": "Nested UI components",
613 "items": {"$ref": "#"}
614 },
615 "attributes": {
616 "type": "array",
617 "description": "Arbitrary attributes for the UI component, suitable for any element",
618 "items": {
619 "type": "object",
620 "properties": {
621 "name": {
622 "type": "string",
623 "description": "The name of the attribute, for example onClick or className"
624 },
625 "value": {
626 "type": "string",
627 "description": "The value of the attribute"
628 }
629 },
630 "required": ["name", "value"],
631 "additionalProperties": false
632 }
633 }
634 },
635 "required": ["type", "label", "children", "attributes"],
636 "additionalProperties": false
637 },
638 "strict": true
639 }
640 }
641 }'
642```
643
644
645
646#### Example response
647
648```json
649{
650 "type": "form",
651 "label": "User Profile Form",
652 "children": [
653 {
654 "type": "div",
655 "label": "",
656 "children": [
657 {
658 "type": "field",
659 "label": "First Name",
660 "children": [],
661 "attributes": [
662 {
663 "name": "type",
664 "value": "text"
665 },
666 {
667 "name": "name",
668 "value": "firstName"
669 },
670 {
671 "name": "placeholder",
672 "value": "Enter your first name"
673 }
674 ]
675 },
676 {
677 "type": "field",
678 "label": "Last Name",
679 "children": [],
680 "attributes": [
681 {
682 "name": "type",
683 "value": "text"
684 },
685 {
686 "name": "name",
687 "value": "lastName"
688 },
689 {
690 "name": "placeholder",
691 "value": "Enter your last name"
692 }
693 ]
694 }
695 ],
696 "attributes": []
697 },
698 {
699 "type": "button",
700 "label": "Submit",
701 "children": [],
702 "attributes": [
703 {
704 "name": "type",
705 "value": "submit"
706 }
707 ]
708 }
709 ],
710 "attributes": [
711 {
712 "name": "method",
713 "value": "post"
714 },
715 {
716 "name": "action",
717 "value": "/submit-profile"
718 }
719 ]
720}
721```
722
723
724
725
726
727
728
729Moderation
730
731
732
733### Moderation
734
735You can classify inputs on multiple categories, which is a common way of doing moderation.
736
737
738
739
740 Moderation using Structured Outputs
741
742```javascript
743import OpenAI from "openai";
744import { zodTextFormat } from "openai/helpers/zod";
745import { z } from "zod";
746
747const openai = new OpenAI();
748
749const ContentCompliance = z.object({
750 is_violating: z.boolean(),
751 category: z.enum(["violence", "sexual", "self_harm"]).nullable(),
752 explanation_if_violating: z.string().nullable(),
753});
754
755const response = await openai.responses.parse({
756 model: "gpt-5.6",
757 input: [
758 {
759 role: "system",
760 content:
761 "Determine if the user input violates specific guidelines and explain if they do.",
762 },
763 {
764 role: "user",
765 content: "How do I prepare for a job interview?",
766 },
767 ],
768 text: {
769 format: zodTextFormat(ContentCompliance, "content_compliance"),
770 },
771});
772
773const compliance = response.output_parsed;
774```
775
776```python
777from enum import Enum
778from typing import Optional
779
780from openai import OpenAI
781from pydantic import BaseModel
782
783client = OpenAI()
784
785
786class Category(str, Enum):
787 violence = "violence"
788 sexual = "sexual"
789 self_harm = "self_harm"
790
791
792class ContentCompliance(BaseModel):
793 is_violating: bool
794 category: Optional[Category]
795 explanation_if_violating: Optional[str]
796
797
798response = client.responses.parse(
799 model="gpt-5.6",
800 input=[
801 {
802 "role": "system",
803 "content": "Determine if the user input violates specific guidelines and explain if they do.",
804 },
805 {"role": "user", "content": "How do I prepare for a job interview?"},
806 ],
807 text_format=ContentCompliance,
808)
809
810compliance = response.output_parsed
811```
812
813```bash
814curl https://api.openai.com/v1/responses \
815 -H "Authorization: Bearer $OPENAI_API_KEY" \
816 -H "Content-Type: application/json" \
817 -d '{
818 "model": "gpt-5.6",
819 "input": [
820 {
821 "role": "system",
822 "content": "Determine if the user input violates specific guidelines and explain if they do."
823 },
824 {
825 "role": "user",
826 "content": "How do I prepare for a job interview?"
827 }
828 ],
829 "text": {
830 "format": {
831 "type": "json_schema",
832 "name": "content_compliance",
833 "description": "Determines if content is violating specific moderation rules",
834 "schema": {
835 "type": "object",
836 "properties": {
837 "is_violating": {
838 "type": "boolean",
839 "description": "Indicates if the content is violating guidelines"
840 },
841 "category": {
842 "type": ["string", "null"],
843 "description": "Type of violation, if the content is violating guidelines. Null otherwise.",
844 "enum": ["violence", "sexual", "self_harm"]
845 },
846 "explanation_if_violating": {
847 "type": ["string", "null"],
848 "description": "Explanation of why the content is violating"
849 }
850 },
851 "required": ["is_violating", "category", "explanation_if_violating"],
852 "additionalProperties": false
853 },
854 "strict": true
855 }
856 }
857 }'
858```
859
860
861
862#### Example response
863
864```json
865{
866 "is_violating": false,
867 "category": null,
868 "explanation_if_violating": null
869}
870```
871
872
873
874
875
876
877
878
879How to use Structured Outputs with
880text.format
881
882
883
884
885Step 1: Define your schema
886
887First you must design the JSON Schema that the model should be constrained to follow. See the [examples](https://developers.openai.com/api/docs/guides/structured-outputs#examples) at the top of this guide for reference.
888
889While Structured Outputs supports much of JSON Schema, some features are unavailable either for performance or technical reasons. See [here](https://developers.openai.com/api/docs/guides/structured-outputs#supported-schemas) for more details.
890
891#### Tips for your JSON Schema
892
893To maximize the quality of model generations, we recommend the following:
894
895- Name keys clearly and intuitively
896- Create clear titles and descriptions for important keys in your structure
897- Create and use evals to determine the structure that works best for your use case
898
899Step 2: Supply your schema in the API call
900
901To use Structured Outputs, simply specify
902
903
904
905
906```json
907text: { format: { type: "json_schema", "strict": true, "schema": … } }
908```
909
910
911For example:
912
913
914
915
916```python
917response = client.responses.create(
918 model="gpt-5.6",
919 input=[
920 {
921 "role": "system",
922 "content": "You are a helpful math tutor. Guide the user through the solution step by step.",
923 },
924 {"role": "user", "content": "how can I solve 8x + 7 = -23"},
925 ],
926 text={
927 "format": {
928 "type": "json_schema",
929 "name": "math_response",
930 "schema": {
931 "type": "object",
932 "properties": {
933 "steps": {
934 "type": "array",
935 "items": {
936 "type": "object",
937 "properties": {
938 "explanation": {"type": "string"},
939 "output": {"type": "string"},
940 },
941 "required": ["explanation", "output"],
942 "additionalProperties": False,
943 },
944 },
945 "final_answer": {"type": "string"},
946 },
947 "required": ["steps", "final_answer"],
948 "additionalProperties": False,
949 },
950 "strict": True,
951 },
952 },
953)
954
955print(response.output_text)
956```
957
958```javascript
959const response = await openai.responses.create({
960 model: "gpt-5.6",
961 input: [
962 {
963 role: "system",
964 content:
965 "You are a helpful math tutor. Guide the user through the solution step by step.",
966 },
967 { role: "user", content: "how can I solve 8x + 7 = -23" },
968 ],
969 text: {
970 format: {
971 type: "json_schema",
972 name: "math_response",
973 schema: {
974 type: "object",
975 properties: {
976 steps: {
977 type: "array",
978 items: {
979 type: "object",
980 properties: {
981 explanation: { type: "string" },
982 output: { type: "string" },
983 },
984 required: ["explanation", "output"],
985 additionalProperties: false,
986 },
987 },
988 final_answer: { type: "string" },
989 },
990 required: ["steps", "final_answer"],
991 additionalProperties: false,
992 },
993 strict: true,
994 },
995 },
996});
997
998console.log(response.output_text);
999```
1000
1001```bash
1002curl https://api.openai.com/v1/responses \
1003 -H "Authorization: Bearer $OPENAI_API_KEY" \
1004 -H "Content-Type: application/json" \
1005 -d '{
1006 "model": "gpt-5.6",
1007 "input": [
1008 {
1009 "role": "system",
1010 "content": "You are a helpful math tutor. Guide the user through the solution step by step."
1011 },
1012 {
1013 "role": "user",
1014 "content": "how can I solve 8x + 7 = -23"
1015 }
1016 ],
1017 "text": {
1018 "format": {
1019 "type": "json_schema",
1020 "name": "math_response",
1021 "schema": {
1022 "type": "object",
1023 "properties": {
1024 "steps": {
1025 "type": "array",
1026 "items": {
1027 "type": "object",
1028 "properties": {
1029 "explanation": { "type": "string" },
1030 "output": { "type": "string" }
1031 },
1032 "required": ["explanation", "output"],
1033 "additionalProperties": false
1034 }
1035 },
1036 "final_answer": { "type": "string" }
1037 },
1038 "required": ["steps", "final_answer"],
1039 "additionalProperties": false
1040 },
1041 "strict": true
1042 }
1043 }
1044 }'
1045```
1046
1047
1048
1049**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.
1050
1051Step 3: Handle edge cases
1052
1053In some cases, the model might not generate a valid response that matches the provided JSON schema.
1054
1055This 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.
1056
1057
1058
1059
1060```javascript
1061try {
1062 const response = await openai.responses.create({
1063 model: "gpt-5.6",
1064 input: [
1065 {
1066 role: "system",
1067 content:
1068 "You are a helpful math tutor. Guide the user through the solution step by step.",
1069 },
1070 {
1071 role: "user",
1072 content: "how can I solve 8x + 7 = -23",
1073 },
1074 ],
1075 max_output_tokens: 50,
1076 text: {
1077 format: {
1078 type: "json_schema",
1079 name: "math_response",
1080 schema: {
1081 type: "object",
1082 properties: {
1083 steps: {
1084 type: "array",
1085 items: {
1086 type: "object",
1087 properties: {
1088 explanation: {
1089 type: "string",
1090 },
1091 output: {
1092 type: "string",
1093 },
1094 },
1095 required: ["explanation", "output"],
1096 additionalProperties: false,
1097 },
1098 },
1099 final_answer: {
1100 type: "string",
1101 },
1102 },
1103 required: ["steps", "final_answer"],
1104 additionalProperties: false,
1105 },
1106 strict: true,
1107 },
1108 },
1109 });
1110
1111 if (
1112 response.status === "incomplete" &&
1113 response.incomplete_details.reason === "max_output_tokens"
1114 ) {
1115 // Handle the case where the model did not return a complete response
1116 throw new Error("Incomplete response");
1117 }
1118
1119 const message = response.output.find((item) => item.type === "message");
1120 const math_response = message?.content[0];
1121
1122 if (!math_response) {
1123 throw new Error("No response content");
1124 }
1125
1126 if (math_response.type === "refusal") {
1127 // handle refusal
1128 console.log(math_response.refusal);
1129 } else if (math_response.type === "output_text") {
1130 console.log(math_response.text);
1131 } else {
1132 throw new Error("No response content");
1133 }
1134} catch (e) {
1135 // Handle edge cases
1136 console.error(e);
1137}
1138```
1139
1140```python
1141try:
1142 response = client.responses.create(
1143 model="gpt-5.6",
1144 input=[
1145 {
1146 "role": "system",
1147 "content": "You are a helpful math tutor. Guide the user through the solution step by step.",
1148 },
1149 {"role": "user", "content": "how can I solve 8x + 7 = -23"},
1150 ],
1151 text={
1152 "format": {
1153 "type": "json_schema",
1154 "name": "math_response",
1155 "strict": True,
1156 "schema": {
1157 "type": "object",
1158 "properties": {
1159 "steps": {
1160 "type": "array",
1161 "items": {
1162 "type": "object",
1163 "properties": {
1164 "explanation": {"type": "string"},
1165 "output": {"type": "string"},
1166 },
1167 "required": ["explanation", "output"],
1168 "additionalProperties": False,
1169 },
1170 },
1171 "final_answer": {"type": "string"},
1172 },
1173 "required": ["steps", "final_answer"],
1174 "additionalProperties": False,
1175 },
1176 },
1177 },
1178 max_output_tokens=50,
1179 )
1180
1181 if (
1182 response.status == "incomplete"
1183 and response.incomplete_details.reason == "max_output_tokens"
1184 ):
1185 raise Exception("Incomplete response")
1186
1187 message = next((item for item in response.output if item.type == "message"), None)
1188 math_response = message.content[0] if message and message.content else None
1189
1190 if not math_response:
1191 raise Exception("No response content")
1192
1193 if math_response.type == "refusal":
1194 print(math_response.refusal)
1195 elif math_response.type == "output_text":
1196 print(math_response.text)
1197 else:
1198 raise Exception("No response content")
1199except Exception as e:
1200 # handle errors like finish_reason, refusal, content_filter, etc.
1201 print(e)
1202```
1203
1204
1205
1206
1207
1208
1209
1210Refusals with Structured Outputs
1211
1212
1213
1214When 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.
1215
1216When 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.
1217
1218
1219
1220
1221```python
1222class Step(BaseModel):
1223 explanation: str
1224 output: str
1225
1226
1227class MathReasoning(BaseModel):
1228 steps: list[Step]
1229 final_answer: str
1230
1231
1232response = client.responses.parse(
1233 model="gpt-5.6",
1234 input=[
1235 {
1236 "role": "system",
1237 "content": "You are a helpful math tutor. Guide the user through the solution step by step.",
1238 },
1239 {"role": "user", "content": "how can I solve 8x + 7 = -23"},
1240 ],
1241 text_format=MathReasoning,
1242)
1243
1244for output in response.output:
1245 if output.type != "message":
1246 continue
1247
1248 for item in output.content:
1249 if item.type == "refusal":
1250 # If the model refuses to respond, you will get a refusal message
1251 print(item.refusal)
1252 continue
1253
1254 if not item.parsed:
1255 raise Exception("Could not parse response")
1256
1257 print(item.parsed)
1258```
1259
1260```javascript
1261const Step = z.object({
1262 explanation: z.string(),
1263 output: z.string(),
1264});
1265
1266const MathReasoning = z.object({
1267 steps: z.array(Step),
1268 final_answer: z.string(),
1269});
1270
1271const response = await openai.responses.parse({
1272 model: "gpt-5.6",
1273 input: [
1274 {
1275 role: "system",
1276 content:
1277 "You are a helpful math tutor. Guide the user through the solution step by step.",
1278 },
1279 { role: "user", content: "how can I solve 8x + 7 = -23" },
1280 ],
1281 text: {
1282 format: zodTextFormat(MathReasoning, "math_response"),
1283 },
1284});
1285
1286for (const output of response.output) {
1287 if (output.type !== "message") {
1288 continue;
1289 }
1290
1291 for (const item of output.content) {
1292 if (item.type == "refusal") {
1293 // If the model refuses to respond, you will get a refusal message
1294 console.log(item.refusal);
1295 continue;
1296 }
1297
1298 if (!item.parsed) {
1299 throw new Error("Could not parse response");
1300 }
1301
1302 console.log(item.parsed);
1303 }
1304}
1305```
1306
1307
1308
1309The API response from a refusal will look something like this:
1310
1311
1312
1313
1314```json
1315{
1316 "id": "resp_1234567890",
1317 "object": "response",
1318 "created_at": 1721596428,
1319 "status": "completed",
1320 "completed_at": 1721596429,
1321 "error": null,
1322 "incomplete_details": null,
1323 "input": [],
1324 "instructions": null,
1325 "max_output_tokens": null,
1326 "model": "gpt-4o-2024-08-06",
1327 "output": [{
1328 "id": "msg_1234567890",
1329 "type": "message",
1330 "role": "assistant",
1331 "content": [
1332 // highlight-start
1333 {
1334 "type": "refusal",
1335 "refusal": "I'm sorry, I cannot assist with that request."
1336 }
1337 // highlight-end
1338 ]
1339 }],
1340 "usage": {
1341 "input_tokens": 81,
1342 "output_tokens": 11,
1343 "total_tokens": 92,
1344 "output_tokens_details": {
1345 "reasoning_tokens": 0,
1346 }
1347 },
1348}
1349```
1350
1351
1352
1353
1354Tips and best practices
1355
1356
1357
1358#### Handling user-generated input
1359
1360If 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.
1361
1362The model will always try to adhere to the provided schema, which can result in hallucinations if the input is completely unrelated to the schema.
1363
1364You 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.
1365
1366#### Handling mistakes
1367
1368Structured 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](https://developers.openai.com/api/docs/guides/prompt-engineering) for more guidance on how to tweak your inputs.
1369
1370#### Avoid JSON schema divergence
1371
1372To prevent your JSON Schema and corresponding types in your programming language from diverging, we strongly recommend using the native Pydantic/zod sdk support.
1373
1374If 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).
1375
1376## Streaming
1377
1378
1379
1380You can use streaming to process model responses or function call arguments as they are being generated, and parse them as structured data.
1381
1382That way, you don't have to wait for the entire response to complete before handling it.
1383This 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.
1384
1385We recommend relying on the SDKs to handle streaming with Structured Outputs.
1386
1387
1388
1389
1390```python
1391from typing import List
1392
1393from openai import OpenAI
1394from pydantic import BaseModel
1395
1396
1397class EntitiesModel(BaseModel):
1398 attributes: List[str]
1399 colors: List[str]
1400 animals: List[str]
1401
1402
1403client = OpenAI()
1404
1405with client.responses.stream(
1406 model="gpt-5.6",
1407 input=[
1408 {"role": "system", "content": "Extract entities from the input text"},
1409 {
1410 "role": "user",
1411 "content": "The quick brown fox jumps over the lazy dog with piercing blue eyes",
1412 },
1413 ],
1414 text_format=EntitiesModel,
1415) as stream:
1416 for event in stream:
1417 if event.type == "response.refusal.delta":
1418 print(event.delta, end="")
1419 elif event.type == "response.output_text.delta":
1420 print(event.delta, end="")
1421 elif event.type == "response.error":
1422 print(event.error, end="")
1423 elif event.type == "response.completed":
1424 print("Completed") # print(event.response.output)
1425
1426 final_response = stream.get_final_response()
1427 print(final_response)
1428```
1429
1430```javascript
1431import { OpenAI } from "openai";
1432import { zodTextFormat } from "openai/helpers/zod";
1433import { z } from "zod";
1434
1435const EntitiesSchema = z.object({
1436 attributes: z.array(z.string()),
1437 colors: z.array(z.string()),
1438 animals: z.array(z.string()),
1439});
1440
1441const openai = new OpenAI();
1442const stream = openai.responses
1443 .stream({
1444 model: "gpt-5.6",
1445 input: [
1446 { role: "user", content: "What's the weather like in Paris today?" },
1447 ],
1448 text: {
1449 format: zodTextFormat(EntitiesSchema, "entities"),
1450 },
1451 })
1452 .on("response.refusal.delta", (event) => {
1453 process.stdout.write(event.delta);
1454 })
1455 .on("response.output_text.delta", (event) => {
1456 process.stdout.write(event.delta);
1457 })
1458 .on("response.output_text.done", () => {
1459 process.stdout.write("\n");
1460 })
1461 .on("error", (error) => {
1462 console.error(error);
1463 });
1464
1465const result = await stream.finalResponse();
1466
1467console.log(result);
1468```
1469
1470
1471
1472## Supported schemas
1473
1474
1475
1476Structured Outputs supports a subset of the [JSON Schema](https://json-schema.org/docs) language.
1477
1478#### Supported types
1479
1480The following types are supported for Structured Outputs:
1481
1482- String
1483- Number
1484- Boolean
1485- Integer
1486- Object
1487- Array
1488- Enum
1489- anyOf
1490
1491#### Supported properties
1492
1493In addition to specifying the type of a property, you can specify a selection of additional constraints:
1494
1495**Supported `string` properties:**
1496
1497- `pattern` — A regular expression that the string must match.
1498- `format` — Predefined formats for strings. Currently supported:
1499 - `date-time`
1500 - `time`
1501 - `date`
1502 - `duration`
1503 - `email`
1504 - `hostname`
1505 - `ipv4`
1506 - `ipv6`
1507 - `uuid`
1508
1509**Supported `number` properties:**
1510
1511- `multipleOf` — The number must be a multiple of this value.
1512- `maximum` — The number must be less than or equal to this value.
1513- `exclusiveMaximum` — The number must be less than this value.
1514- `minimum` — The number must be greater than or equal to this value.
1515- `exclusiveMinimum` — The number must be greater than this value.
1516
1517**Supported `array` properties:**
1518
1519- `minItems` — The array must have at least this many items.
1520- `maxItems` — The array must have at most this many items.
1521
1522Here are some examples on how you can use these type restrictions:
1523
1524
1525
1526String Restrictions
1527
1528```json
1529{
1530 "name": "user_data",
1531 "strict": true,
1532 "schema": {
1533 "type": "object",
1534 "properties": {
1535 "name": {
1536 "type": "string",
1537 "description": "The name of the user"
1538 },
1539 "username": {
1540 "type": "string",
1541 "description": "The username of the user. Must start with @",
1542 // highlight-start
1543 "pattern": "^@[a-zA-Z0-9_]+$"
1544 // highlight-end
1545 },
1546 "email": {
1547 "type": "string",
1548 "description": "The email of the user",
1549 // highlight-start
1550 "format": "email"
1551 // highlight-end
1552 }
1553 },
1554 "additionalProperties": false,
1555 "required": [
1556 "name", "username", "email"
1557 ]
1558 }
1559}
1560```
1561
1562
1563
1564
1565
1566
1567Number Restrictions
1568
1569```json
1570{
1571 "name": "weather_data",
1572 "strict": true,
1573 "schema": {
1574 "type": "object",
1575 "properties": {
1576 "location": {
1577 "type": "string",
1578 "description": "The location to get the weather for"
1579 },
1580 "unit": {
1581 "type": ["string", "null"],
1582 "description": "The unit to return the temperature in",
1583 "enum": ["F", "C"]
1584 },
1585 "value": {
1586 "type": "number",
1587 "description": "The actual temperature value in the location",
1588 // highlight-start
1589 "minimum": -130,
1590 "maximum": 130
1591 // highlight-end
1592 }
1593 },
1594 "additionalProperties": false,
1595 "required": [
1596 "location", "unit", "value"
1597 ]
1598 }
1599}
1600```
1601
1602
1603
1604Note these constraints are [not yet supported for fine-tuned
1605 models](#some-type-specific-keywords-are-not-yet-supported).
1606
1607#### Root objects must not be `anyOf` and must be an object
1608
1609Note 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:
1610
1611```javascript
1612import { z } from "zod";
1613import { zodResponseFormat } from "openai/helpers/zod";
1614
1615const BaseResponseSchema = z.object({
1616 /* ... */
1617});
1618const UnsuccessfulResponseSchema = z.object({
1619 /* ... */
1620});
1621
1622const finalSchema = z.discriminatedUnion("status", [
1623 BaseResponseSchema,
1624 UnsuccessfulResponseSchema,
1625]);
1626
1627// Invalid JSON Schema for Structured Outputs
1628const json = zodResponseFormat(finalSchema, "final_schema");
1629```
1630
1631
1632#### All fields must be `required`
1633
1634To use Structured Outputs, all fields or function parameters must be specified as `required`.
1635
1636```json
1637{
1638 "name": "get_weather",
1639 "description": "Fetches the weather in the given location",
1640 "strict": true,
1641 "parameters": {
1642 "type": "object",
1643 "properties": {
1644 "location": {
1645 "type": "string",
1646 "description": "The location to get the weather for"
1647 },
1648 "unit": {
1649 "type": "string",
1650 "description": "The unit to return the temperature in",
1651 "enum": ["F", "C"]
1652 }
1653 },
1654 "additionalProperties": false,
1655 // highlight-start
1656 "required": ["location", "unit"]
1657 // highlight-end
1658 }
1659}
1660```
1661
1662
1663Although 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`.
1664
1665```json
1666{
1667 "name": "get_weather",
1668 "description": "Fetches the weather in the given location",
1669 "strict": true,
1670 "parameters": {
1671 "type": "object",
1672 "properties": {
1673 "location": {
1674 "type": "string",
1675 "description": "The location to get the weather for"
1676 },
1677 "unit": {
1678 // highlight-start
1679 "type": ["string", "null"],
1680 // highlight-end
1681 "description": "The unit to return the temperature in",
1682 "enum": ["F", "C"]
1683 }
1684 },
1685 "additionalProperties": false,
1686 "required": [
1687 "location", "unit"
1688 ]
1689 }
1690}
1691```
1692
1693
1694#### Objects have limitations on nesting depth and size
1695
1696A schema may have up to 5000 object properties total, with up to 10 levels of nesting.
1697
1698#### Limitations on total string size
1699
1700In a schema, total string length of all property names, definition names, enum values, and const values cannot exceed 120,000 characters.
1701
1702#### Limitations on enum size
1703
1704A schema may have up to 1000 enum values across all enum properties.
1705
1706For 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.
1707
1708#### `additionalProperties: false` must always be set in objects
1709
1710`additionalProperties` controls whether it is allowable for an object to contain additional keys / values that were not defined in the JSON Schema.
1711
1712Structured Outputs only supports generating specified keys / values, so we require developers to set `additionalProperties: false` to opt into Structured Outputs.
1713
1714```json
1715{
1716 "name": "get_weather",
1717 "description": "Fetches the weather in the given location",
1718 "strict": true,
1719 "schema": {
1720 "type": "object",
1721 "properties": {
1722 "location": {
1723 "type": "string",
1724 "description": "The location to get the weather for"
1725 },
1726 "unit": {
1727 "type": "string",
1728 "description": "The unit to return the temperature in",
1729 "enum": ["F", "C"]
1730 }
1731 },
1732 // highlight-start
1733 "additionalProperties": false,
1734 // highlight-end
1735 "required": [
1736 "location", "unit"
1737 ]
1738 }
1739}
1740```
1741
1742
1743#### Key ordering
1744
1745When using Structured Outputs, outputs will be produced in the same order as the ordering of keys in the schema.
1746
1747#### Some type-specific keywords are not yet supported
1748
1749- **Composition:** `allOf`, `not`, `dependentRequired`, `dependentSchemas`, `if`, `then`, `else`
1750
1751For fine-tuned models, we additionally do not support the following:
1752
1753- **For strings:** `minLength`, `maxLength`, `pattern`, `format`
1754- **For numbers:** `minimum`, `maximum`, `multipleOf`
1755- **For objects:** `patternProperties`
1756- **For arrays:** `minItems`, `maxItems`
1757
1758If you turn on Structured Outputs by supplying `strict: true` and call the API with an unsupported JSON Schema, you will receive an error.
1759
1760#### For `anyOf`, the nested schemas must each be a valid JSON Schema per this subset
1761
1762Here's an example supported anyOf schema:
1763
1764```json
1765{
1766 "type": "object",
1767 "properties": {
1768 "item": {
1769 "anyOf": [
1770 {
1771 "type": "object",
1772 "description": "The user object to insert into the database",
1773 "properties": {
1774 "name": {
1775 "type": "string",
1776 "description": "The name of the user"
1777 },
1778 "age": {
1779 "type": "number",
1780 "description": "The age of the user"
1781 }
1782 },
1783 "additionalProperties": false,
1784 "required": [
1785 "name",
1786 "age"
1787 ]
1788 },
1789 {
1790 "type": "object",
1791 "description": "The address object to insert into the database",
1792 "properties": {
1793 "number": {
1794 "type": "string",
1795 "description": "The number of the address. Eg. for 123 main st, this would be 123"
1796 },
1797 "street": {
1798 "type": "string",
1799 "description": "The street name. Eg. for 123 main st, this would be main st"
1800 },
1801 "city": {
1802 "type": "string",
1803 "description": "The city of the address"
1804 }
1805 },
1806 "additionalProperties": false,
1807 "required": [
1808 "number",
1809 "street",
1810 "city"
1811 ]
1812 }
1813 ]
1814 }
1815 },
1816 "additionalProperties": false,
1817 "required": [
1818 "item"
1819 ]
1820}
1821```
1822
1823
1824#### Definitions are supported
1825
1826You can use definitions to define subschemas which are referenced throughout your schema. The following is a simple example.
1827
1828```json
1829{
1830 "type": "object",
1831 "properties": {
1832 "steps": {
1833 "type": "array",
1834 "items": {
1835 "$ref": "#/$defs/step"
1836 }
1837 },
1838 "final_answer": {
1839 "type": "string"
1840 }
1841 },
1842 "$defs": {
1843 "step": {
1844 "type": "object",
1845 "properties": {
1846 "explanation": {
1847 "type": "string"
1848 },
1849 "output": {
1850 "type": "string"
1851 }
1852 },
1853 "required": [
1854 "explanation",
1855 "output"
1856 ],
1857 "additionalProperties": false
1858 }
1859 },
1860 "required": [
1861 "steps",
1862 "final_answer"
1863 ],
1864 "additionalProperties": false
1865}
1866```
1867
1868
1869#### Recursive schemas are supported
1870
1871Sample recursive schema using `#` to indicate root recursion.
1872
1873```json
1874{
1875 "name": "ui",
1876 "description": "Dynamically generated UI",
1877 "strict": true,
1878 "schema": {
1879 "type": "object",
1880 "properties": {
1881 "type": {
1882 "type": "string",
1883 "description": "The type of the UI component",
1884 "enum": ["div", "button", "header", "section", "field", "form"]
1885 },
1886 "label": {
1887 "type": "string",
1888 "description": "The label of the UI component, used for buttons or form fields"
1889 },
1890 "children": {
1891 "type": "array",
1892 "description": "Nested UI components",
1893 "items": {
1894 "$ref": "#"
1895 }
1896 },
1897 "attributes": {
1898 "type": "array",
1899 "description": "Arbitrary attributes for the UI component, suitable for any element",
1900 "items": {
1901 "type": "object",
1902 "properties": {
1903 "name": {
1904 "type": "string",
1905 "description": "The name of the attribute, for example onClick or className"
1906 },
1907 "value": {
1908 "type": "string",
1909 "description": "The value of the attribute"
1910 }
1911 },
1912 "additionalProperties": false,
1913 "required": ["name", "value"]
1914 }
1915 }
1916 },
1917 "required": ["type", "label", "children", "attributes"],
1918 "additionalProperties": false
1919 }
1920}
1921```
1922
1923
1924Sample recursive schema using explicit recursion:
1925
1926```json
1927{
1928 "type": "object",
1929 "properties": {
1930 "linked_list": {
1931 "$ref": "#/$defs/linked_list_node"
1932 }
1933 },
1934 "$defs": {
1935 "linked_list_node": {
1936 "type": "object",
1937 "properties": {
1938 "value": {
1939 "type": "number"
1940 },
1941 "next": {
1942 "anyOf": [
1943 {
1944 "$ref": "#/$defs/linked_list_node"
1945 },
1946 {
1947 "type": "null"
1948 }
1949 ]
1950 }
1951 },
1952 "additionalProperties": false,
1953 "required": [
1954 "next",
1955 "value"
1956 ]
1957 }
1958 },
1959 "additionalProperties": false,
1960 "required": [
1961 "linked_list"
1962 ]
1963}
1964```
1965
1966
1967
1968## JSON mode
1969
1970JSON mode is a more basic version of the Structured Outputs feature. While
1971 JSON mode ensures that model output is valid JSON, Structured Outputs reliably
1972 matches the model's output to the schema you specify. We recommend you use
1973 Structured Outputs if it is supported for your use case.
1974
1975When 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.
1976
1977
1978
1979
1980To 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.
1981
1982
1983Important notes:
1984
1985- 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.
1986- 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.
1987- Your application must detect and handle the edge cases that can result in the model output not being a complete JSON object (see below)
1988
1989Handling edge cases
1990
1991```javascript
1992const we_did_not_specify_stop_tokens = true;
1993
1994try {
1995 const response = await openai.responses.create({
1996 model: "gpt-5.6",
1997 input: [
1998 {
1999 role: "system",
2000 content: "You are a helpful assistant designed to output JSON.",
2001 },
2002 {
2003 role: "user",
2004 content:
2005 "Who won the world series in 2020? Please respond in the format {winner: ...}",
2006 },
2007 ],
2008 text: { format: { type: "json_object" } },
2009 });
2010
2011 const message = response.output.find((item) => item.type === "message");
2012 const messageContent = message?.content[0];
2013
2014 // Check if the conversation was too long for the context window, resulting in incomplete JSON
2015 if (
2016 response.status === "incomplete" &&
2017 response.incomplete_details.reason === "max_output_tokens"
2018 ) {
2019 // your code should handle this error case
2020 }
2021
2022 // Check if the OpenAI safety system refused the request and generated a refusal instead
2023 if (messageContent?.type === "refusal") {
2024 // your code should handle this error case
2025 // In this case, the .content field will contain the explanation (if any) that the model generated for why it is refusing
2026 console.log(messageContent.refusal);
2027 }
2028
2029 // Check if the model's output included restricted content, so the generation of JSON was halted and may be partial
2030 if (
2031 response.status === "incomplete" &&
2032 response.incomplete_details.reason === "content_filter"
2033 ) {
2034 // your code should handle this error case
2035 }
2036
2037 if (response.status === "completed") {
2038 // 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"
2039
2040 if (we_did_not_specify_stop_tokens) {
2041 // If you didn't specify any stop tokens, then the generation is complete and the content key will contain the serialized JSON object
2042 // This will parse successfully and should now contain {"winner": "Los Angeles Dodgers"}
2043 console.log(JSON.parse(response.output_text));
2044 } else {
2045 // Check if the response.output_text ends with one of your stop tokens and handle appropriately
2046 }
2047 }
2048} catch (e) {
2049 // Your code should handle errors here, for example a network error calling the API
2050 console.error(e);
2051}
2052```
2053
2054```python
2055we_did_not_specify_stop_tokens = True
2056
2057try:
2058 response = client.responses.create(
2059 model="gpt-5.6",
2060 input=[
2061 {
2062 "role": "system",
2063 "content": "You are a helpful assistant designed to output JSON.",
2064 },
2065 {
2066 "role": "user",
2067 "content": 'Who won the World Series in 2020? Respond as {"winner": "team name"}.',
2068 },
2069 ],
2070 text={"format": {"type": "json_object"}},
2071 )
2072
2073 message = next((item for item in response.output if item.type == "message"), None)
2074 message_content = message.content[0] if message and message.content else None
2075
2076 # Check if the conversation was too long for the context window, resulting in incomplete JSON
2077 if (
2078 response.status == "incomplete"
2079 and response.incomplete_details.reason == "max_output_tokens"
2080 ):
2081 raise RuntimeError("The response was truncated before the JSON completed.")
2082
2083 # Check if the OpenAI safety system refused the request and generated a refusal instead
2084 if message_content and message_content.type == "refusal":
2085 # your code should handle this error case
2086 # In this case, the .content field will contain the explanation (if any) that the model generated for why it is refusing
2087 print(message_content.refusal)
2088
2089 # Check if the model's output included restricted content, so the generation of JSON was halted and may be partial
2090 if (
2091 response.status == "incomplete"
2092 and response.incomplete_details.reason == "content_filter"
2093 ):
2094 raise RuntimeError("The response was interrupted by the content filter.")
2095
2096 if response.status == "completed":
2097 # 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"
2098
2099 if we_did_not_specify_stop_tokens:
2100 # If you didn't specify any stop tokens, then the generation is complete and the content key will contain the serialized JSON object
2101 # This will parse successfully and should now contain "{"winner": "Los Angeles Dodgers"}"
2102 print(response.output_text)
2103except Exception as e:
2104 # Your code should handle errors here, for example a network error calling the API
2105 print(e)
2106```
302 2107
303## Resources2108## Resources
304 2109