Structured output
When you need an object from the model rather than prose — a product card, a classification result, arguments for a function — POST /v1/chat/completions takes a response_format parameter. It is passed to the upstream as is, so it behaves exactly like the OpenAI API. Billing is the usual per-token kind — there is no surcharge for structured output.
response_format combines freely with the other parameters: stream, reasoning_effort and tools can all appear in the same request — call a tool and get the function’s answer back as schema-conformant JSON, for example.
json_object — just valid JSON
Section titled “json_object — just valid JSON”The {"type": "json_object"} mode obliges the model to answer with valid JSON: no preamble, no markdown wrapper, no mid-object truncation.
One requirement worth following: the word “JSON” must appear in the prompt itself. That is OpenAI’s rule — some models, not seeing it, refuse to answer or the request fails with a 400. The simplest fix is to say it directly: “Return the answer as JSON with the fields …”.
import jsonfrom openai import OpenAI
client = OpenAI( base_url="https://api.mixen.ai/v1", api_key="mxn-...",)
resp = client.chat.completions.create( model="gpt-5.6-luna", messages=[{ "role": "user", "content": ( "Extract the sender, the recipient and the transfer amount from the email. " "Return the answer as JSON with the fields from, to, amount (a number, in rubles).\n\n" "Email: «Ivan, hi! Please transfer 1500 rubles to Katya by Friday. — Maria»" ), }], response_format={"type": "json_object"},)
data = json.loads(resp.choices[0].message.content)print(data["from"], data["to"], data["amount"])curl https://api.mixen.ai/v1/chat/completions \ -H "Authorization: Bearer $MIXEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5.6-luna", "messages": [{ "role": "user", "content": "Extract the sender, the recipient and the transfer amount from the email. Return the answer as JSON with the fields from, to, amount (a number, in rubles).\n\nEmail: «Ivan, hi! Please transfer 1500 rubles to Katya by Friday. — Maria»" }], "response_format": {"type": "json_object"} }'message.content arrives as a string roughly like this:
{"from": "Maria", "to": "Katya", "amount": 1500}Note that content stays a string — Mixen and the upstream guarantee valid JSON inside it, but the client does the parsing (json.loads). That is the general rule of structured output: the guarantee lives on the model’s side, the parsing lives in your code.
Only syntax is guaranteed. Which fields end up inside and of what types is decided by the prompt; for a hard schema, see the next mode.
json_schema — the response follows a schema
Section titled “json_schema — the response follows a schema”The json_schema mode is stricter: the model must build the answer according to a schema — exactly those fields, exactly those types. The schema travels inside response_format, and "strict": true turns on strict generation.
A pydantic model is a convenient way to describe the schema: .model_json_schema() yields a ready dict, and the same model then validates the response:
import jsonfrom openai import OpenAIfrom pydantic import BaseModel
client = OpenAI( base_url="https://api.mixen.ai/v1", api_key="mxn-...",)
class ProductCard(BaseModel): name: str # product name price: int # price in rubles tags: list[str] # up to five tags
resp = client.chat.completions.create( model="gpt-5.6-luna", messages=[{ "role": "user", "content": ( "Build a product card: name, price in rubles and up to five tags.\n\n" "Product: wireless headphones with active noise cancellation, 7,990 ₽" ), }], response_format={ "type": "json_schema", "json_schema": { "name": "product_card", "schema": ProductCard.model_json_schema(), "strict": True, }, },)
parsed = json.loads(resp.choices[0].message.content)card = ProductCard.model_validate(parsed)print(card.name, card.price, card.tags)curl https://api.mixen.ai/v1/chat/completions \ -H "Authorization: Bearer $MIXEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5.6-luna", "messages": [{ "role": "user", "content": "Build a product card: name, price in rubles and up to five tags.\n\nProduct: wireless headphones with active noise cancellation, 7,990 ₽" }], "response_format": { "type": "json_schema", "json_schema": { "name": "product_card", "strict": true, "schema": { "type": "object", "properties": { "name": {"type": "string"}, "price": {"type": "integer"}, "tags": {"type": "array", "items": {"type": "string"}} }, "required": ["name", "price", "tags"], "additionalProperties": false } } } }'A few notes on the examples:
- The schema can be written by hand, as in the cURL example — pydantic is optional. For strict mode OpenAI usually requires every field to be listed in
requiredandadditionalPropertiesto befalse. model_validatehere is a safety net, not a replacement for strict generation: the answer almost always matches the schema already, but checking on your side is cheaper than catching a surprise in production.- As in the first mode,
message.contentis a string. Theparse(..., response_model=...)method of the OpenAI SDK assumes the API does the validation — here the upstream does it at generation time, so parsing and checking are your code’s job: an explicitjson.loadsplus validation.
If the model doesn’t support it
Section titled “If the model doesn’t support it”json_schema with strict is a capability of the specific model, not part of the protocol. A model that lacks it returns 400 with the upstream’s error (error codes and formats are covered in Errors). Two workable moves:
- Fallback without
strict. Describe the schema in words inside the prompt — “Return JSON with the fields name (string), price (integer), tags (array of strings, up to five)” — and keep{"type": "json_object"}. Syntax validity is guaranteed, field conformance is checked by the same pydantic model viamodel_validate; on a mismatch, simply retry the request. - A probe request. Whether a particular model supports strict schemas is fastest to check with one cheap request carrying a minimal schema — then pin the result in your application’s configuration.
Limitations
Section titled “Limitations”/v1/chat/completionsonly. The/v1/messagesand/v1/responsesendpoints do not acceptresponse_format.message.contentis a string. The shape guarantee lives in the upstream; parsing the JSON is always the client’s job.strictdepends on the model: some support it, some don’t. There is no universal flag in the catalog — check with a probe request.
A word about the Anthropic protocol: /v1/messages has no structured-output parameter, and that is not a Mixen restriction but how the protocol itself is built. The same effect is assembled there through tool-use: you describe a tool whose input_schema matches the schema you need, force the call via tool_choice, and read the JSON from the call’s arguments.
See also: Chat and streaming and Tool calling.