Structured output (JSON mode)

What you'll build: a call that returns JSON guaranteed to match your schema, so you can parse it directly instead of writing defensive cleanup around freeform text.

SectionAPI fundamentals
CapabilitiesStructured output
Time to complete~10 min
Modelmuse-spark-1.3
LanguagePython
Prerequisitesthe chat completions recipe, Python 3.10+, the openai package, and a MODEL_API_KEY.

Pass a JSON schema in response_format and the model's output is constrained to conform. That turns "parse the model's prose and hope" into "load a known shape." See the structured output guide.

Setup

One cell wires up the client: it reads your key from the environment and falls back to Colab secrets when you run in a notebook.

python
import os
from openai import OpenAI
# Auth: in Colab, read the key from the secrets manager (the key icon in the
# left sidebar); otherwise read the MODEL_API_KEY environment variable.
try:
from google.colab import userdata
api_key = userdata.get("MODEL_API_KEY")
except Exception:
api_key = os.environ.get("MODEL_API_KEY")
if not api_key:
raise RuntimeError(
"Set MODEL_API_KEY in your environment (or add it to Colab secrets)."
)
# The OpenAI SDK does not auto-read MODEL_API_KEY, so pass it explicitly.
client = OpenAI(base_url="https://api.meta.ai/v1", api_key=api_key)
# Connectivity check: a one-line call that confirms the key and endpoint work.
ping = client.chat.completions.create(
model="muse-spark-1.3",
messages=[{"role": "user", "content": "Reply with the single word: ready"}],
)
print(ping.choices[0].message.content)

Ask for JSON the naive way

A common first attempt is to ask for JSON in the prompt and parse the reply. The problem: the reply is freeform text. The model often adds a sentence of preamble or wraps the JSON in a markdown fence, and json.loads() chokes on anything that isn't pure JSON.

python
import json
response = client.chat.completions.create(
model="muse-spark-1.3",
messages=[
{
"role": "user",
"content": (
"Describe the Keychron K2 in one sentence, then give its specs as JSON "
"with keys name, price_usd, and in_stock."
),
}
],
)
content = response.choices[0].message.content
print("Raw output:\n", content[:240], "...\n")
try:
data = json.loads(content)
print("Parsed — but only because this reply happened to be clean:", data)
except json.JSONDecodeError as exc:
print("json.loads failed:", exc)

Constrain the output with a schema

Pass response_format with type: "json_schema" and a schema. Constrained decoding guarantees the output conforms, so json.loads() always succeeds. Set additionalProperties: false and list required keys to lock the shape down.

python
schema = {
"type": "json_schema",
"json_schema": {
"name": "product",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"price_usd": {"type": "number"},
"in_stock": {"type": "boolean"},
},
"required": ["name", "price_usd", "in_stock"],
"additionalProperties": False,
},
},
}
response = client.chat.completions.create(
model="muse-spark-1.3",
messages=[
{
"role": "user",
"content": (
"Describe the Keychron K2 in one sentence, then give its specs as JSON "
"with keys name, price_usd, and in_stock."
),
}
],
response_format=schema,
)
data = json.loads(response.choices[0].message.content)
print(data)

Parse straight into a typed object with Pydantic

client.beta.chat.completions.parse() takes a Pydantic model as response_format and returns the typed object on message.parsed. The schema comes from your model definition, so there is one source of truth for the shape.

python
from pydantic import BaseModel
class Product(BaseModel):
name: str
price_usd: float
in_stock: bool
completion = client.beta.chat.completions.parse(
model="muse-spark-1.3",
messages=[
{"role": "user", "content": "Product: Logitech MX Master 3S, $99, sold out."}
],
response_format=Product,
)
product = completion.choices[0].message.parsed
print(type(product).__name__, "->", product)
print("in_stock:", product.in_stock)

Validate structured output in production

  • Lock the shape. Set additionalProperties: false and mark every field you depend on as required; use enum for closed value sets.
  • Prefer the Pydantic path when you already model the data in Python: one definition drives both the request and the parsed type.
  • Recursive schemas are rejected. A self-referencing $ref returns HTTP 400 on both endpoints; flatten it or cap the depth.
  • On the Responses API the parameter is text.format, not response_format. Sending response_format there returns HTTP 400. See the structured output guide.
  • Structured output vs tool calling. Use structured output when you want one JSON object back; use tool calling when the model should choose to invoke your code.

Summary

PatternHow
Guarantee JSONresponse_format={"type": "json_schema", "json_schema": {...}}
Lock the shapeadditionalProperties: false + required + enum
Typed objectclient.beta.chat.completions.parse(..., response_format=PydanticModel)message.parsed
Read itjson.loads(response.choices[0].message.content) always parses
Responses APIuse text.format, not response_format

Next steps