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

# 决策 API

> 提交应用当前状态（可附带图片）和类型化问题，直接拿到带概率的类型化答案，包括图片中的 box 位置——无需解读生成的散文；执行前仍需校验响应。

# 决策 API

大多数模型调用返回的是一段话，然后由你把决策从里面抠出来：提示它输出 JSON、解析、校验、格式跑偏了再重试。决策 API 去掉了这一层。你提交应用当前的状态和需要回答的问题，每个答案回来时**已经是类型化的**——一个类似布尔的似然值、你自己命名的某个选项，或者你自己那把刻度上的一个位置——并且附带概率。

它服务的是软件内部的判断点，而不是对话：工单分流、工具调用的放行闸、队列分级、人工介入之前的打分。

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

这是 TypeSafe 官方的端点路径，请求体和响应体也都照它来——照着 TypeSafe 官方 API 或它任何一个 SDK 写的客户端，只需改 base URL 和模型名，其余不用适配。`POST /v1/decisions` 作为别名保留。

| 模型              | 适用场景                                                               |
| --------------- | ------------------------------------------------------------------ |
| `jev-1.14-free` | **从这里开始：免费的 JEV。** 输入输出均为 \$0，余额为 0 也能调。支持图片和 `box` 问题。见下方「免费调用」。  |
| `jev-1.14`      | 可选的付费模型。输入 $0.0252 / 百万 token（牌价 $0.042，6 折），输出不计费。支持图片和 `box` 问题。 |
| `jev-1.13-free` | 上一代免费版，仍可使用，条件与 `jev-1.14-free` 相同。支持图片，不支持 `box`。                 |
| `jev-1.13`      | 上一代付费版，仍可使用，价格与 `jev-1.14` 相同。支持图片，不支持 `box`。                      |

`jev-1.14` 即 TypeSafe 的 `jev-1.14.0`：相比 1.13 是全面的小幅提升，多语言输入提升最大。TypeSafe 没有公布具体数字，这里也不给。价格与 1.13 相同。

> **Note**
>
> 这个接口不是 `/v1/chat/completions`，JEV 模型在那里也不可用。请求里没有 `messages`，响应里也没有 `choices`——决策模型不生成文本。

## 快速开始

先用 `jev-1.14-free`：输入输出均为 **\$0**，余额为 0 也能调。请使用默认 **auto** 分组的 Key。免费调用有单独的限额：首次充值前**每分钟 1 次成功请求**，任意充值后**每分钟最多 10 次**。见[免费调用](#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.14-free",
    "state": "任务：季度报表前清理不活跃账号。\n待执行调用：delete_rows(table=\"customers\", where=\"last_login < 2023-01-01\")\n背景：customers 表有 48210 行，今天没有做过备份。",
    "questions": {
      "safe_to_run": {
        "type": "noul",
        "instructions": "这个操作可以不经人工批准直接执行吗？",
        "criteria": {
          "true": "可逆或影响很小，且明确在任务范围之内。",
          "false": "破坏性、不可逆，或超出了任务需要的范围。"
        }
      }
    }
  }'
```

下面的响应仅作说明，不是实测结果。请解析 JSON，检查 HTTP 状态码和答案类型，校验数值之后再套用你自己的阈值。

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

`0.04` 是模型认为答案为「是」的似然——4%，所以 agent 停下来找人确认。这正是问它的意义。

### 一次类型化决策。没有 JSON 修复循环。

指向 BeatAPI，提交 state + questions，读取 answers。别名 /v1/decisions 同样可用。这不是 chat/completions——JEV 模型在那里不可用。

```javascript
const response = await fetch("https://api.beatapi.io/v1/systemone", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.BEATAPI_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "jev-1.14-free",
    state: {
      task: "clean up inactive accounts",
      tool: 'delete_rows(table="customers", where="last_login < 2023-01-01")',
      rows: 48210,
      backup_today: false,
    },
    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.",
        },
      },
    },
  }),
});

if (!response.ok) {
  // On HTTP 429, wait the Retry-After seconds before retrying.
  throw new Error(`JEV HTTP ${response.status}; Retry-After: ${response.headers.get("Retry-After")}`);
}
const decision = await response.json();
const answer = decision.answers?.safe_to_run;
if (answer?.type !== "noul" || !Number.isFinite(answer.noul)
    || answer.noul < 0 || answer.noul > 1) {
  throw new Error("Invalid JEV answer; route to human review");
}
// Illustrative threshold: evaluate it on your own cases before use.
const needsReview = answer.noul < 0.9;
console.log({ probability: answer.noul, needsReview });
```

## 请求参数

| 字段          | 类型                        | 必填 | 说明                                                                                                            |
| ----------- | ------------------------- | -: | ------------------------------------------------------------------------------------------------------------- |
| `model`     | string                    |  是 | `jev-1.14`；想免费调用就填 `jev-1.14-free`。`jev-1.13` 和 `jev-1.13-free` 仍然可用。不带模型的请求会被拒绝。                             |
| `state`     | string \| object \| array |  是 | 应用此刻掌握的情况。支持字符串、JSON 对象或文本数组。文本和结构化文本放在这里；要带图片，请在 `assets` 里定义，再在文本里引用它（见[图片](#images)）。音频、视频和其他二进制内容须先转换为文本。 |
| `questions` | object                    |  是 | 一个或多个具名问题。名字由你定，答案就挂在这个键下面返回。                                                                                 |
| `assets`    | object                    |  否 | 文本里引用的图片，键是你自定的引用字符串，例如 `"<door>"`。有 `box` 问题时必填。见[图片](#images)。                                              |

每个问题需要 `type`，以及视类型而定的 `criteria`。

| 字段             | 类型                        |              必填 | 说明                                                                                                             |
| -------------- | ------------------------- | --------------: | -------------------------------------------------------------------------------------------------------------- |
| `type`         | string                    |               是 | `noul`、`choice`、`score` 或 `box`。                                                                               |
| `instructions` | string \| object \| array | `noul`、`box` 必填 | 你要问什么。像对同事说话那样写就行。`noul` 没有别的字段说明这个是非题问的是什么，`box` 也没有别的字段说明要定位什么，所以两者必填；`choice` 和 `score` 光靠 `criteria` 也能成立。 |
| `criteria`     | 见下                        |             视类型 | 答案里各个取值的含义。`box` 不接受。                                                                                          |

## 四种问题类型

### `noul`——校准过的似然

用在那些值得设阈值、而不是非黑即白的是非题上。答案是 `0` 到 `1` 之间的数。

```json
"refund_ok": {
  "type": "noul",
  "instructions": "这笔退款可以自动批准吗？",
  "criteria": {
    "true": "明确的计费错误、金额低于 100 美元、该账号首次申请。",
    "false": "用量存在争议、金额超过 100 美元，或重复申请。"
  }
}
```

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

这里 `criteria` 可以不填，但把两侧都说清楚会让答案更锐利。阈值由你定：高于 `0.9` 自动批，低于 `0.6` 升级处理，中间的进人工队列。

> **Note**
>
> `noul` 就是这个类型的名字，不是 `bool` 写错了。类型拼错会得到一个 400，并在错误信息里列出合法取值。

### `choice`——在你命名的选项里选一个

`criteria` 是一个对象，把每个选项名映射到它的含义。答案给出胜出的选项，并附上完整的概率分布。

```json
"route": {
  "type": "choice",
  "instructions": "这张工单应该进哪个队列？",
  "criteria": {
    "billing": "付款、发票、退款或扣费。",
    "technical": "产品坏了或在报错。",
    "sales": "购买前关于套餐或价格的咨询。"
  }
}
```

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

### `score`——你那把刻度上的位置

`criteria` 是一个数组，从低到高描述刻度。答案是一个**按数组下标**的连续值，从 `0` 起算。

```json
"urgency": {
  "type": "score",
  "instructions": "这张工单有多急？",
  "criteria": [
    "1 - 可以放一周",
    "2 - 几天内回复",
    "3 - 今天回复",
    "4 - 一小时内回复",
    "5 - 半夜也要叫人起来"
  ]
}
```

```json
"urgency": {
  "type": "score",
  "score": 1.98,
  "legend": { "0": "1 - 可以放一周", "1": "2 - 几天内回复",
              "2": "3 - 今天回复", "3": "4 - 一小时内回复",
              "4": "5 - 半夜也要叫人起来" },
  "probabilities": { "0": 0, "1": 0.17, "2": 0.67, "3": 0.16, "4": 0 },
  "confidence": 0.72
}
```

`1.98` 落在 `legend["1"]` 和 `legend["2"]` 之间，明显偏向后者——「今天回复」。需要分桶就四舍五入，需要给队列排序就保留小数。

### `box`——图片里某样东西的位置

用来在图片里定位一样东西。只有 `jev-1.14` 和 `jev-1.14-free` 支持 `box`。它需要 `instructions` 说明要找什么，不接受 `criteria`，并且请求里必须至少有一张图片（见[图片](#images)），没有图片会得到 400。在 `jev-1.13` 或 `jev-1.13-free` 上使用 `box` 会被拒绝。

```json
"where": {
  "type": "box",
  "instructions": "The red rectangle in <picture>."
}
```

```json
"where": {
  "type": "box",
  "box": { "top": 0.12, "bottom": 0.43, "left": 0.56, "right": 0.89 },
  "confidence": 0.93
}
```

四个值都是 `0` 到 `1` 的比例，`top`、`bottom` 相对图片高度，`left`、`right` 相对图片宽度，从上边和左边量起。乘以图片的像素尺寸就得到像素位置。`confidence` 可能为 `null`，与阈值比较之前先判断。

## 图片

1.13 和 1.14 都能读图片。把每张图片在顶层的 `assets` 对象里定义一次，然后在 `state` 的文本、问题的 `instructions` 或 `criteria` 的值里写上它的引用字符串，图片就放在引用出现的位置。

| 字段            | 类型     | 必填 | 说明                            |
| ------------- | ------ | -: | ----------------------------- |
| `type`        | string |  否 | `image`，可省略。                  |
| `format`      | string |  是 | `png`、`jpg` 或 `jpeg`。         |
| `data_base64` | string |  是 | 图片字节的标准 base64，不带 `data:` 前缀。 |

```bash
curl https://api.beatapi.io/v1/systemone \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "jev-1.14-free",
    "state": "Check the uploaded image.",
    "assets": {
      "<picture>": { "type": "image", "format": "png", "data_base64": "iVBORw0KGgo..." }
    },
    "questions": {
      "red":   { "type": "noul", "instructions": "Is there a red rectangle in <picture>?" },
      "where": { "type": "box",  "instructions": "The red rectangle in <picture>." }
    }
  }'
```

这次调用用的是一张 280×280、白底带一个红色矩形的图片。下面是实测响应；`id` 是 BeatAPI 在厂商字段之外加的请求标识。

```json
{
  "id": "task_01J...",
  "model": "jev-1.14-free",
  "answers": {
    "red":   { "type": "noul", "noul": 0.92 },
    "where": { "type": "box",
               "box": { "top": 0.12, "bottom": 0.43, "left": 0.56, "right": 0.89 },
               "confidence": 0.93 }
  },
  "usage": { "input_tokens": 501, "output_tokens": 36 }
}
```

引用的规则：

* 引用按纯文本匹配，可以是任何字符串，比如 `"<door>"` 或 `"[img-1]"`；对象的键不会被替换，所以请把引用写在值里。
* 定义的每个引用至少要用一次，否则请求会被 400 拒绝。
* 只收 JSON。不接受 multipart；asset 里带 `part` 字段会被 400 拒绝，并提示改用 `data_base64`。
* TypeSafe 官方 API 要求的 `TypeSafe-Danger-Zone-Images` 请求头不用你发，BeatAPI 会自动加上；发了也无害。

图片限制：

* 只收 PNG 或 JPEG 静态图，动图会被拒绝。
* 单张解码后最大 16 MiB，整个请求体最大 32 MiB。
* 每边最大 1568 px，最小 280 px。小于下限的图片会被拒绝，提示 `Invalid image dimensions.`。
* 图片不会被自动缩放。超过上限的图片请先自行缩小。
* 一张图片出错，整个请求失败。

## 一次问多个问题

针对同一份状态，把要问的一次问完。各个答案互相独立，而状态**只计费一次**，不是每个问题算一遍。`jev-1.14-free` 的调用仍然免费。

```bash
curl https://api.beatapi.io/v1/systemone \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "jev-1.14",
    "state": {"ticket": "本月被扣了两次款", "plan": "pro", "tenure_days": 412},
    "questions": {
      "refund_ok": {"type": "noul", "instructions": "可以自动批准退款吗？"},
      "urgency":   {"type": "score", "instructions": "有多急？", "criteria": ["低", "中", "高"]}
    }
  }'
```

## 响应

| 字段        | 说明                                                |
| --------- | ------------------------------------------------- |
| `model`   | 作答的模型。                                            |
| `answers` | 每个问题一项，键就是你起的名字。每一项带有 `type`，以及下表中的字段。            |
| `usage`   | 本次调用的 `input_tokens` 与 `output_tokens`。           |
| `id`      | BeatAPI 请求标识，提工单时附上它。这是在厂商那三个字段之外多给的，客户端没建模就会忽略它。 |

每个答案的字段，按问题类型：

| `type`   | 字段                                                                                            |
| -------- | --------------------------------------------------------------------------------------------- |
| `noul`   | `noul`：答案为「是」的似然，`0` 到 `1`。                                                                   |
| `choice` | `choice`：胜出的选项；`probabilities`：每个选项一个值；`confidence`。                                          |
| `score`  | `score`：按 `criteria` 下标的连续值；`legend`；`probabilities`；`confidence`。                            |
| `box`    | `box`：`top`、`bottom`、`left`、`right`，都是相对图片高度或宽度的 `0`–`1` 比例，从上边和左边量起；`confidence`，可能为 `null`。 |

响应是同步的：HTTP 成功返回就意味着决策已经做完。没有轮询，也没有产物要取。

## 限制

* **上下文**——`state` 与所有问题合计最多 64K token；`state` 加最长单个问题最多 32K token。参见[模型限制](https://docs.typesafe.ai/models)。
* **暂不支持长上下文。** TypeSafe 提供一个可把上下文开到 262K token 的请求头，BeatAPI 暂不支持，所以单次调用仍在上面的 64K 限制之内。图片 token 也占用这部分上下文。
* **图片**只收 JSON（不接受 multipart），并受[图片](#images)一节的限制。
* **不接受采样参数。** `temperature`、`top_p`、`seed` 之类一律不收；这个模型在形状上本就是确定的。
* **不支持流式。** 答案是一个值，不是一串 token。
* **`choice` 需要非空的 `criteria` 对象，`score` 需要非空的 `criteria` 数组**，缺了会返回 400 并指明是哪一个。`instructions` 可以不填，但填了空的会被直接拒绝，而不是照原样发出。

## 错误

错误使用 BeatAPI 标准错误结构，每个错误都带 `retryable`。完整错误码表与重试策略见[错误码](/errors)。

| HTTP 状态码 | code                                        | 含义                                                                                                          |
| -------: | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|      400 | `bad_request`                               | JSON、模型、问题类型、criteria 或图片不合法，错误信息会点名字段。例如：`assets` 里有没被引用或格式不对的条目、asset 里带 `part`、`box` 问题没有图片、图片格式或尺寸不被接受。 |
|      401 | `missing_api_key` / `invalid_api_key`       | 没有带密钥，或密钥错误、已过期、已停用、额度已用尽。                                                                                  |
|      402 | `insufficient_credits`                      | 余额不足：余额为零或低于本次调用的价格。                                                                                        |
|      403 | `forbidden`                                 | 该密钥或账户不能使用这个模型，例如免费额度不能使用的模型。                                                                               |
|      404 | `not_found`                                 | 模型不存在。                                                                                                      |
|      429 | `rate_limit_exceeded`                       | 触发速率限制，包括免费模型的限额。按 `Retry-After` 等待。                                                                        |
|      500 | `internal_error`                            | 内部异常，可以重试。                                                                                                  |
|      503 | `processing_unavailable` 或其他 `processing_*` | 请求未能完成，退避重试。                                                                                                |

失败的调用不消耗额度。

## 计费

付费模型 `jev-1.14` 和 `jev-1.13` **只按输入 token 计费**——也就是 `state`、`questions` 和图片占用的 token——单价 \*\*$0.0252 / 1M 输入 token**（牌价 $0.042，6 折）。输出不计费，因为输出是一个类型化的值，而不是生成的文字。每次响应里的 `usage.input_tokens` 就是这次实际计入的量，可以逐次对账。

图片同样按输入 token 以相同单价计费，没有单独的图片费。作为参考：280×280 的图片大约 320 token，512×512 大约 840 token，1024×768 大约 2,200 token。实际数值以 `usage.input_tokens` 为准。

所以把同一份状态的多个问题合并到一次调用里，比把同一份状态发好几遍要便宜得多。

### 免费调用

把 `"model"` 填成 `jev-1.14-free`，这次调用就不收钱。`jev-1.13-free` 的免费条件相同。它与对应的付费模型是同一个模型，请求和响应完全一样，返回里的模型名就是你请求的那个。余额为 0 也能调，所以不充值就能先把 JEV 接进项目里。

* 通过默认的 **auto** 分组调用，新建的 Key 默认都是这个分组。固定在某个特定分组的 Key 看不到它。
* 免费调用有单独的固定账户限额：首次充值前**每分钟 1 次成功请求**；任意金额充值后**每分钟最多 10 次**，与充值多少无关。该限额与账户常规的速率限制分开计算，免费调用不会占用付费额度。
* 超出限额的免费调用返回 `429 rate_limit_exceeded`，并带 `Retry-After` 响应头；请等待相应时间后再重试。
* 需要更高的限流、或者想逐次看到花费时，把模型名换成 `jev-1.14` 即可，其他不用改。付费模型按账户常规速率限制计算，额度随累计充值提升。

### 提高请求额度

任意金额充值后，`jev-1.14-free` 仍免费，每分钟最多成功调用 **10 次**。付费模型 `jev-1.14` 使用账户共享请求额度，当前上限与下一档充值门槛请在控制台查看。这不是 JEV 专属额度。

[充值余额](https://beatapi.io/dashboard/billing)后，如需付费调用，请主动将 `model` 从 `jev-1.14-free` 改为 `jev-1.14`，端点、`state` 和 `questions` 不用改。输入 \*\*$0.0252 / 百万 tokens**（牌价 $0.042，6 折），输出免费。充值不会自动切换模型；付费调用仍需有足够余额。遇到 429 时遵循 `Retry-After`。