> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.milliseconds.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.milliseconds.ai/_mcp/server.

# Why a decisions model

An LLM prompt can answer "is this ticket urgent?". It also writes prose and invents fields you never asked for. `decision-machine-1` does one job: it turns text into a typed decision with a number attached.

This page states where that trade pays, and where it costs you.

## The four wins

### 1. Latency you can put in a request path

A call makes one pass over the text. Nothing is generated, so the time does not grow with the answer.

Measured end to end from a laptop on 2026-09-18, against production, median of 7 runs, with short inputs of 51 to 199 characters:

| Call                    | Observed    |
| ----------------------- | ----------- |
| `yes-no`, one statement | 0.42 s      |
| `classify`, 3 labels    | 0.43 s      |
| `rate`, 4 levels        | 0.42 s      |
| `answer`, 1 question    | 0.40 s      |
| `entities`, 3 types     | 0.42 s      |
| `verify`                | 0.40 s      |
| `extract`, 9 properties | 0.41 s      |
| Every 400 / 404         | under 0.1 s |

The numbers include TLS, internet transit and the API hop. Every call lands near 0.4 s. The model itself takes 36 to 54 ms of that, reported in the `x-inference-ms` header.

Batching costs almost nothing. Several statements or questions ride in one call: 2 statements took 0.49 s, and 3 questions took 0.40 s. [Batching](/capabilities/batching) covers the rules.

### 2. No parsing, no repair step

The response is typed JSON from a schema, not a string you have to trust. You write no fallback parser for a code fence or a stray sentence.

**`curl`**

```bash title="curl"
curl -s https://api.milliseconds.ai/v1/decision-machine-1/classify \
  -H "content-type: application/json" \
  -H "authorization: Bearer test_sk-..." \
  -d '{
    "text": "I was charged twice for my subscription this month and support has not replied.",
    "labels": {
      "billing": "payments, invoices, charges, refunds",
      "shipping": "delivery, tracking, returns",
      "account": "login, password, profile settings"
    }
  }'
```

**`Python`**

```python title="Python"
import requests

r = requests.post(
    "https://api.milliseconds.ai/v1/decision-machine-1/classify",
    headers={"authorization": "Bearer test_sk-..."},
    json={
        "text": "I was charged twice for my subscription this month and support has not replied.",
        "labels": {
            "billing": "payments, invoices, charges, refunds",
            "shipping": "delivery, tracking, returns",
            "account": "login, password, profile settings",
        },
    },
)
print(r.json()["label"])
```

**`TypeScript`**

```typescript title="TypeScript"
const r = await fetch(
  "https://api.milliseconds.ai/v1/decision-machine-1/classify",
  {
    method: "POST",
    headers: { "content-type": "application/json", authorization: "Bearer test_sk-..." },
    body: JSON.stringify({
      text: "I was charged twice for my subscription this month and support has not replied.",
      labels: {
        billing: "payments, invoices, charges, refunds",
        shipping: "delivery, tracking, returns",
        account: "login, password, profile settings",
      },
    }),
  },
);
const { label } = await r.json();
```

Real response from that call:

```json
{"label":"billing","probability":0.995,"confidence":0.972,"scores":{"billing":0.995,"shipping":0,"account":0.005}}
```

Every request needs an API key. See [Authentication](/authentication).

### 3. Probabilities you can threshold

A prompt gives you a verdict. This gives you a verdict and its distribution.

* `probability` is the winner's normalized share on `classify` and `rate`.
* `confidence` is `1 − normalized entropy`. A single peak scores 1. A flat distribution scores 0.
* `scores` carries the full distribution, so you can see the runner-up.

That lets you set one bar per action instead of trusting every answer equally. Read [Decisions and probabilities](/concepts/decisions-and-probabilities), then [Thresholds and confidence routing](/concepts/thresholds).

A `rate` call over the scale `["Calm","Annoyed","Frustrated","Threatening to leave"]` reports its own disagreement:

```json
{"score":1.585,"level":2,"label":"Frustrated","confidence":0.371,"scores":[0,0.471,0.472,0.057]}
```

The winning `level` beat level 1 by 0.001. The `score` of 1.585 sits between the two levels. The `confidence` of 0.371 reports that ambiguity. A prompt would have named one level and stopped.

### 4. Cost that tracks your input, not the output

Every capability response carries `x-input-chars`, the number of input characters, and `x-input-tokens`, the input tokens billed for this call. You pay \$0.04 per million input tokens and \$0 per output token. See [Pricing](/reference/pricing). The label count does not change the cost, and the answer length does not change it either.

Texts over 2,000 characters are split into chunks, so cost stays proportional to what you send: the text plus the labels, questions, or schema. [Long text and chunking](/concepts/long-text) explains the split.

## What it cannot do

> **Warning**
>
> `decision-machine-1` decides. It does not write, reason, or hold a conversation. Do not reach for it when you need any of the three.

| It cannot              | What that means                                                                                                                    |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Generate text          | `answer` returns a **span of your input** with `start` and `end` offsets, never new prose. The answer is `null` when nothing fits. |
| Reason in steps        | There is no chain of thought and no planning. One pass, one decision.                                                              |
| Chat                   | Plain chat on the [OpenAI-compatible surface](/openai/overview) returns `400 unsupported_request` on purpose.                      |
| Follow a system prompt | The OpenAI facade drops `system`, `developer`, `assistant` and `tool` roles. It reads only user turns.                             |
| Stream tokens          | `stream: true` returns one content chunk, one finish chunk, then `[DONE]`, after the work finishes.                                |
| Summarize or rewrite   | There is no such capability. Send that to a large model.                                                                           |

Two more honest limits. An array-of-objects property in an extract schema is accepted and returned empty for now. And `required` in an extract schema is read, never enforced.

## The practical answer: use both

Run the cheap typed decision first. Escalate only the uncertain cases to a large model. The [Cascade to an LLM](/patterns/cascade) pattern does the cost math with the latencies above.

Use `decision-machine-1` when you need one of these:

* A routing decision in front of tools, agents or queues.
* A guardrail on messages going into or out of an LLM app.
* A field pulled out of a document, with a check that the document really says it.
* A score you can threshold, log and tune.

Use a large model when you need prose, a plan or a conversation. Use it also when the judgment needs world knowledge outside the text you sent.

## Next

#### [Quickstart](/start/quickstart)

One curl call that returns a decision in under a minute.

#### [Choosing a capability](/concepts/choosing-a-capability)

A decision table that sends you to the right endpoint.

#### [Cascade to an LLM](/patterns/cascade)

Run the cheap decision first, escalate the uncertain cases.