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

# Decisions API

> Send your application state and a typed question, get back a typed answer with a probability — no prose to parse and nothing to validate.

# Decisions API

Most model calls hand you a paragraph and leave you to pull a decision out of it: prompt for JSON, parse it, validate it, retry when it drifts. The Decisions API removes that layer. You send the state your application is in plus the questions you need answered, and each answer comes back already typed — a boolean-like likelihood, one of your named options, or a position on your own scale — with a probability attached.

It is built for the decision points inside software rather than for conversation: routing a ticket, gating a tool call, triaging a queue, scoring a submission before a human sees it.

`POST https://api.beatapi.io/v1/systemone`

This is TypeSafe's own endpoint path, and the request and response bodies are theirs too — a client written against the TypeSafe API or any of its SDKs reaches this by changing the base URL and the model name, with nothing else to adapt. `POST /v1/decisions` is kept as an alias.

| Model           | Choose it for                                                                                          |
| --------------- | ------------------------------------------------------------------------------------------------------ |
| `jev-1.13-free` | **Start here: free JEV.** Input and output cost \$0, even on a zero balance. See **Free calls** below. |
| `jev-1.13`      | Optional paid model. \$0.042 per 1M input tokens; output is not billed.                                |

This endpoint is not `/v1/chat/completions`, and `jev-1.13` is not available there. There are no `messages` in the request and no `choices` in the response — a decision model does not generate text.

## Quick start

Start with `jev-1.13-free`: input and output cost **\$0**, even with a zero balance. Use a key in the default **auto** group. Before your first top-up, the account limit is **one successful request per minute**. See [Free calls](#free-calls).

```bash
curl https://api.beatapi.io/v1/systemone \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "jev-1.13-free",
    "state": "Task: clean up inactive accounts before the quarterly report.\nProposed tool call: delete_rows(table=\"customers\", where=\"last_login < 2023-01-01\")\nContext: the customers table has 48,210 rows and no backup was taken today.",
    "questions": {
      "safe_to_run": {
        "type": "noul",
        "instructions": "Is this action safe to run without a human approving it first?",
        "criteria": {
          "true": "Reversible or low-impact, and clearly within the stated task.",
          "false": "Destructive, irreversible, or broader than the task requires."
        }
      }
    }
  }'
```

```json
{
  "id": "task_01J...",
  "model": "jev-1.13-free",
  "answers": {
    "safe_to_run": { "type": "noul", "noul": 0.04 }
  },
  "usage": { "input_tokens": 384, "output_tokens": 22 }
}
```

`0.04` is the model's likelihood that the answer is *yes*. Four percent — so the agent stops and asks a human, which is the point of asking.

## Request

| Field       | Type             | Required | Description                                                                                                                              |
| ----------- | ---------------- | -------: | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `model`     | string           |      Yes | `jev-1.13`, or `jev-1.13-free` to call it at no charge. A request without a model is rejected.                                           |
| `state`     | string \| object |      Yes | Whatever your application knows right now. A plain string or a JSON object both work — send the object if that is what you already have. |
| `questions` | object           |      Yes | One or more named questions. The name is yours; it is the key the answer comes back under.                                               |

Each question takes a `type`, an `instructions`, and — depending on the type — a `criteria`.

| Field          | Type                      | Required | Description                                                                                                                |
| -------------- | ------------------------- | -------: | -------------------------------------------------------------------------------------------------------------------------- |
| `type`         | string                    |      Yes | `noul`, `choice`, or `score`.                                                                                              |
| `instructions` | string \| object \| array |       No | What you are asking. Write it as you would to a colleague. A `choice` or a `score` can be carried by its `criteria` alone. |
| `criteria`     | see below                 |  Depends | What the answer's values mean.                                                                                             |

## The three question types

### `noul` — a calibrated likelihood

Use it for a yes/no that deserves a threshold rather than a coin flip. The answer is a number from `0` to `1`.

```json
"refund_ok": {
  "type": "noul",
  "instructions": "Should this refund be approved automatically?",
  "criteria": {
    "true": "Clear billing error, under $100, first request from this account.",
    "false": "Disputed usage, over $100, or a repeat request."
  }
}
```

```json
"refund_ok": { "type": "noul", "noul": 0.83 }
```

`criteria` is optional here, but explaining both sides sharpens the answer. Pick your own threshold: auto-approve above `0.9`, escalate below `0.6`, queue the middle for review.

`noul` is the type's real name, not a typo for `bool`. A misspelled type is rejected with a 400 that names the three valid ones.

### `choice` — one of your named options

`criteria` is an object mapping each option name to what it means. The answer names the winning option and shows the full distribution.

```json
"route": {
  "type": "choice",
  "instructions": "Which queue should this ticket go to?",
  "criteria": {
    "billing": "Payment, invoices, refunds, or charges.",
    "technical": "The product is broken or erroring.",
    "sales": "Pre-purchase questions about plans or pricing."
  }
}
```

```json
"route": {
  "type": "choice",
  "choice": "billing",
  "probabilities": { "billing": 1, "technical": 0, "sales": 0 },
  "confidence": 1
}
```

### `score` — a position on your scale

`criteria` is an array describing the scale, lowest first. The answer is a continuous value over the **array indices**, starting at `0`.

```json
"urgency": {
  "type": "score",
  "instructions": "How urgent is this ticket?",
  "criteria": [
    "1 - can wait a week",
    "2 - answer within a few days",
    "3 - answer today",
    "4 - answer within an hour",
    "5 - wake someone up"
  ]
}
```

```json
"urgency": {
  "type": "score",
  "score": 1.98,
  "legend": { "0": "1 - can wait a week", "1": "2 - answer within a few days",
              "2": "3 - answer today", "3": "4 - answer within an hour",
              "4": "5 - wake someone up" },
  "probabilities": { "0": 0, "1": 0.17, "2": 0.67, "3": 0.16, "4": 0 },
  "confidence": 0.72
}
```

`1.98` sits between `legend["1"]` and `legend["2"]`, leaning hard on the latter — "answer today". Round it when you need a bucket; keep the decimal when you need to sort a queue.

## Several questions, one call

Ask everything you need about the same state at once. Each answer is independent, and the shared state is counted once rather than once per question. Calls to `jev-1.13-free` remain free.

```bash
curl https://api.beatapi.io/v1/systemone \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "jev-1.13-free",
    "state": {"ticket": "charged twice this month", "plan": "pro", "tenure_days": 412},
    "questions": {
      "refund_ok": {"type": "noul", "instructions": "Approve the refund automatically?"},
      "urgency":   {"type": "score", "instructions": "How urgent?", "criteria": ["low", "medium", "high"]}
    }
  }'
```

## Response

| Field     | Description                                                                                                                                  |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `model`   | The model that answered.                                                                                                                     |
| `answers` | One entry per question, keyed by the name you gave it.                                                                                       |
| `usage`   | `input_tokens` and `output_tokens` for this call.                                                                                            |
| `id`      | BeatAPI request identifier — quote it in support tickets. Added on top of the vendor's three fields; clients that do not model it ignore it. |

The response is synchronous: a successful HTTP response means the decision is made. There is nothing to poll and no artifacts to fetch.

## Limits

* **Context** — 32,000 tokens for `state` and `questions` combined.
* **No sampling parameters.** `temperature`, `top_p`, `seed` and friends are not accepted; the model is deterministic in shape by design.
* **No streaming.** The answer is a value, not a sequence of tokens.
* **A `choice` needs a non-empty `criteria` object and a `score` needs a non-empty `criteria` array** — both are rejected with a 400 that says which. `instructions` is optional, but an empty one is rejected rather than sent.

## Errors

| HTTP status | Code                     | Meaning                                          |
| ----------: | ------------------------ | ------------------------------------------------ |
|   400 / 422 | `bad_request`            | Invalid JSON, model, question type, or criteria. |
|   401 / 403 | `processing_unavailable` | Authentication or access cannot be completed.    |
|         402 | `insufficient_credits`   | The account does not have enough credits.        |
|         429 | `rate_limit_exceeded`    | The request limit was reached.                   |
|         5xx | `processing_unavailable` | The request could not be completed.              |

Failed calls do not consume credits.

## Billing

The paid model `jev-1.13` is billed on **input tokens only** — the tokens your `state` and `questions` occupy — at **\$0.042 per 1M input tokens**. Output is not billed, because the output is a typed value rather than generated prose. `usage.input_tokens` in every response tells you exactly what the call counted, so you can reconcile spend per call.

Asking several questions about one state in a single call is therefore materially cheaper than sending the same state several times.

### Free calls

Send `"model": "jev-1.13-free"` and the call costs nothing. It is the same model with the same request and response — the answer comes back under the name you asked for — and it works on a zero balance, so you can wire JEV into a project before adding any credit.

* It is reached through the default **auto** group, which every new key uses. A key pinned to one specific group cannot see it.
* Free calls are held to your account's normal rate limit. An account that has never topped up gets **one successful request per minute**; topping up raises the limit for every model, this one included.
* The paid model `jev-1.13` remains available at the rate above. To choose it, change only the model name. Account rate limits still apply.