Skip to content
LUNAROUTEDocs

System One (decision models)

Most LunaRoute models generate text. Decision models do something narrower and much faster: you send a state plus a map of typed questions, and each question is answered with a typed value and calibrated probabilities — never generated prose. Because answers are constrained to the shapes you define, a decision model cannot hallucinate a string; your code branches on the result directly.

That makes decision models the tool for the fast judgments inside a larger workflow: routing, triage, gating, thresholding. Combine the answers with deterministic checks in code, and use confidence to decide when to act and when to escalate to a person or a reasoning (chat) model.

Requests are non-streaming — one buffered JSON request and response, typically well under a second.

Type Answer Good for
noul A calibrated probability (0–1) that a proposition is true Yes/no gates: escalate, allow, needs-review. The value itself is the certainty — there is no separate confidence field, and a value near 0.5 means undecided, not moderately true.
choice One of up to 255 caller-defined options, with confidence and a full probability map Classification and routing over a closed label set.
score A position on an ordered rubric of up to 10 levels you describe in words — the result can be fractional Urgency, risk, quality: threshold it in code.

POST /v1/systemone takes the same authentication as every other endpoint:

Terminal window
curl https://gw.lunaroute.com/v1/systemone \
-H "LUNAROUTE-API-KEY: $LUNAROUTE_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "kev-4b",
"state": "Ticket #4812: \"I was charged twice this month and I want one of the charges refunded.\"",
"questions": {
"refund_requested": {
"type": "noul",
"instructions": "Does this message request a refund?"
},
"team": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": { "billing": "payments, charges, invoices", "bug": "broken product behavior", "account": "access, login, profile" }
},
"urgency": {
"type": "score",
"instructions": "How urgent is this for the customer?",
"criteria": ["routine", "annoyed but patient", "escalation risk"]
}
}
}'
{
"model": "kev-4b",
"answers": {
"refund_requested": { "type": "noul", "noul": 0.95 },
"team": {
"type": "choice", "choice": "billing", "confidence": 0.56,
"probabilities": { "billing": 0.56, "bug": 0.27, "account": 0.17 }
},
"urgency": {
"type": "score", "score": 1.4, "confidence": 0.78,
"legend": { "0": "routine", "1": "annoyed but patient", "2": "escalation risk" },
"probabilities": { "0": 0.05, "1": 0.61, "2": 0.34 }
}
},
"usage": { "input_tokens": 129, "output_tokens": 54 },
"latency_ms": 63
}
Field Rule
state Required. A string, JSON object, or array — the content the questions are evaluated against.
images Optional. A top-level state image: an array of at most 1 image data URL. Needs a vision-capable model — see Images.
model Required. A decision-model id from the catalog, e.g. kev-4b. The response echoes the public id. Image requests need a vision-capable decision model.
questions Required, non-empty. Each key is a caller-chosen id, echoed verbatim in answers.
type noul, choice, or score.
instructions Required per question. A string, object, or array. An object may be an image option {"image": …, "text": …} — see Images.
criteria noul: optional object with true/false descriptions. choice: required object of 1–255 options. score: required array of up to 10 levels. A choice option or a noul true/false value may be an image option; a score level may not.

Unknown top-level fields are rejected (typo protection).

Whether System One is available to you is set per organization and overrides the plan: off (403 systemone_policy_disabled), included (no per-request charge), or metered. Metered requests are billed on input tokens only; usage.output_tokens is reported but never billed.

Questions in one request share the same state, which is counted once — not once per question — so batching related questions into one request is much cheaper than repeating the state.

Limits are per-model (state size, request size, question count, input tokens) and enforced fail-closed: an over-limit request is rejected with a 400, never silently truncated.

The options field is not accepted; as with any unknown top-level field it is rejected with a 400.

A decision model that is vision-capable (the vision capability on top of systemone) accepts image input; the djev model is. A text-only decision model returns 400 systemone_invalid_request for an image-bearing request.

Images appear in two places:

  • State image — a top-level images array of data-URL strings, max 1: the picture the state is about.
  • Image as an option — {"image": "<data URL>", "text": "<optional>"} as a question’s instructions, as the value of a choice criterion, or as a noul criterion’s true/false value. Use it to ask “which of these images…?” or “does this image show…?”. An image-valued score level is rejected — score levels are described in words. Instructions and criteria may be structured JSON, so an image option may sit inside a nested object or array within instructions or a choice/noul criterion (the backend reads image options at any depth there). score criteria reject image options at every depth, and an image data URL anywhere else — including inside state — is refused.
Terminal window
curl https://gw.lunaroute.com/v1/systemone \
-H "LUNAROUTE-API-KEY: $LUNAROUTE_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "djev",
"state": "Which screenshot matches the customer report?",
"images": ["data:image/png;base64,…"],
"questions": {
"matching": {
"type": "choice",
"instructions": "Which screenshot matches the customer report?",
"criteria": {
"first": { "image": "data:image/png;base64,…", "text": "the first screen" },
"second": { "image": "data:image/png;base64,…", "text": "the second screen" }
}
}
}
}'

Images must be data URLs — data:image/jpeg;base64,, data:image/png;base64,, or data:image/webp;base64, followed by base64-encoded bytes. Remote http(s) URLs are not accepted. Limits, all fail-closed with 400 systemone_invalid_request:

  • at most 6 image attachments per request, counting the state image and every image option;
  • at most 5 MiB decoded per image;
  • the serialized request, as forwarded to the model, must fit the model’s request-size cap — exceeding it is 400 systemone_invalid_request. The raw (pre-decode) request body is separately capped route-wide at 8 MiB by default; a body over that cap returns 413 payload_too_large.

GET /v1/models lists decision models alongside chat models, but the CLI’s model listing omits them (they do not answer on chat routes), and there is no /v1/systemone/models — the shared catalog is the discovery surface. Decision-model ids answer only on POST /v1/systemone; chat-model ids are rejected here, and decision ids are rejected on chat, embedding, and image routes.

The error envelope is shared. Codes specific to this endpoint:

Status Code Meaning
400 systemone_invalid_request Structural validation failed — bad question type, criteria shape, over a model limit, or the model cannot serve decisions (e.g. a chat-model id).
402 insufficient_credits / reservation_too_large Wallet refused the reservation; reservation_too_large names the organization bound as credits_bound.
403 systemone_policy_disabled Decision models are not enabled for your organization or plan.
404 model_not_found Unknown model id.
408 body_read_timeout The request body was not received within the slow-body timeout.
413 payload_too_large Request body over the size cap.
415 unsupported_content_encoding Compressed request bodies are not accepted.
429 systemone_gate_busy / systemone_backend_busy Decision capacity is busy (gate_busy), or the upstream is rate-limiting (backend_busy) — carries Retry-After.
502 systemone_backend_error The upstream failed or answered unusable (5xx, malformed, or over cap).
503 systemone_disabled The feature is switched off platform-side.
503 systemone_models_unavailable and siblings Temporary platform-side unavailability (catalog, policy, billing, draining).
504 systemone_deadline_exceeded The request outlived its deadline.

Retries follow the shared rules on Errors & rate limits.