# The HTTP API

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

Source: https://opendecisio.com/docs/api/

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: 7 routes: one asks questions, the rest teach, list and report. The server's routes: POST /v1/systemone answers typed questions about a state; GET /v1/models lists 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/import loads tasks exported with &#x60;GET /v1/tasks?full=1&#x60;; POST /v1/abstention/tasks, GET /v1/abstention/tasks registers and lists per-task abstention thresholds; GET /health what the server is serving; POST /v1/answer the engine's own letters route, for the conformance gates and benchmarks.]

 

## TL;DR

 

With a server running ([Get started](https://opendecisio.com/docs/) 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.

  

| Word | Meaning |
| --- | --- |
| `detail` | what a 422 or 400 says is wrong, in the body of the answer |
| `criteria` | a choice’s options (keys, with a description or `null`), or a score’s levels |
| `confidence` | System One’s own figure: for a choice, the top probability rescaled from uniform |
| task | one recurring question registered from labelled examples (`POST /v1/tasks`) |

 

## 1. Ask what it serves

 

`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.

 

## 2. Ask a question

 

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.

 

Common mistake

Fields the server does not know are ignored, not rejected, so a misspelled field has no effect. Check a field your client relies on against the reference.

 

## Reference

 

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.

  

| Route | What it does |
| --- | --- |
| `POST /v1/systemone` | answers typed questions about a state |
| `GET /v1/models` | lists 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/import` | loads tasks exported with `GET /v1/tasks?full=1` |
| `POST /v1/abstention/tasks`, `GET /v1/abstention/tasks` | registers and lists per-task abstention thresholds |
| `GET /health` | what the server is serving |
| `POST /v1/answer` | the engine's own letters route, for the conformance gates and benchmarks |

 

## `POST /v1/systemone`

 

### Request

  

| Field | Type | Meaning |
| --- | --- | --- |
| `state` | string, object or array | the text the questions are about; an object is rendered as `key: value` lines |
| `questions` | object, at least one entry | the questions, by name; the names are the keys of the response's `answers` |
| `model` | string, optional | TypeSafe's API requires it; decisio accepts a request without it and names the served model in the response |
| `images` | array of data URLs, optional | images 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`](https://opendecisio.com/docs/evaluation-card/) 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:

  

| Field | Type | Meaning |
| --- | --- | --- |
| `type` | `"noul"`, `"choice"` or `"score"` | yes/no, a choice among named options, or a level on an ordered scale |
| `instructions` | string, object or array, optional | the question; an object or array is sent as compact JSON; absent or blank, a fixed default per type |
| `criteria` | depends on `type` | `noul`: 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`](https://opendecisio.com/docs/flags/); 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.

 

### Response

 

```
{"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 field | Meaning |
| --- | --- |
| `noul` | the probability of yes |
| `choice` | the 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 |
| `score` | the probability-weighted mean level |
| `probabilities` | the distribution over the options (choice) or the levels by index (score) |
| `confidence` | System One's confidence: for a choice, the top probability rescaled from uniform; for a score, the concentration at the mode |
| `legend` | each level's index and description |
| `unknown_probability`, `abstained` | with 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`.

 

## `GET /v1/models`

 

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

 

## Tasks

 

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

 

```
{"id": "ticket-routing",
 "examples": [{"request": &#x3C;a /v1/systemone request with exactly one question, text only>, "answer": &#x3C;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`](https://opendecisio.com/docs/tasks/) walks through registration and states what each number was measured on; [`docs/handoffs/tasks.md`](https://github.com/aminry/decisio/blob/v0.11.1/docs/handoffs/tasks.md) is the reference for its rules.

 

## Abstention

 

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

 

```
{"id": "...",
 "option": {"key": "&#x3C;the request's own abstain key>"} or {"append": "&#x3C;text of an option the server adds>"},
 "match": "option_set" or "imajev_extension",
 "examples": [{"request": &#x3C;a /v1/systemone request, images allowed>, "answer": &#x3C;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).

 

## `GET /health`

 

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`](https://github.com/aminry/decisio/blob/v0.11.1/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`](https://github.com/aminry/decisio/blob/v0.11.1/docs/running.md), "When the engine dies").

 

## `POST /v1/answer`

 

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`.

 

## What next

 

- [Your first decision](https://opendecisio.com/docs/first-request/): one request and its answer, line by line.
- [Teaching it a task](https://opendecisio.com/docs/tasks/): register a question you ask again and again.
- [Deploying and operating](https://opendecisio.com/docs/operating/): health, restarts and security.
