Structured outputs
Constrain replies to valid JSON with response_format — json_object and json_schema, on chat completions and the Responses API.
Two shapes
/v1/chat/completions accepts response_format and forwards it to the engine
as sent:
response_format |
What you get |
|---|---|
{ "type": "json_object" } |
the reply is a JSON object — no schema, just valid JSON |
{ "type": "json_schema", "json_schema": { "name": "…", "schema": { … }, "strict": true } } |
guided decoding against your schema — strict is optional |
This is guided decoding (a grammar constraint on the sampler). It is independent of the tool-call parser, so it works on models that cannot call tools.
Which models support it
The live catalog declares structured_outputs: { "type": "boolean" } and
response_format: { "type": "boolean" } on every text model it lists — today that
is glm-5.3, deepseek-v4-flash and qwen3.8-flash.
Read the flags from the catalog rather than hardcoding the list, and check the
entry's is_ready flag before putting a model on a production path:
curl https://api.runaii.cloud/v1/models
Runnable request
curl https://api.runaii.cloud/v1/chat/completions \
-H "Authorization: Bearer $RUNAII_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": "Extract the invoice fields."},
{"role": "user", "content": "Invoice #A-1024, due 2026-11-01, total 420.00 USD."}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "invoice",
"strict": true,
"schema": {
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"due_date": {"type": "string"},
"total": {"type": "number"},
"currency": {"type": "string"}
},
"required": ["invoice_number", "due_date", "total", "currency"],
"additionalProperties": false
}
}
}
}'
What comes back
message.content is a string containing the JSON object — JSON.parse it
before using it. finish_reason is "stop" on a complete answer ("length" if
max_tokens cut it short), and usage reports the real token counts and the exact
cost you were charged (see pricing):
{
"id": "rq-...",
"object": "chat.completion",
"model": "deepseek-v4-flash",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "{\"invoice_number\":\"A-1024\",\"due_date\":\"2026-11-01\",\"total\":420,\"currency\":\"USD\"}"
},
"finish_reason": "stop"
}
]
}
Responses API (/v1/responses)
The Responses dialect carries the same two modes under text.format, and it is
translated onto response_format for you:
text.format |
response_format sent to the engine |
|---|---|
{ "type": "text" } |
none — the default, unconstrained |
{ "type": "json_object" } |
{ "type": "json_object" } |
{ "type": "json_schema", "name": "…", "schema": { … }, "strict": true } |
{ "type": "json_schema", "json_schema": { "name": "…", "schema": { … }, "strict": true } } |
curl https://api.runaii.cloud/v1/responses \
-H "Authorization: Bearer $RUNAII_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-flash",
"input": "Invoice #A-1024, due 2026-11-01, total 420.00 USD.",
"text": {
"format": {
"type": "json_schema",
"name": "invoice",
"strict": true,
"schema": {
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"due_date": {"type": "string"},
"total": {"type": "number"},
"currency": {"type": "string"}
},
"required": ["invoice_number", "due_date", "total", "currency"],
"additionalProperties": false
}
}
}
}'
The answer arrives in the message output item's output_text content part, and
usage carries input_tokens, output_tokens, total_tokens and cost — the
same metering as chat completions.
Limits
json_schemaneeds a name and a schema. On the Responses path atext.formatthat is nottext,json_objectorjson_schema+name+schemais rejected with400 invalid_request(param: "text.format").- A schema the engine rejects is a 400, not a bill. The upstream refusal
comes back as
400 upstream_rejectedwith no credits spent — checkmessages,tools,response_formatandmax_tokenswhen you see it. - Constrained shape, not constrained content. The schema fixes the output structure; the values still come from the model, so keep validating business rules on your side.
- Not a tool substitute.
response_formatshapes the answer text; to have the model ask you to run code, use function calling instead.