Skip to Content

Structured output

Use structured output for extraction, classification and machine-readable responses. Check support for both the model and the chosen endpoint. This guide covers OpenAI-compatible requests; do not copy these fields into Anthropic or Gemini bodies.

Choose a format

ModeWhat it constrainsWhat your application still validates
Prompt onlyA natural-language request to return JSONSyntax, schema and business rules
JSON modeJSON syntax, on supported modelsRequired fields, types and business rules
JSON SchemaA supported schema, with strict mode where availableCompletion status, refusals and business meaning

JSON mode uses response_format={"type": "json_object"} in Chat Completions. Include an explicit instruction to return JSON. It does not enforce your field names or types.

Chat Completions with a schema

Install openai, set FENNO_API_KEY and FENNO_MODEL, then run:

import os from openai import OpenAI client = OpenAI( base_url="https://api.fenno.ai/v1", api_key=os.environ["FENNO_API_KEY"], timeout=60.0, max_retries=0, ) import json schema = { "type": "object", "properties": { "summary": {"type": "string"}, "status": {"type": "string", "enum": ["ok", "needs_review"]}, }, "required": ["summary", "status"], "additionalProperties": False, } result = client.chat.completions.create( model=os.environ["FENNO_MODEL"], messages=[{"role": "user", "content": "Summarize: The order arrived damaged."}], response_format={ "type": "json_schema", "json_schema": {"name": "ticket", "strict": True, "schema": schema}, }, ) choice = result.choices[0] if choice.finish_reason != "stop" or choice.message.refusal: raise RuntimeError("No complete structured result") data = json.loads(choice.message.content) print(data)

json.loads checks JSON syntax, not the whole schema. Validate the parsed object with your application’s schema validator before writing to a database or triggering a workflow.

Responses uses text.format

Using the same client and schema, a Responses-capable model expects a different field layout:

response = client.responses.create( model=os.environ["FENNO_MODEL"], input="Summarize: The order arrived damaged.", text={"format": { "type": "json_schema", "name": "ticket", "strict": True, "schema": schema, }}, ) if response.status != "completed": raise RuntimeError("Incomplete response") data = json.loads(response.output_text) print(data)

Chat Completions nests the schema under response_format.json_schema; Responses puts name, schema and strict directly under text.format. Do not send response_format to Responses.

Handle failures explicitly

  • A refusal, safety block or output limit may prevent a complete structured result. Handle these states before parsing.
  • Partial JSON in a stream is not a completed object. Do not execute actions from incremental fragments.
  • Start with a small schema: explicit object types, required properties and additionalProperties: false. The supported JSON Schema subset varies by model.
  • Never turn a validation failure into success by silently dropping fields or stripping arbitrary text.
  • Schema-valid output can still contain wrong or unauthorized business values.

See Chat Completions, Responses, and the official format migration guide .