Decisions
POST /v1/decisions — typed decisions and calibrated probabilities instead of generated text.
POST /v1/decisions returns typed answers rather than prose. You send a state and a map
of typed questions; the model evaluates them and returns one answer per question, each with
calibrated probabilities.
This is the endpoint for the decisions your program makes, not the text a person reads:
intent routing, moderation and triage, relevance scoring, and agent context retention. The
assigned route must include a model with the decisions capability.
Request
curl https://ai.latentkit.com/v1/decisions \
-H "Authorization: Bearer $LATENTKIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": "My card was declined twice and nobody has replied to my email.",
"questions": {
"intent": {
"type": "choice",
"instructions": "Which category applies?",
"criteria": {
"billing": "Payment-related",
"technical": "Technical issue",
"other": "Anything else"
}
},
"urgency": {
"type": "score",
"instructions": "How urgent is this?",
"criteria": ["not urgent", "somewhat urgent", "very urgent"]
},
"complaint": {
"type": "noul",
"instructions": "Is the customer complaining?"
}
}
}'Fields
| Field | Description |
|---|---|
state | What to evaluate: a plain string, or structured data (required) |
questions | Map of your own question id to a question object; at least one (required) |
purpose | Routing label configured for your app |
response_profile | fast, balanced, or thinking when the route allows overrides |
There is no stream, temperature, max_tokens, tools, or response_format. A decision
model has no dial for any of them, so sending one returns a 400 naming the field rather
than being silently dropped.
state and instructions each accept a string, an object, or an array. Structured values
reach the model unchanged, so you can put a record in one field and the question in
another and refer to the data by name.
Question types
| Type | criteria | Answer |
|---|---|---|
noul | Optional {"true": …, "false": …} | noul — probability between 0 and 1 (no confidence) |
choice | Required object, up to 255 options | choice (your option key), probabilities, confidence |
score | Required array of 2–10 level descriptions, lowest first | score (probability-weighted), legend, probabilities, confidence |
Question ids are yours. The answers come back keyed by the same ids.
SDK
const decision = await client.decisions.create({
state: 'My card was declined twice and nobody has replied to my email.',
questions: {
intent: {
type: 'choice',
instructions: 'Which category applies?',
criteria: { billing: 'Payment-related', technical: 'Technical issue', other: 'Anything else' },
},
},
});
if (decision.answers.intent.choice === 'billing') {
// route to the billing queue
}decision = client.decisions.create(
state="My card was declined twice and nobody has replied to my email.",
questions={
"intent": {
"type": "choice",
"instructions": "Which category applies?",
"criteria": {
"billing": "Payment-related",
"technical": "Technical issue",
"other": "Anything else",
},
}
},
)
if decision["answers"]["intent"]["choice"] == "billing":
...latentkit decide "My card was declined twice" --ask intent:choice:billing,technical,otherResponse
{
"provider": "typesafe",
"model": "jev-1.13.0",
"content": "",
"answers": {
"intent": {
"type": "choice",
"choice": "billing",
"probabilities": { "billing": 0.94, "technical": 0.04, "other": 0.02 },
"confidence": 0.94
},
"urgency": {
"type": "score",
"score": 2.05,
"legend": { "0": "not urgent", "1": "somewhat urgent", "2": "very urgent" },
"confidence": 0.71
},
"complaint": { "type": "noul", "noul": 0.88 }
},
"usage": { "input_tokens": 318, "output_tokens": 34, "total_tokens": 352 }
}Decision responses use the unified runtime envelope (id, provider, model,
policy_version_id, usage) with two differences worth knowing:
contentis always empty. A decision is machine-readable output and is never serialized as assistant text. Readanswers.output_tokensis reported but free. TypeSafe charges per input token and prices output tokens at zero, so billing is input-only. The count is still recorded in your usage and analytics.
Routing
Decision routes and chat routes never overlap. A decision model can never be selected for
/v1/chat, and a chat model can never be selected for /v1/decisions — there is no silent
fallback from a decision to a generated answer in either direction.
Troubleshooting
NO_HEALTHY_PROVIDERmeans no route model has thedecisionscapability. Check the route's model list in the console.- A
400naming a field such astemperatureorstreammeans the request carried a generative option this endpoint does not accept. Remove it.