Skip to content

DocsUse it from

9 min read · updated

The HTTP API

Every route, request field and response field, in the System One wire format.

The server answers questions on POST /v1/systemone and has a few more routes to register a task, set an abstention threshold and report what it is serving. A malformed request is answered with status 422 and a detail naming what is wrong; a request the engine cannot answer, with 400.

Figure · The routes

7 routes: one asks questions, the rest teach, list and report.

Each route links to its section of the reference below.

Source: the table of docs/api.md at the pinned release.

With a server running (Get started starts one), ask what it serves, check that it is up, then ask one choice question with bare option names:

curl -s http://127.0.0.1:8000/v1/models
curl -s http://127.0.0.1:8000/health
curl -s http://127.0.0.1:8000/v1/systemone \
-H 'Content-Type: application/json' \
-d '{"state": "My card keeps getting declined when I renew.", "questions": {"category": {"type": "choice", "instructions": "Which team should handle this ticket?", "criteria": {"billing": null, "access": null, "bug": null}}}}'
# the model's name, as System One lists it
# {"ok": true, ...}: the engine, the base, its checkpoint and revision, the temperatures and the prompt
# {"model": "...", "answers": {"category": {"type": "choice", "choice": "...", "confidence": ..., "probabilities": {...}}}, ...}

Run against the CPU stand-in at v0.11.1 on 2026-10-14.

WordMeaning
detailwhat a 422 or 400 says is wrong, in the body of the answer
criteriaa choice’s options (keys, with a description or null), or a score’s levels
confidenceSystem One’s own figure: for a choice, the top probability rescaled from uniform
taskone recurring question registered from labelled examples (POST /v1/tasks)

GET /v1/models lists the served model’s name, the base’s own unless --served-name sets another. GET /health says more: the engine, the base, its checkpoint and revision, the temperatures and the prompt.

The request fields and the answer’s are in the reference below. The model field is optional: a decisio server serves one base, chosen when it starts, and names it in the answer.

The server speaks TypeSafe's System One wire format on POST /v1/systemone, so existing clients work unchanged, and adds routes for task registration, abstention and health. It listens on http://127.0.0.1:8000 unless --host and --port say otherwise. A malformed request is answered with status 422 and a detail naming what is wrong; a request the engine cannot answer, with 400.

RouteWhat it does
POST /v1/systemoneanswers typed questions about a state
GET /v1/modelslists the served model's name
POST /v1/tasks, GET /v1/tasks, DELETE /v1/tasks/{id}registers, lists and removes per-task calibration and intent heads
POST /v1/tasks/importloads tasks exported with GET /v1/tasks?full=1
POST /v1/abstention/tasks, GET /v1/abstention/tasksregisters and lists per-task abstention thresholds
GET /healthwhat the server is serving
POST /v1/answerthe engine's own letters route, for the conformance gates and benchmarks
FieldTypeMeaning
statestring, object or arraythe text the questions are about; an object is rendered as key: value lines
questionsobject, at least one entrythe questions, by name; the names are the keys of the response's answers
modelstring, optionalTypeSafe's API requires it; decisio accepts a request without it and names the served model in the response
imagesarray of data URLs, optionalimages for the image route (a server started with --image-model)

A prompt, the state with one question, can hold up to 32,768 tokens (EVAL_CARD.md section 2). A prompt over that is answered with 422 and a detail that says the rendered prompt is longer than the context, with the lengths vLLM reports (shorten the state or the question and send it again); the server stays up (before 0.11.0 it was a bare 500). The same holds for POST /v1/answer and for the examples of POST /v1/tasks and POST /v1/abstention/tasks. Fields the server does not know are ignored, so extensions (such as orders, for two-order averaging) and fields added to the wire format later do not break a request.

Each question:

FieldTypeMeaning
type"noul", "choice" or "score"yes/no, a choice among named options, or a level on an ordered scale
instructionsstring, object or array, optionalthe question; an object or array is sent as compact JSON; absent or blank, a fixed default per type
criteriadepends on typenoul: optional, {"true": ..., "false": ...} describing each side; choice: required, an object of option keys to descriptions (or null), up to 255 options; score: required, a list of level descriptions, lowest first

How a question is shown to the model (the layout, the yes/no rendering, which keys are shown) is set by the server's flags, listed in docs/cli.md; the answer keys are always the request's own.

Images: up to 2 per request, PNG, JPEG or WebP, up to 20 MB each. They can be sent as data URLs in images, as data:image/... URIs anywhere inside the state, or as a multipart/form-data request whose request field holds the JSON body and whose image, images or image[] fields hold the files. A request with images is served by the image engine; the text route is unchanged.

Request headers:

  • x-decisio-route: text or image chooses the route explicitly.
  • x-decisio-debug: readout or hidden adds the raw readout to the response, on a server started with --debug-readout only (verification, never production).

The earlier x-rlcd-route and x-rlcd-debug headers are still honoured.

{"model": "decisio-gemma-4-31b-it-letters",
"answers": {
"urgent": {"type": "noul", "noul": ...},
"category": {"type": "choice", "choice": "access", "confidence": ..., "probabilities": {"billing": ..., "access": ..., ...}},
"impact": {"type": "score", "score": ..., "confidence": ..., "legend": {"0": "...", ...}, "probabilities": {"0": ..., ...}}},
"usage": {"input_tokens": ..., "output_tokens": ...}}

A shape to fill in, not a command to run.

Answer fieldMeaning
noulthe probability of yes
choicethe most probable option's key; among exactly tied options, the key that sorts first in Unicode code-point order, so the answer never depends on the order the keys arrive in
scorethe probability-weighted mean level
probabilitiesthe distribution over the options (choice) or the levels by index (score)
confidenceSystem One's confidence: for a choice, the top probability rescaled from uniform; for a score, the concentration at the mode
legendeach level's index and description
unknown_probability, abstainedwith imajev's abstention extension (--abstain-option): the probability of the offered "can't tell" option and whether the server abstained

usage.input_tokens counts the state and each question's own text; usage.output_tokens is one per question.

Response headers:

  • x-decisio-server-ms: the server's time for the request.
  • x-decisio-route: the route that served it (text or image).
  • x-decisio-tasks: the registered tasks applied, when any were.
  • x-decisio-stages: where the engine's time went, for example prepare=...;warm=...;questions=...;readout=...;engine=...;wait=.... engine is the engine call itself; wait is the time before it, from the request reaching the engine until it took the engine's lock, so engine + wait is the server time within the request's own overhead (a few ms). The wait is 0 on an idle server.
  • x-decisio-wait-behind: registration when a boundary registration (--register-boundary after) was running when the request arrived, so the wait was behind it; absent otherwise. On POST /v1/answer the same facts are timing.waited_ms and timing.waited_behind_registration.

The served model in System One's format; the name is the base's served name unless --served-name sets another.

POST /v1/tasks registers one recurring question from labelled examples:

{"id": "ticket-routing",
"examples": [{"request": <a /v1/systemone request with exactly one question, text only>, "answer": <the option key>}, ...]}

A shape to fill in, not a command to run.

Every example must ask the same question with the same options. The server fits a per-task calibration and, for a question with 10 or more options, an intent head on the model's hidden state, keeps each only when cross-validation on the examples shows a gain, and answers with the task's record: what was fitted, what is applied, and why anything was not. Every later question with the same option list is answered with the task applied.

  • GET /v1/tasks lists the registered tasks, whether tasks are applied (--tasks, on by default), whether the server can serve intent heads (head_engine), and the server's fingerprint (the model and rendering tasks are fitted under).
  • GET /v1/tasks?full=1 includes the heads' arrays: the form --tasks-file loads at start and POST /v1/tasks/import takes.
  • POST /v1/tasks/import with {"tasks": [...]} loads tasks without fitting them again; tasks fitted under another model or rendering are refused and must be registered again from their examples.
  • DELETE /v1/tasks/{id} removes a task (404 when there is none of that id).

docs/tasks.md walks through registration and states what each number was measured on; docs/handoffs/tasks.md is the reference for its rules.

POST /v1/abstention/tasks registers a threshold on an abstain option's probability, fitted from labelled examples that include unanswerable ones:

{"id": "...",
"option": {"key": "<the request's own abstain key>"} or {"append": "<text of an option the server adds>"},
"match": "option_set" or "imajev_extension",
"examples": [{"request": <a /v1/systemone request, images allowed>, "answer": <the option key, or null when unanswerable>}, ...]}

A shape to fill in, not a command to run.

The server then abstains when that option's probability exceeds the threshold, and otherwise answers the best of the other options; the probabilities themselves are reported unchanged. The threshold is kept only when the examples justify it. GET /v1/abstention/tasks lists the tasks and whether thresholds are applied (--abstention, on by default).

What the server is serving, in one object a run record can quote:

  • ok, and the engine's facts: the vLLM version, the GPU, the mode, the padding (pad_unit, pad_where), the quantization and the cache data types.
  • block_size and match_unit, the KV cache's block and the unit a prefix match is counted in; cache_hit_unit, the step prefix-cache hits come in, and hash_unit, the step prefixes are hashed at (on the Gemma 4 12B base, whose cache keeps groups of 16- and 64-token blocks, hits come in 64-token steps; 32 on the 31B; 1,056 on the Qwen base); registers_state_boundary, whether a single question registers its state's boundary in the cache for the next question, and register_boundary, when (after the response or before the question; docs/running.md); with after, state_boundary counts the registrations pending, deferred, registered, dropped and failed.
  • base and profile: the base, the checkpoint and its revision, where vLLM's engine runs (engine_process), the temperature for each question type, and the prompt (layout, answer position, label forms, system turn, yes/no and option rendering, multi-question scoring, padding).
  • prompt_format: the prompt's layout settings.
  • systemone: the System One route's settings (rendering rules, noul_commit, abstention and its tasks, orders, tasks and which are applied, the temperatures).
  • head_engine, the intent head's hidden-state reader (the serving engine by default, a second engine with --head-engine), and image_engine when one is running.

Once the engine has died, /health answers 503 with {"ok": false, "engine": "dead", "reason": "...", "exit_code": 70}, every other route answers 503 with the reason, and the server exits with code 70 (docs/running.md, "When the engine dies").

The engine's own route, below System One: a state and a list of questions in the engine's form (kind, instructions, options), answered with each question's options and probabilities and the engine's timing. The conformance gates and the benchmark scripts use it; clients should use /v1/systemone.