API reference
Guaranteed JSON and function calling
Enforcing the shape of the answer instead of hoping for it: response_format, json_schema and tools. For anyone extracting fields from documents.
Last updated: 2026-10-03
If you need to extract fields from a document and load them into a business system, this is the page you need. You do not have to write a tolerant parser, and you do not have to hope that the model answers in the right format: you enforce it.
Both features work on every chat model in the catalogue and follow the shape of the OpenAI API, so they work with the libraries you already use.
Guaranteed JSON
With response_format the engine constrains generation: the answer is valid
JSON, not "usually" valid JSON.
curl https://api.ai.itsincom.org/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemma-4-26b",
"messages": [{"role": "user", "content": "Extract the fields from this invoice: ..."}],
"response_format": {"type": "json_object"}
}'
With a schema, if you also want the right fields
json_object guarantees that it is JSON. To guarantee which keys are there
and of which type, pass the schema:
{
"model": "gemma-4-26b",
"messages": [{"role": "user", "content": "…"}],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "invoice",
"schema": {
"type": "object",
"properties": {
"document_number": {"type": "string"},
"date": {"type": "string"},
"total": {"type": "number"},
"vat_rate": {"type": "number"}
},
"required": ["document_number", "date", "total"]
}
}
}
}
The model cannot produce a document that breaks the schema. The required keys are always there, and the types are the declared ones.
Function calling
Pass tools and the model answers with tool_calls instead of text, exactly as
with the OpenAI API.
{
"model": "gemma-4-26b",
"messages": [{"role": "user", "content": "Record invoice 2026-114 for 1,250 francs"}],
"tools": [{
"type": "function",
"function": {
"name": "record_invoice",
"description": "Create a draft supplier invoice",
"parameters": {
"type": "object",
"properties": {
"number": {"type": "string"},
"amount": {"type": "number"}
},
"required": ["number", "amount"]
}
}
}]
}
The response contains choices[0].message.tool_calls, with function.name and
function.arguments as a JSON string.
Until when it did not work
From 16 to 18 August 2026 the gateway did not forward response_format to
the engine: whoever passed it got free prose, or an object wrapped in a markdown
fence, while this page promised the opposite. Fixed on 18 August. If you wrote a
downstream validation to work around it, you can keep it — it does no harm — but
you no longer need it.
With stream: true the defect lasted two more days: the format was forwarded on
the normal path but not on the streaming one, so whoever asked for JSON while
streaming still got prose. That was also fixed on 18 August; response_format
now works on both paths.
In the same round tool_choice: "required" was fixed; it used to answer 502.
The cause was not the value: a "properties": {} in the schema reached the
engine as [], and the engine rejected the grammar.
What we do not guarantee, and should say
Numbers must be recomputed. The model reads well, but a total that was read is not a total that was checked: add up the lines and compare. It holds for any model, not only ours, and it is why the result should always land in a draft to be confirmed, never straight into the books.
The schema constrains the shape, not the truth. If the date field is
required, it is in the answer; whether it is the right date is for your check to
say, not us.
enum inside tools is not enforced. In the response_format schema it
is; in the parameters of a function it is not, because there the constraint does
not go through the grammar. The model tends to answer with the word in the
language of the conversation: "urgente" instead of "urgent". Two things
work, and integrators taught them to us: putting the mapping in the property's
description ("urgente/urgentissimo=urgent, alta=high") raises adherence a lot,
and validating downstream remains necessary.
The model sometimes writes the string "null". On a field that allows
null, the four-letter word may arrive instead of the JSON value. We fix it in
tool-call arguments (what we change); in your own
parsing, treat "null", "none", "nessuno" and the empty string as absence.
On a Swiss invoice, read the QR code first. If the document has the payment part with the code, IBAN, reference, amount and creditor can be read deterministically, without a model and without margin of error. Use the model for what the code does not contain: document number, dates, lines, VAT rates. It is the right order, and it costs you less.
Search on your documents
To upload documents, ask a question and get the answer with the passage it comes from, see Knowledge bases. If your case is extracting fields from invoices you do not need it — the two features above are enough.