For the complete documentation index, see llms.txt. Markdown versions of all pages are available by appending .md to any URL (e.g. /get-started.md).
Decisions
The /v1/decisions endpoint turns a model into a decision model. You send an
input and a list of typed questions, and each answer comes back with the
probability of every option. The probabilities are read from the model's
next-token scores at the answer position, so no text is generated and no output
is parsed.
Use it to route tickets, classify or filter text, rate content on a scale, or answer yes/no checks, where you need a number you can threshold or rank on rather than a sentence you have to parse.
The wire format matches
SGLang's /v1/decisions,
so existing clients work unchanged. /v1/decisions is an extension under /v1,
like /v1/embeddings, and isn't part of the OpenAI API. Call it over HTTP
rather than through an OpenAI SDK method.
Start the server
Any text generation model serves decisions, with no extra flag:
max serve --model-path Qwen/Qwen3.5-2BThe first decision request compiles a small label-gather graph, so it takes a few seconds longer than the ones after it. Decisions work with the overlap scheduler and device graph capture, though a step that scores a request waits for the GPU rather than overlapping.
A model that ships a decider_config.json, such as Mapika/decider-2b, is
served in the decider prompt format that it was trained on. MAX checks for the
file when the server starts. Every other model uses its chat template.
The model needs a chat template whose answer position follows a closed (or
absent) reasoning block. MAX turns thinking off for these requests. A request
that turns it on, or a model that can't answer with one-token labels, is
rejected with a 400 that names the question. A request that doesn't match the
schema is a 422.
Ask questions
curl -X POST http://localhost:8000/v1/decisions \
-H "Content-Type: application/json" \
-d '{
"input": "I was charged twice for order A-104. Please refund one.",
"questions": [
{
"id": "team",
"type": "choice",
"question": "Which team should handle this?",
"options": [
{"name": "billing", "description": "Charges and refunds"},
{"name": "technical", "description": "Bugs and outages"},
{"name": "other"}
]
},
{"id": "refund", "type": "yes_no", "question": "Is a refund requested?"}
]
}'There are three question types:
choice: 2 to 26options, each with anameand an optionaldescription. Options are labeledAtoZin order, and the answer includes the winningchoice.score: 2 to 10levels, labeled0to9in order. The answer includesscore, the expected level under the probabilities.yes_no: answered by the labelsyesandno, with optionalyesandnodescriptions.
Every question needs a distinct id, and a request takes up to 64 questions.
Text fields accept a string, object, or array. Objects and arrays are rendered
into the prompt as compact JSON. A malformed request returns 422.
Read the response
{
"object": "decisions",
"model": "Qwen/Qwen3.5-2B",
"prompt_format_version": 1,
"answers": {
"team": {
"type": "choice",
"choice": "billing",
"probabilities": {"billing": 0.97, "technical": 0.01, "other": 0.02},
"label_mass": 0.99
},
"refund": {
"type": "yes_no",
"probabilities": {"yes": 0.95, "no": 0.05},
"label_mass": 0.98
}
},
"usage": {"prompt_tokens": 412, "completion_tokens": 0, "total_tokens": 412}
}probabilities is a softmax over the answer labels' scores, divided by
temperature, so it sums to 1 over the options. It is a ranking you can
threshold. It isn't a calibrated probability that the answer is correct unless
you fit the temperature on your own data.
label_mass is the full-vocabulary probability of all the answer labels at the
answer position. It stays near 1 when the model answers with a label and drops
when it wants to say something else, which makes it a cheap out-of-scope
signal. temperature scales probabilities only, never label_mass.
usage.completion_tokens is always 0. prompt_tokens sums the prompt of every
question. Questions that share the same input reuse its prefill when prefix
caching is on, and usage.prompt_tokens_details.cached_tokens reports how much
was reused.
Request fields
| Field | Description |
|---|---|
input | The text or JSON value the questions are about. Required. |
questions | One or more typed questions. Required. |
temperature | Divides label scores before the softmax. Default 1.0. |
chat_template_kwargs | Extra chat template arguments. Thinking is off by default and may not be turned on. |
prompt_format_version | Pins the prompt wording; a different served version is rejected. Currently 1 (2 for decider). |
return_prompt_token_ids | Returns the exact prompt and label token IDs scored, for reproducing a result offline. |
model | The model to use. |
How a question is scored
Each question becomes its own chat prompt: the input, a blank line, the question with its labeled options, and an instruction such as "Answer with the letter of one option only." The prompt ends where the assistant's answer would start. MAX runs one prefill step and reads the log-probability of each answer label at the last position, normalized over the whole vocabulary.
Because each question is independent, adding, removing, or reordering questions
never changes another question's answer. Before scoring, MAX checks that every
label is exactly one distinct token after the rendered prompt for the model's
tokenizer. If one isn't, the request fails with a 400 instead of returning a
misleading number.
System One compatible route
POST /v1/systemone accepts the System One request shape and answers it with
the same scoring path, so System One clients (such as typesafe-sdk) and the
Decision Index http engine can point at a MAX server.
{
"state": "I was charged twice for order A-104. Please refund one.",
"model": "Qwen/Qwen3.5-0.8B",
"questions": {
"refund": {"type": "noul", "instructions": "Is a refund requested?"},
"team": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {"billing": "Charges and refunds", "technical": null}
},
"urgency": {
"type": "score",
"instructions": "How urgent is this?",
"criteria": ["no rush", "soon", "today", "immediately"]
}
}
}Answers come back keyed by your question IDs: noul (the probability of yes),
choice with confidence and probabilities, and score with confidence,
a legend, and probabilities. Each also carries x_label_mass, the
full-vocabulary probability of the answer labels. usage reports
input_tokens and output_tokens (always 0).
Prompts, label checks, and probabilities are the same as for /v1/decisions
with the model's default temperature. state may be empty when the questions
carry everything. Schema errors return 422, and requests MAX can't answer
(unknown model, unsupported server features, labels that aren't single tokens)
return 400. Fields that only /v1/decisions has (temperature,
prompt_format_version, return_prompt_token_ids) are refused with a 422.
Choice questions take up to 26 options.
Decider models
Models trained for decisions, such as
Mapika/decider-0.8b and
Mapika/decider-2b (Apache-2.0,
Qwen3.5 text-only), read a different prompt than a chat model. They need no
extra flag: MAX finds the decider_config.json that ships with the checkpoint
at startup and serves the decider prompt format:
max serve --model-path Mapika/decider-2bWith this format the prompt is plain text with the state first and the question
last, with no chat template. The model picks an option letter, A to J, at
the last position. MAX reads each model's decider_config.json: answers are
divided by a temperature per question type (choice, noul, score), and a
score question is read as one yes/no row per level whose "fits" probabilities
are normalized. Both routes use this format, and the response reports
prompt_format_version 2. On /v1/decisions, a request's temperature
still overrides the model's own; /v1/systemone always uses the model's.
These models read at most 10 options or levels per question, so wider questions
return a 400. Because a score question becomes one row per level, its
usage.prompt_tokens counts the state once per level.
Limits
- Text input only.
- Thinking models whose chat template always reasons before answering aren't supported.
- Servers that use speculative decoding, LoRA, or variable logits, and models with their own pipeline class (such as block diffusion), don't support decisions.
- Decider models take at most 10 options per question, and chat-layout decider configs aren't supported.