> ## Documentation index
> Fetch the complete documentation index at: https://rolter-ai-rolter.docs7.io/llms.txt
> Ask it with GET https://rolter-ai-rolter.docs7.io/ask?q=<keywords or question>. Short keywords rank best.
> Use these to discover all available pages before exploring further.

# POST /v1/systemone: System One Typed Decisions

> Ask a decision model typed questions (choice, score, yes/no) about an input and get probabilities back. Compatible with the System One API that TypeSafe Jev, vLLM Semantic Router and Ollama serve.

The `/v1/systemone` endpoint takes the System One API's request and returns its response. A client written for TypeSafe's API or for a vLLM Semantic Router engine works against Rolter by changing its base URL and using a Rolter virtual key. Rolter authenticates the request, picks a target from the route, writes the target's upstream model into `model`, and forwards the body unchanged, so question types and fields Rolter has never seen still reach the model.

Set up the decision servers behind it under [System One decisions](/configuration/system-one).

## Request

**`POST /v1/systemone`**

### Headers

<ParamField header="Authorization" type="string" required>
  `Bearer <virtual-key>`: your Rolter virtual key.
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Must be `application/json`.
</ParamField>

### Body

<ParamField body="model" type="string" required>
  The route name, for example `jev`. Rolter replaces it with the target's upstream model id (`jev-latest`, `vllm-sr/d3-edge`) before forwarding. Returns `404` if no route matches.
</ParamField>

<ParamField body="state" type="string | object | array" required>
  The input the questions are about: a ticket, a message, a document, a JSON record.
</ParamField>

<ParamField body="questions" type="object" required>
  Questions keyed by ids you choose. Each answer comes back under the same id. Every question has a `type` and `instructions`; most have `criteria`:

- `choice`: `criteria` maps each option to a description. The answer names the most likely option.
- `score`: `criteria` is a list of 2 to 10 level descriptions, lowest first. The answer is a probability-weighted level.
- `noul`: a yes/no question; `criteria` may describe `true` and `false`. The answer is the probability of yes.

Other types (Vela's `span` and `set`, for example) are forwarded as written; whether they are answered depends on the model.

</ParamField>

Fields beyond these are forwarded as written too. Their names are upstream-specific.

### Images with OpenJev

The Playground's **Decisions** mode accepts text or JSON state, a questions object,
and one JPEG, PNG or WebP image. It sends the image separately as `image_data`
(a base64 data URI), not as chat `messages`, `image_url`, or an `images` array:

```json
{
  "model": "openjev",
  "state": "An image: <<IMG>>",
  "image_data": "data:image/png;base64,<image bytes>",
  "questions": {
    "damaged": { "type": "noul", "instructions": "Is the parcel visibly damaged?" }
  }
}
```

This follows [AlexWortega's OpenJev image-serving contract](https://huggingface.co/AlexWortega/openjev/blob/main/code/serving/IMAGE-SERVING.md),
used by the image and frame-scoring examples in the [OpenJev Space](https://huggingface.co/spaces/AlexWortega/openjev).
The image is shared by all questions; `<<IMG>>` is optional and positions it in the
premise. OpenJev appends it when absent. Limits are 4 MiB decoded and 12 million
pixels. Remote image URLs and server file paths are not accepted. Vision needs a
compatible upstream; `fake-llm` exercises the request shape but does not inspect pixels.
The Space's video example samples frames and scores each separately; it is not a
native video payload for this endpoint.

The Playground renders choice probabilities, score legends, yes probabilities,
confidence and per-question errors, with the full JSON response available below.
Its model picker uses `rolter_capabilities` from the gateway model list, so a
decision-only address is not offered in Chat. Older gateways without that metadata
still offer `fake-llm` in Decisions; update the gateway to discover configured routes.

### Example

```bash
curl https://gateway.example.com/v1/systemone \
  -H "Authorization: Bearer $ROLTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "jev",
    "state": "Our checkout has returned 500 errors since 9am.",
    "questions": {
      "department": {
        "type": "choice",
        "instructions": "Which team owns this ticket?",
        "criteria": {"billing": "payments and refunds", "engineering": "software errors"}
      },
      "is_urgent": {"type": "noul", "instructions": "Does this need action within the hour?"},
      "impact": {
        "type": "score",
        "instructions": "How many customers are affected?",
        "criteria": ["one customer", "some customers", "every customer"]
      }
    }
  }'
```

## Response

The decision server's response, as it wrote it:

```json
{
  "model": "jev-1.13.0",
  "answers": {
    "department": {
      "type": "choice",
      "choice": "engineering",
      "probabilities": { "billing": 0.03, "engineering": 0.97 },
      "confidence": 0.91
    },
    "is_urgent": { "type": "noul", "noul": 0.96 },
    "impact": {
      "type": "score",
      "score": 1.8,
      "legend": { "0": "one customer", "1": "some customers", "2": "every customer" },
      "probabilities": { "0": 0.05, "1": 0.1, "2": 0.85 },
      "confidence": 0.78
    }
  },
  "usage": { "input_tokens": 174, "output_tokens": 0 }
}
```

`usage` drives the request's cost and its budget and rate-limit accounting. The response headers carry `x-rolter-provider` and `x-rolter-target`, as on every proxied endpoint.

## Errors

| Status | Code                   | When                                                                                                |
| ------ | ---------------------- | --------------------------------------------------------------------------------------------------- |
| `400`  | `unsupported_endpoint` | The route's targets cannot answer decisions (none is `system_one`, `openai_compatible` or `ollama`) |
| `400`  | `guardrail_blocked`    | A guardrail rule matched text in `state` or in a question                                           |
| `401`  | `invalid_api_key`      | The virtual key is missing or not valid                                                             |
| `404`  | `model_not_found`      | No route matches `model`                                                                            |
| `429`  |                        | A rate limit or the route's caps refused the request                                                |

An error the decision server returns (TypeSafe's `422` for a malformed question, for example) reaches the caller unchanged.

The reverse also holds: a route whose targets are all `system_one` refuses `/v1/chat/completions`, `/v1/messages`, `/v1/embeddings` and the audio endpoints with `400 unsupported_endpoint`, since a decision model cannot answer them.

## Without a decision server

The built-in `fake-llm` model answers `choice`, `score` and `noul` questions deterministically and reports input-only usage, so the endpoint can be tried and tested with no key and no GPU:

```bash
curl https://gateway.example.com/v1/systemone \
  -H "Authorization: Bearer $ROLTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "fake-llm", "state": "hello", "questions": {"greeting": {"type": "noul", "instructions": "Is this a greeting?"}}}'
```
