Skip to content
kenari.

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:

Terminal window
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.

FieldTypeRequiredDescription
modelstringyesA System One model id.
statestring, object or arrayyesThe text or data to evaluate. A fields call can leave it out when it sends an image.
questionsobjectyes, or fieldsOne or more questions. Each key is a question id you choose, and the answer comes back under the same key. Up to 128 questions.
fieldsobjectyes, or questionsClairic only. The typed fields to extract, in place of questions. Send questions or fields, not both. See Extract fields.
reference_datestringnoClairic only. An ISO date, such as 2026-10-25, that anchors dates written without a year.
optionsobjectnoClairic 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.
assetsobjectnoImages, each under a reference name you choose. See Images.
imagesarraynoImages in order, for the models that take this shape. Send it or assets, not both.
audioarraynoAudio clips, for the models that read audio. See Audio and video.
videosarraynoVideos, 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:

FieldTypeRequiredDescription
typestringyesnoul, choice, score or box.
instructionsstring, object or arrayyesThe question itself.
criteriaobject or arraynoFor noul, true and false descriptions. For choice, a map of the options. For score, an ordered rubric. box takes none.

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 }
}

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.

FieldTypeRequiredDescription
typestringnoimage.
formatstringyespng, jpg or jpeg. It must match the image bytes.
data_base64stringyesThe 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.

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 }

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.

FieldLimitTypes
audioUp to 4 clips, 8 MiB eachaudio/wav (also audio/x-wav and audio/wave) or audio/mpeg (also audio/mp3)
videosUp to 2 videos, 16 MiB eachvideo/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.

Terminal window
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.

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:

TypeReturns
textA piece of the state, copied as written.
numberA number read or worked out from the state.
date, time, datetimeA calendar date, a time of day or both, in ISO form.
durationA length of time.
listEvery matching text, number or date, in the order they appear.
estimateAn estimate of a number or date between the min and max you set.
forecastOne 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.

Terminal window
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 }
}
KeyMeaning
typeThe field’s type.
valueThe answer, or null when the input does not determine it.
guessThe best candidate. It stays in the answer when value is null.
confidenceHow sure the model is of the answer.
p_nullThe probability that the input does not determine the value.
spanFor 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.

Terminal window
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?"
}
}
}'

There is no SDK method for this endpoint, so use requests.

import os
import 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"])
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();

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.

StatusCodeWhen
400bad_requestmodel 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.
400bad_requestThe 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.
400bad_requestThe 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.
400bad_requestfields 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.
400model_not_foundThe model is listed but no provider serves it right now. Retry later.
413noneThe request body is above 32 MiB. The response is plain text.

See Errors for the shared codes, such as all_providers_failed.