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.
Question types
Section titled “Question types”| 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. |
Make a request
Section titled “Make a request”POST /v1/systemone takes the same
authentication as every other endpoint:
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 rules
Section titled “Field rules”| 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.
Images
Section titled “Images”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
imagesarray of data-URL strings, max 1: the picture the state is about. - Image as an option —
{"image": "<data URL>", "text": "<optional>"}as a question’sinstructions, as the value of achoicecriterion, or as anoulcriterion’strue/falsevalue. Use it to ask “which of these images…?” or “does this image show…?”. An image-valuedscorelevel 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 withininstructionsor achoice/noulcriterion (the backend reads image options at any depth there).scorecriteria reject image options at every depth, and an image data URL anywhere else — including insidestate— is refused.
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 returns413 payload_too_large.
Discovering decision models
Section titled “Discovering decision models”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.
Errors
Section titled “Errors”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.