Structured outputs
Structured outputs make the model answer with JSON that follows a JSON Schema you provide, so you can parse the answer instead of scraping text. You ask for it with response_format on Chat Completions and with text.format on the Responses API. Both accept the same three types.
The formats
Section titled “The formats”| Type | Effect |
|---|---|
text |
Plain text. The same as sending nothing. |
json_object |
Free-form JSON, with no schema. |
json_schema |
JSON that follows your schema. |
The two dialects spell json_schema differently, as OpenAI does:
// Chat Completions"response_format": { "type": "json_schema", "json_schema": { "name": "invoice", "description": "…", "schema": { … } }}
// Responses API"text": { "format": { "type": "json_schema", "name": "invoice", "description": "…", "schema": { … } }}| Field | Rule |
|---|---|
name |
Required. 1 to 64 letters, digits, _ or -. |
schema |
Required. A JSON Schema object, at most 64,000 characters once serialized. |
description |
Optional. Truncated to 1,000 characters. |
Other fields, such as strict, are not read. A malformed format returns 400 invalid_request, with param set to response_format or text.format.
Pick a model that supports it
Section titled “Pick a model that supports it”In GET /v1/models, a model that supports structured outputs has belarel.capabilities.structured_output: true and lists response_format in supported_parameters. See Models.
If you ask for json_object or json_schema on a model that cannot produce structured output, the call is refused before any token is generated, so it costs nothing:
{ "error": { "message": "Model \"<model-id>\" cannot produce structured output", "type": "invalid_request_error", "code": "model_lacks_structured_output", "param": "response_format" }}Example
Section titled “Example”curl https://api.belarel.com/v1/chat/completions \ -H "Authorization: Bearer $BELAREL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5", "messages": [{"role": "user", "content": "Invoice 4512 from Acme, total 1,250.00 USD, due 2026-11-30."}], "response_format": { "type": "json_schema", "json_schema": { "name": "invoice", "schema": { "type": "object", "properties": { "number": {"type": "string"}, "vendor": {"type": "string"}, "total": {"type": "number"}, "currency": {"type": "string"}, "due_date": {"type": "string"} }, "required": ["number", "vendor", "total", "currency", "due_date"], "additionalProperties": false } } } }'import jsonimport osfrom openai import OpenAI
client = OpenAI(base_url="https://api.belarel.com/v1", api_key=os.environ["BELAREL_API_KEY"])
invoice_schema = { "type": "object", "properties": { "number": {"type": "string"}, "vendor": {"type": "string"}, "total": {"type": "number"}, "currency": {"type": "string"}, "due_date": {"type": "string"}, }, "required": ["number", "vendor", "total", "currency", "due_date"], "additionalProperties": False,}
response = client.responses.create( model="openai/gpt-5", input="Invoice 4512 from Acme, total 1,250.00 USD, due 2026-11-30.", text={"format": {"type": "json_schema", "name": "invoice", "schema": invoice_schema}},)invoice = json.loads(response.output_text)print(invoice["total"])import OpenAI from 'openai';
const client = new OpenAI({ baseURL: 'https://api.belarel.com/v1', apiKey: process.env.BELAREL_API_KEY });
const completion = await client.chat.completions.create({ model: 'openai/gpt-5', messages: [{ role: 'user', content: 'Invoice 4512 from Acme, total 1,250.00 USD, due 2026-11-30.' }], response_format: { type: 'json_schema', json_schema: { name: 'invoice', schema: { type: 'object', properties: { number: { type: 'string' }, vendor: { type: 'string' }, total: { type: 'number' }, currency: { type: 'string' }, due_date: { type: 'string' }, }, required: ['number', 'vendor', 'total', 'currency', 'due_date'], additionalProperties: false, }, }, },});const invoice = JSON.parse(completion.choices[0].message.content ?? '{}');console.log(invoice.total);Reading the answer
Section titled “Reading the answer”The JSON arrives as text, where any answer would: choices[0].message.content on Chat Completions, the output_text part of the message on Responses. When you stream, it arrives in content deltas like any text. Concatenate them and parse at the end.

