Skip to content

DocsReference

27 min read · updated

HTTP API

Every route, request field and response field of a decisio server, in TypeSafe's System One wire format.

The server answers questions on POST /v1/systemone; its other routes teach and certify tasks, set abstention thresholds, and say what it serves. Ask what a server serves, check that it is up, then ask one question:

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}}}}'

Run against the CPU stand-in at v0.35.0 on 2026-10-26. It printed:

{"models":[{"name":"decisio-qwen3.6-35b-a3b-letters","description":"letters readout, served default","release_date":"2026-09-27"}]}
{"ok":true,"engine":"hf_letters (CPU stand-in, not for measurement)","transformers":"5.17.0","torch":"2.13.0+cu130","model":"Qwen/Qwen3-0.6B-Base","mode":"separate","block_size":64,"pad_unit":64,"pad_where":"front","adapters":[],"prompt_format":{"tail":"spaced","slot":"prefill","variants":"single"},"base":"qwen3.6-35b-a3b","profile":{"base":"qwen3.6-35b-a3b","checkpoint":"Qwen/Qwen3-0.6B-Base","revision":null,"quantization_on_load":null,"engine_process":null,"temperatures":{"choice":1.37,"noul":1.506,"score":1.506},"prompt":{"tail":"spaced","slot":"prefill","variants":"single","system_prompt":false,"noul_rendering":"letters-keys","describe_options":true,"multi_question":"sequential","register_boundary":null,"pad_policy":"always","pad_unit":64}},"systemone":{"hide_index_keys":true,"desnake_labels":true,"describe_options":true,"noul_rendering":"letters-keys","noul_commit":false,"think":{"default":false,"factor":1.0,"budget":512,"requests":0,"questions":0,"unavailable":0,"tokens":0,"ms":0.0},"over_limit":{"mode":"refuse","prompt_limit":null,"requests":0,"largest_state_tokens":0},"header_limit":{"setting":"auto","requests":0,"plain":0,"refused":0,"lines_dropped":0},"abstention":true,"abstention_tasks":{},"orders":1,"abstain_option":null,"tasks":true,"debug_readout":false,"temperature":1.506,"temperatures":{"choice":1.37},"choice_bands":{},"repository_defaults":null,"layout":{"value":"plain","why":"the default of the base","default":"plain","held_by":[],"notes":[]},"adapter_temperatures":{},"task_store":{"saved_to":null,"readout":{"loaded":0,"applied":0,"other_stack":0},"abstention":{"loaded":0,"applied":0,"other_model":0,"other_layout":0},"at_start":{"saved_to":null,"from_file":{"readout":0,"abstention":0},"usable":0}},"readout_tasks":{}},"head_engine":{"engine":"hf hidden (CPU stand-in, not for measurement)","pad_unit":64,"route":"reserved logit columns (emulated)"},"concurrency":{"mode":"serial"}}
{"model":"decisio-qwen3.6-35b-a3b-letters","answers":{"category":{"type":"choice","choice":"access","confidence":0.25092346099283586,"probabilities":{"billing":0.3625722726465573,"access":0.5006156406618906,"bug":0.13681208669155218}}},"usage":{"input_tokens":43,"output_tokens":1}}
  • A request the server cannot read is answered with 422, and one the model cannot answer with 400, each with a detail naming the problem.
  • The answer’s confidence field is System One’s own figure, the top probability rescaled; thresholds and certificates use the top probability itself, the largest of probabilities.

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.

The machine-readable form of this page is reference/openapi.json (OpenAPI 3.1: every served route, its parameters, request and response models and headers), generated from the server's code and written at release time; the flags have the same kind of table, Server flags. 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
GET /v1/tasks/{id}, POST /v1/tasks/{id}/certifyone task with its status; makes a certified task's certificate again on new labelled examples
GET /metricsPrometheus text: per certified task its state, answers, certified answers and answer-mix distance
POST /v1/abstention/tasks, GET /v1/abstention/tasks, DELETE /v1/abstention/tasks/{id}registers, lists (the export form) and removes per-task abstention thresholds
POST /v1/abstention/tasks/importloads abstention tasks exported with GET /v1/abstention/tasks
GET /healthwhat the server is serving
POST /v1/answerthe engine's own letters route, for the conformance gates and benchmarks

This route follows TypeSafe's System One, so a System One client works unchanged. vLLM 0.32 also serves a route called /v1/systemone (vLLM #59299); it is not this wire. It defines confidence as the chosen option's probability over the whole vocabulary, where System One and decisio use the top probability rescaled from uniform (choice) and the concentration at the mode (score), and it takes up to 26 options per choice where System One and decisio take 255. Check which server a client is pointed at before comparing confidences or sending more than 26 options.

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. The response's model is the name the request sent, or the served model's name when it sent none
imagesarray of data URLs, optionalimages for the image route (a server started with --image-model)
assetsobject, optionalSystem One's images: placeholder names mapped to images, each placeholder found in the state or a question's text is replaced by its image (below)

A prompt, the state with one question, can hold 32,768 tokens on the Qwen base. On a Gemma base it can hold the largest length that fits the card (at least 32,768, at most 98,304 on the 31B and 65,536 on the 12B), which the server chooses at start-up and /health reports as max_model_len with its reason, unless --max-model-len fixed it; with --image-model it is 32,768 (Requirements and limits). Every measured record in Evaluation card is at 32,768.

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. A NaN or infinity in the engine's probabilities (a model whose arithmetic does not survive the device's dtype or matmul precision, or a checkpoint served under a profile that is not its own) is answered with 500 and a detail that says the values are not finite, that the request is not at fault, and which served model and engine produced them. 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.

adapter (optional, decisio's extension). The name of a LoRA adapter the server loaded with --adapter name=path; the question is then answered by the base with that adapter (text route only). Without the field, or with null or an empty string, the base answers.

An unknown name is answered 404 with the name and the adapters that are loaded; a value that is not a string is 422. The response carries "adapter": "<name>" (and the header x-decisio-adapter) when an adapter answered. The registered tasks (calibration, intent head, certificates, abstention thresholds) were fitted on the base's readout, so a question that one of them matches is answered 400 with the task named, not with a correction that does not fit.

The temperature is the adapter's own for the question's type when the adapter has one (--adapter-temperatures, running.md), and otherwise the base's. The task registration and certification routes refuse an adapter field instead of ignoring it. POST /v1/answer takes adapter too and reports it.

think (optional, decisio's extension). true or false: whether the questions of this request that are unsure are read again after the served model has written its reasoning (running.md). Absent or null, the server's setting (--think, or a tuned repository's defaults.think; off unless one of them says otherwise). A value that is not a boolean is 422, and so is true on a server whose engine cannot write reasoning (the vLLM engine can).

When thinking is on for the request the response carries "thinking": {"questions": n, "of": m, "tokens": t, "ms": ms, "factor": f, "budget": b, "per_question": {name: {"thought": true, "tokens": n} or {"thought": false, "why": ...}}}, usage.thinking_tokens and the headers x-decisio-think-ms and x-decisio-think-tokens; usage.output_tokens is then one per question plus the reasoning tokens. A question of a registered task, one with more than 16 options, and every question of a request that asks for two orders, an abstention option or the image route stays at one read and the response says why.

A header over the context. Under the header layout a request whose rows would be over the context because the header lists many long instructions is answered with a header that lists only the first questions that fit (none: the plain layout's row), unless the server was started with --header-limit off (then it is a 422 that says where the tokens are).

The response then carries "header_limited": {"layout": "header" | "plain", "listed": K, "of": N, "header_tokens": T, "of_header_tokens": W, "row_limit": L, "allowed_tokens": A, "reason": "context" | "cap"} and the header x-decisio-layout; both are absent when the whole header was listed. A question of a certified task in such a request carries certified: false with certified_reason: "header_limited" (running.md).

over_limit (optional, decisio's extension). What to do with a state that makes a row longer than the engine's prompt ceiling (the served context less the one generated token): "refuse" or "truncate"; absent or null, the server's setting (--over-limit, or a tuned repository's defaults.over_limit; refuse unless one of them says otherwise). A refused request is answered 422, as the engine says.

With "truncate" the state, never a question, is cut once to its start, so that the longest question of the request still fits, and the request is answered from what was kept. The response then carries "state_truncated": {"kept_tokens": N, "of_tokens": M, "part": "start", "limit": L} (the state's tokens kept and in all, and the ceiling) and the header x-decisio-state-truncated; both are absent when the whole state was read. No answer of a truncated state is certified: a question of a certified task carries certified: false with certified_reason: "state_truncated", and the traffic monitor does not count it.

A request whose questions alone are over the ceiling is answered 422 whatever the setting; a value other than the two is 422. A server whose engine has no ceiling to read (the MLX build) takes --over-limit-tokens to say where to cut. Image requests are not cut.

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 (more is 422); 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 Server flags; the answer keys are always the request's own.

Images: as many per request as the context holds (the image engine takes up to --max-images, 128 unless set), PNG, JPEG or WebP, up to 20 MB each; each costs tokens that count against the context (a 512 by 512 image 256 tokens on the Qwen base). 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 multipart request must carry an X-Decisio-Client header (any value, for example X-Decisio-Client: my-app), and is refused with 415 without one.

A request with images is served by the image engine; the text route is unchanged. Starting the server with --image-model makes vLLM's engine run in a process of its own (--engine-process separate), which turns off the 31B's in-process --multi-question warm default and keeps the context at 32,768 tokens (Server flags); the image route is measured with the Qwen base only.

Images the System One way (assets). assets maps a placeholder name to an image: {"type": "image", "format": "png"|"jpg"|"jpeg", "data_base64": "..."}, or, in a multipart request, {"type": "image", "part": "<the name of an image part>"} (a part wins over data_base64). Every place a placeholder appears, as a string or inside one, in the state or in a question's instructions or criteria descriptions (option keys are not searched; the longest name wins when one contains another), becomes the marker [image N] and the asset's bytes become image N, in reading order; a placeholder used twice is two images. That is the same path as a data:image URI in the state: the image engine gets the same state text, the same questions and the same images, and gives the same probabilities as the other encodings (test I7), with System One's answer fields only (no unknown_probability or abstained).

A placeholder name that matches no asset is plain text. A multipart request puts the request part first and every image in a part of its own, with a unique, non-empty name that exactly one asset uses; it needs the X-Decisio-Client header like any multipart request here. A problem with an asset (a name no text uses, an empty assets, a format that is not png, jpg or jpeg, bad base64, bytes that are not an image, an image part nothing uses or two assets share, assets together with images, more images than the engine's --max-images) is a 400 with {"detail": {"error_type": "api_usage_error", "message": "..."}}, System One's shape for image errors.

System One's TypeSafe-Danger-Zone-Images header is not needed and is ignored. A calibration or head task's examples (POST /v1/tasks) are text only and refuse assets; the examples of POST /v1/abstention/tasks may carry them.

What an image costs and where it is cut down (the image route runs on the Qwen base, --image-model Qwen/Qwen3.6-35B-A3B-FP8; the numbers are that model's processor settings and smart_resize from the pinned transformers, computed on 2026-10-14):

  • An image over 20 MiB, or over 20 megapixels (width times height), or in another format, is refused with 422 (bad image: ...) before it is read; a request with more images than --max-images (128 unless set) is a 400 api_usage_error, as the paragraph above says.
  • The processor then resizes the image, keeping its proportions and rounding both sides to multiples of 32, so that it has between 65,536 pixels (256 by 256) and 16,777,216 (4,096 by 4,096). An image of 200 by 200 is read as 256 by 256; one of 4,000 by 6,000 as 3,328 by 4,992.
  • Each image costs one token for every 32 by 32 pixels of the resized image, and the tokens count in usage.input_tokens and against the 32,768-token context that a server with --image-model serves: 64 tokens at the least, 16,384 at the most (1,024 by 768: 768 tokens; 1,920 by 1,080, read as 1,920 by 1,088: 2,040; 4,000 by 6,000: 16,224).
  • The Ollama listings are not this route. ollama show (2026-10-14) lists the Qwen listing aminroudaki/decisio with the capabilities decision and vision, and the Gemma listing aminroudaki/decisio-gemma (every tag) with decision only. Whether the Qwen listing's image answers match this route's was not measured.

Every POST must have Content-Type: application/json (parameters such as ; charset=utf-8 are fine): any other type, or none, is refused with 415 and accept-post: application/json before the body is read (Operate, "Cross-site requests from a web page"). The one exception is multipart/form-data on this route, with an X-Decisio-Client header (any value): a web page cannot add a custom header to a cross-site request without a preflight, which the server refuses.

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).
{"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 the abstention extension of imajev (an open-source System One client and test harness, github.com/mohit67890/imajev; --abstain-option): the probability of the offered "can't tell" option and whether the server abstained

model echoes the name the request sent, so a client left on the SDK's default model name gets that name back unchanged; a request without one gets the served model's name (--served-name, or the base's). 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-adapter: the adapter that answered, when one did.
  • x-decisio-think-ms, x-decisio-think-tokens: the milliseconds spent on reasoning and the second reads, and the reasoning tokens generated, when thinking was on for the request.
  • x-decisio-state-truncated: kept=N; of=M; part=start, when the state was cut (over_limit, above); absent otherwise.
  • x-decisio-layout: header; listed=K; of=N; reason=context (or plain; ...), when the header listed only some of the request's questions (--header-limit, above); absent otherwise.
  • x-decisio-prompt: layout=header; tokens=1523; cached=1440: the layout of the user turn, the mean prompt tokens the engine read per question, and the mean of them the prefix cache served. Under the header layout a state cached for another set of questions shows as a low cached. Absent when the engine does not report prompt sizes.
  • 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. With --concurrency batched, a request that shared an engine call (x-decisio-batch) reports the whole call's questions and engine times.
  • x-decisio-batch: requests=N; rows=M when the server batches concurrent requests (--concurrency batched) and this request's questions were scored in one engine call with other requests' (N requests, M rows in all); absent when it was scored alone. On POST /v1/answer the same facts are timing.batch.
  • 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.

The conformance check compares the request and response fields with System One's published OpenAPI document (version 0.2.0) and fails on any difference that is not in this list:

  • model is optional. System One requires it and refuses a request without it with 422; here a request without it is answered and the response names the served model.
  • A score may have more than 10 levels. System One refuses more than 10 with 422 (too_long); decisio accepts more (up to the engine's 255 labels) and does not cap them.
  • box questions are not supported. A request with "type": "box" is refused with 422.
  • Images (assets) are read, with these differences. Images per request are limited by the context, as on System One (about 110 images of 320 by 320 pixels in System One's 32k tokens, more with its long-context header), up to the engine's --max-images (128 by default); no size window (System One takes sides of 280 to 1,568 pixels and refuses others with "Invalid image dimensions"; here the image is resized, up to 20 megapixels and 20 MB); WebP bytes are accepted besides PNG and JPEG (System One refuses them); a placeholder is replaced longest name first (System One replaces naively, so PHOTO_1 inside PHOTO_10 breaks it); an image costs the Qwen image engine's tokens, not System One's; a multipart request needs X-Decisio-Client; and the image route runs on a server started with --image-model, on the Qwen base.
  • Unknown fields are accepted. System One refuses a request with an unknown top-level field (400 Invalid request.), so its images-style extensions cannot be sent to it; decisio ignores unknown fields (imajev's images and orders are such extensions).
  • usage.output_tokens is 1 per question, not the tokens a generating model produced.
  • Probabilities and confidences are returned at full precision (System One rounds them to two decimals), the response has no x-typesafe-request-id header (see the x-decisio-* headers), and model is the name the request sent (the served model's name when it sent none), not the name of the model that answered.

Decisio sets no limit of its own on the size of a request body, on the number of questions in one request, or on the number of concurrent connections; uvicorn's defaults apply (no body limit, a backlog of 2,048). What bounds a request:

  • The context. The state with one question, as rendered, can hold up to the server's max_model_len tokens (at least 32,768; /health reports it and why, and running.md says how it is chosen); over it the answer is 422 (above).
  • Options per question. A choice has up to 255 options (the wire format's cap); more is refused with 422 and a too_long detail, as System One does (Dictionary should have at most 255 items after validation, not 256). A score has no cap on its levels here (System One's is 10).
  • Questions per request. No cap: a request with no questions is 422, and any other number is answered. Each question costs its own time: with --multi-question sequential (the default of the Qwen and 12B bases, and of the 31B when it has a second engine) about 20 ms per further question on a cached state (Evaluation card section 4); with --multi-question warm (the default of the 31B, the default base, when its engine runs in the server's process) the questions are scored in one batch, 5.4 ms per question at 100 questions and 2.7 ms at 1,000 on an 8,000-token state on the Qwen base (Evaluation card section 3, runs/2026-09-30_plugin-verification/bench_patched_on.json). The response and the batch grow with the question count, so for thousands of questions send several requests about the same state: the second and later read the state from the prefix cache.
  • Concurrency. The server runs one engine and, by default, its calls are serialized by a lock, so concurrent requests queue behind it and are answered in turn; there is no queue limit. With --concurrency batched the requests waiting for the engine are scored together in one call instead (running.md, "Concurrency"; x-decisio-batch says when a request shared a call). It was measured with 32 and 64 concurrent clients sending first and follow-up questions about new 1,000-token states (Get started, "A later question about the same state"); x-decisio-stages reports each request's wait (wait=), and x-decisio-wait-behind: registration says when it waited behind a background boundary registration.

The server sets no CORS headers and has no authentication on any route. A page on another origin therefore cannot read its answers, and cannot make it do work either:

  • A request with a JSON body makes the browser send a preflight OPTIONS first; the server answers 405 (allow: POST), so the browser never sends the POST. Checked on the CPU stand-in on 2026-10-14 with curl -X OPTIONS -H 'Origin: http://example.com' -H 'Access-Control-Request-Method: POST': 405 Method Not Allowed, no access-control-* header.
  • A request a browser sends without a preflight (a "simple" one: text/plain, application/x-www-form-urlencoded or multipart/form-data) is refused with 415 and accept-post: application/json before its body is read, on every route (Operate, "Cross-site requests from a web page"), because a simple request needs no preflight, so a page could otherwise make the server do the work, register or replace tasks, even though it cannot read the answer. The one exception is multipart/form-data on /v1/systemone (imajev's image clients), which is accepted when the request carries an X-Decisio-Client header (any value): a page cannot add a custom header without a preflight, which the server refuses.

Do not expose the port to browsers, and do not rely on CORS to protect it. To call it from a web page, call it from your own backend, or put a reverse proxy in front that authenticates the caller and adds the CORS headers your page needs (Access-Control-Allow-Origin, Access-Control-Allow-Headers: content-type, and an answer to OPTIONS). A client of this server sends Content-Type: application/json; every client the integration checks cover does.

  • GET /health. The HTTP status is the contract: 200 while the server is serving, 503 with {"ok": false, "engine": "dead", ...} from the moment the engine is declared dead. Alert on anything but 200. The other fields describe the running server (listed below) and can gain fields between releases; read them for what is being served, not as an alert signal.
  • The process exit code. 70 means the engine died and the server ended itself; a restart policy acts on the exit (Get started, "When the engine dies"; Operate).
  • Per request, in the response headers: x-decisio-server-ms (the server's time), x-decisio-stages (prepare, warm, engine, wait) and x-decisio-wait-behind when the request waited behind a registration. A wait that grows is requests queueing behind the engine.
  • Metrics only for certified tasks, and no structured log. GET /metrics serves Prometheus text for certified tasks (state, answers, certified answers and the answer-mix distance: Teach a task, "Certified answers"); the server exposes no Prometheus metrics for the engine, and its output is the plain text of its start-up lines and uvicorn's warnings. Take latency and queueing from the headers above, in your client or your proxy's log.

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.
  • With --task-store a task's listing and registration response carry store_version; a write names the version it replaces (by default the one the replica serves; x-decisio-replaces: N names another) and answers 409 {message, kind, task, store_version, your_version} when the store holds another (?replace=1 replaces it), 409 on a replica started --task-store-readonly, and 503 when the store cannot be reached; the change is not made in any of them (Teach a task, "Replicas").
  • A write that fails under --tasks-dir (disk full, read-only) answers 500 on registration, certification, import and removal, and the change is not made (Teach a task, "Saved to disk").
  • GET /v1/tasks and GET /v1/abstention/tasks carry a store object: where tasks are saved, how many are loaded and apply, what the start loaded from where.
  • 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).

Certified answers. POST /v1/tasks also takes an optional certify block, {"alpha": 0.05, "delta": 0.1, "min_examples": 59, "holdout": 0.5, "suspend_on_alert": true, "examples": [...]} (every key optional; min_examples defaults to what alpha and delta need, 59 at the defaults, and can only raise that; examples are labelled like the task's own and are never fitted on, and without them the server holds out holdout of the task's examples, fits on the rest and says so in the stored certificate.split). It refuses with 422, and registers nothing, when fewer held-out examples than the procedure can use at all are given: 59 at the defaults, whatever the model does.

The response carries the certificate (threshold, alpha, delta, n, certified_share, the split, the serving-stack fingerprint, served_check (the confidences re-read through the serving path on a sample of the held-out examples: n, max_shift, p99_shift, crossed_threshold, top_answer_changed), a summary in words) and a status (ok, traffic_changed or suspended, with a text). With no threshold that holds, certificate.threshold is null, nothing is certified, and the task is otherwise served normally. Every later answer of a certified task carries certified (true or false), certificate (its id) and, when false, certified_reason: below_threshold, nothing_certified, serving_stack_changed, traffic_changed (the traffic monitor's confidence alert is on and the certificate was made with suspend_on_alert, the default: tasks.md), state_truncated (the request's state was cut, over_limit above), header_limited (the header listed only some of the request's questions, above), think_mode_changed (the server thinks and the certificate was made one-pass, or the other way round) or concurrency_changed (the server batches concurrent requests and the certificate was made one request at a time, or the other way round: running.md, "Concurrency").

A change of the serving stack suspends the certificate (every answer certified false, status suspended: serving stack changed, the differing parts listed in status.changed); POST /v1/tasks/{id}/certify with {"examples": [...]} (and optionally alpha, delta, min_examples) makes it again under the current stack, on examples that share no input with the fit. It keeps the old alpha, delta and suspend_on_alert unless the call names them, and a min_examples only if the original call asked for more than its alpha and delta need; otherwise the minimum is derived from the new alpha and delta (29 at 10% and 10%), so a looser target needs fewer examples than the 59 of the defaults. Teach a task, "Certified answers", says what the promise is and where it stops.

Teach a task walks through registration and states what each number was measured on.

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", "option_keys" 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.

option_set is for one question with one option list: the task applies to a question whose options (keys and descriptions, in any order) are those of the examples. option_keys is for a family of questions that share their option keys and not their texts, such as a multiple-choice exam whose items each have their own four answers behind the keys A to D and an idk key: the task applies to a choice question whose set of keys is exactly the examples' (any descriptions, any instructions), and the threshold is fitted on the pooled probabilities of the abstain key across the items. It is deliberately the whole key set and not the abstain key alone: the probability of an abstain option moves with the number of candidates, so a threshold fitted on four candidates plus idk is not applied to ten plus idk (register one task per key set), and a question that merely has an option of the same name is not caught. When several tasks match a question, the exact option_set one wins over an option_keys one.

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; Get started); 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 (Get started, "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.