System One
Ask typed questions about a piece of text, data, an image, audio or video and get one typed answer per question. The Clairic model extracts values instead: you list typed fields and get one typed value per field. System One models are decision and extraction models, not chat models. Because the request is not a chat request, this is kenari’s own contract and no OpenAI or Anthropic SDK method maps to it.
POST /v1/systemone
Only System One models are served here. Calling it with a chat model returns 400, and calling a chat endpoint with a System One id returns 400 that points here. List the current System One models with a public call that needs no key:
curl "https://kenari.id/v1/models?modality=systemone"A plain GET /v1/models does not list embedding, rerank, moderation or System One models. It does list chat, image, speech, transcription and video models. The examples below use free models. Swap in an id the call above returns, as long as the model takes what you send. There is no streaming.
Request
Section titled “Request”| Field | Type | Required | Description |
|---|---|---|---|
model | string | yes | A System One model id. |
state | string, object or array | yes | The text or data to evaluate. A fields call can leave it out when it sends an image. |
questions | object | yes, or fields | One or more questions. Each key is a question id you choose, and the answer comes back under the same key. Up to 128 questions. |
fields | object | yes, or questions | Clairic only. The typed fields to extract, in place of questions. Send questions or fields, not both. See Extract fields. |
reference_date | string | no | Clairic only. An ISO date, such as 2026-10-25, that anchors dates written without a year. |
options | object | no | Clairic only. include_quantiles adds 99 percentiles to estimates. return_state_text returns the text that the span positions in the answers refer to, as state_text. Both are booleans. |
assets | object | no | Images, each under a reference name you choose. See Images. |
images | array | no | Images in order, for the models that take this shape. Send it or assets, not both. |
audio | array | no | Audio clips, for the models that read audio. See Audio and video. |
videos | array | no | Videos, for the models that read video. See Audio and video. |
The request body can be up to 512 KiB, not counting image, audio and video data. The whole body, that data included, can be up to 32 MiB, and a larger one is rejected with 413. Base64 adds about a third to the size of a file. Each question is an object:
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | noul, choice, score or box. |
instructions | string, object or array | yes | The question itself. |
criteria | object or array | no | For noul, true and false descriptions. For choice, a map of the options. For score, an ordered rubric. box takes none. |
Response
Section titled “Response”The response contains only model, answers and usage, plus truncated on a Clairic call and state_text when you ask for it. model is the versioned id that answered, and falls back to the id you requested. answers has one entry per question id, or one per field name for a fields call. usage reports input_tokens and output_tokens. A Clairic response is shown under Extract fields.
{ "model": "clef:free", "answers": { "q": { "type": "noul", "noul": 0.993 } }, "usage": { "input_tokens": 47, "output_tokens": 0 }}Images
Section titled “Images”Some System One models also read images. They list image in modalities.input in the ?modality=systemone listing. Sending an image or a box question to any other model returns 400.
Put each image in assets under a reference name you choose. Then write that name where the image belongs: in state, in a question’s instructions or criteria, or in a field’s instructions. The name can be the whole string or sit inside text. Object keys and the question type are never read as references.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | no | image. |
format | string | yes | png, jpg or jpeg. It must match the image bytes. |
data_base64 | string | yes | The image file in standard base64, without a data: prefix. |
The fields below go in the request next to model, with an id from that listing:
{ "assets": { "receipt": { "type": "image", "format": "png", "data_base64": "iVBORw0KGgo..." } }, "state": "receipt", "questions": { "itemized": { "type": "noul", "instructions": "Does receipt list a price for every item?" }, "total": { "type": "box", "instructions": "The total amount in receipt" } }}A request carries up to 16 images, each at most 16 MiB. A reference name is 1 to 1,024 bytes, and all names together at most 65,536 bytes. Each model also has its own limits on how many images it reads, their size, their pixel dimensions and their file type. A request outside them returns 400 with a message that names the limit.
Some models also take an images array instead of assets. Each item is a data:image/png;base64,... URL, with image/png, image/jpeg or image/webp, or an object with content_type and base64. The model reads the images in array order. Send assets or images, not both. A model that reads only assets returns 400 for images.
Box questions
Section titled “Box questions”A box question asks where something is in an image. It takes no criteria, and the request needs at least one image. The answer gives each edge of the box as a fraction of the image height or width, from 0 to 1, plus a confidence. Not every image model answers box questions.
{ "type": "box", "box": { "top": 0.62, "bottom": 0.7, "left": 0.55, "right": 0.9 }, "confidence": 0.93 }Audio and video
Section titled “Audio and video”Clef Omni reads audio and video as well as text and images. Models that read them list audio or video in modalities.input in the ?modality=systemone listing. Sending audio or video to any other model returns 400.
Send audio in audio and video in videos, next to model, state and questions. state is still required, so add a short line of text about what you send. Each item is a data: URL with base64 content, or an object with content_type and base64. kenari does not fetch links, so a URL returns 400.
| Field | Limit | Types |
|---|---|---|
audio | Up to 4 clips, 8 MiB each | audio/wav (also audio/x-wav and audio/wave) or audio/mpeg (also audio/mp3) |
videos | Up to 2 videos, 16 MiB each | video/mp4 or video/webm |
Audio and video together can be at most 16 MiB per request. Sizes count the file, not its base64 text. The file must be what its type says, a WAV or MP3 for audio and an MP4 or WebM for video, or the request returns 400.
curl https://kenari.id/v1/systemone \ -H "Authorization: Bearer $KENARI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "clef-omni:free", "state": "A voice note and a short video from the front door.", "audio": ["data:audio/wav;base64,UklGRiQAAABXQVZF..."], "videos": [{ "content_type": "video/mp4", "base64": "AAAAIGZ0eXBpc29t..." }], "questions": { "delivered": { "type": "noul", "instructions": "Was a parcel left at the door?" } } }'The base64 in this example is cut short, so send the whole file. The response has the same shape as for any call with questions.
Extract fields
Section titled “Extract fields”Clairic reads a text, and images if you send them, and extracts one typed value per field. Send fields in place of questions, and send exactly one of the two. Each key in fields is a field name you choose, and the answer comes back under the same name. state is the text to read. An object or an array is sent on as its JSON text. It can be left out only when the request sends an image, and otherwise it must not be empty.
Each field is an object with a type and an optional instructions string that says what the field should hold:
| Type | Returns |
|---|---|
text | A piece of the state, copied as written. |
number | A number read or worked out from the state. |
date, time, datetime | A calendar date, a time of day or both, in ISO form. |
duration | A length of time. |
list | Every matching text, number or date, in the order they appear. |
estimate | An estimate of a number or date between the min and max you set. |
forecast | One estimate for each future step of a series. |
A field name is 1 to 64 characters from A to Z, a to z, 0 to 9, underscore, dot and hyphen, and starts with a letter or underscore. A request holds 1 to 64 fields. kenari sends each field on as you wrote it, so a field also takes the settings of its type, such as unit, integer and decimals for a number, items for a list, min, max, step, scale and null_threshold for an estimate or a forecast, and horizon and interval for a forecast.
curl https://kenari.id/v1/systemone \ -H "Authorization: Bearer $KENARI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "clairic:free", "state": "Invoice INV-2291, issued 3 March 2026. Total due: Rp 4,250,000.", "fields": { "invoice_number": { "type": "text", "instructions": "The invoice number" }, "total": { "type": "number", "instructions": "The total due in rupiah" } } }'The response keeps model, answers and usage, with one answer per field name. It adds truncated, which is true when the state was cut to fit the model, and state_text when you set options.return_state_text to true.
{ "model": "clairic:free", "answers": { "invoice_number": { "type": "text", "value": "INV-2291", "guess": "INV-2291", "confidence": 0.999997, "p_null": 0.000056, "span": [8, 16] }, "total": { "type": "number", "value": 4250000, "guess": 4250000, "confidence": 0.99999, "p_null": 0.000019 } }, "truncated": false, "usage": { "input_tokens": 71, "output_tokens": 0 }}| Key | Meaning |
|---|---|
type | The field’s type. |
value | The answer, or null when the input does not determine it. |
guess | The best candidate. It stays in the answer when value is null. |
confidence | How sure the model is of the answer. |
p_null | The probability that the input does not determine the value. |
span | For a text field, the start and end positions of guess in the text that was read. state_text returns that text. |
A list field adds spans, and an estimate field adds median, p10 and p90. Every value is well formed for its type, but not always correct, so use confidence and p_null to decide which answers to check by hand.
Examples
Section titled “Examples”curl https://kenari.id/v1/systemone \ -H "Authorization: Bearer $KENARI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "clef:free", "state": "a cat sitting on a rug", "questions": { "q": { "type": "noul", "instructions": "Is this a sentence about an animal?" } } }'Python
Section titled “Python”There is no SDK method for this endpoint, so use requests.
import osimport requests
resp = requests.post( "https://kenari.id/v1/systemone", headers={"Authorization": f"Bearer {os.environ['KENARI_API_KEY']}"}, json={ "model": "clef:free", "state": "a cat sitting on a rug", "questions": { "q": { "type": "noul", "instructions": "Is this a sentence about an animal?", } }, },)print(resp.json()["answers"])JavaScript
Section titled “JavaScript”const resp = await fetch("https://kenari.id/v1/systemone", { method: "POST", headers: { Authorization: `Bearer ${process.env.KENARI_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: "clef:free", state: "a cat sitting on a rug", questions: { q: { type: "noul", instructions: "Is this a sentence about an animal?", }, }, }),});const { answers } = await resp.json();Billing
Section titled “Billing”System One is billed per input token. Output tokens are reported and not billed. Images, audio and video count as input tokens. The balance held for a request counts tokens for each image from its pixel size, once for every place its reference name appears. For each audio clip and each video it counts a fixed worst case, because kenari does not read how long a clip is. A free model such as clef:free charges Rp 0. See How billing works for how the estimate, the reservation and the price dimensions work.
Errors
Section titled “Errors”| Status | Code | When |
|---|---|---|
| 400 | bad_request | model is unknown or is not a System One model, state is missing or is not a string, object or array, questions and fields are both sent or both missing, questions is empty or has more than 128 entries, a question has an unknown type or no instructions, or the body is over 512 KiB without image, audio and video data. |
| 400 | bad_request | The model does not read images, an image is not valid base64 or does not match its format, a box question has criteria or the request has no image, or the request is over the model’s image limits. |
| 400 | bad_request | The model does not read audio or video, an audio or videos item is a URL or is not valid base64, its type is not an accepted one or does not match the file, or the request is over the clip limits. |
| 400 | bad_request | fields is sent to a model other than Clairic, or questions is sent to Clairic. A field name or type is not valid, fields has more than 64 entries, reference_date is not a string, an option is not true or false, or state is empty and no image is sent. |
| 400 | model_not_found | The model is listed but no provider serves it right now. Retry later. |
| 413 | none | The request body is above 32 MiB. The response is plain text. |
See Errors for the shared codes, such as all_providers_failed.