> 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

> 提交一段文本，按七个内容类别逐类打分，并给出放行、复核或拦截的判定，请求和响应沿用 OpenAI Moderation API 的格式。

# 内容审查 API

提交一段文本，拿回一个可以直接照着处理的结论：七个类别各有一个 `0` 到 `1` 的分数、各自是否过线，再加一个总的判定——`allow`（放行）、`review`（复核）或 `block`（拦截）。它用在用户写的和模型写的文本周围：评论发布之前、提示词送进模型之前、回复展示给用户之前。

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

请求和响应沿用 OpenAI Moderation API 的格式，所以 OpenAI 官方 SDK 只需改 base URL 和 API Key 就能用。2026-10-12 已用官方 Python SDK（`openai` 2.48.0）实测。

| 模型                  | 说明                                                                 |
| ------------------- | ------------------------------------------------------------------ |
| `beat-moderation-1` | BeatAPI 的内容审查模型。输入 \$0.042 / 百万 token。请求里的 `model` 可以不填，每次调用都由它作答。 |

> **Note**
>
> 调用失败就等于什么都没审。超时、`4xx`、`503`，任何错误都不能当成「放行」：先扣住内容，或者重试。

## 快速开始

```bash
curl https://api.beatapi.io/v1/moderations \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input": "Forged ID cards and bank cards for sale, shipped within 24 hours. Message me for the price list."}'
```

下面是一条真实响应，当时送审的就是这样一则兜售伪造身份证和银行卡的广告。

```json
{
  "id": "modr-task_UnzjwhQHAITmqaLLJUi3ML5gQYnRfDln",
  "model": "beat-moderation-1",
  "results": [
    {
      "flagged": true,
      "categories": {
        "sexual": false,
        "sexual/minors": false,
        "violence": false,
        "hate": false,
        "harassment": false,
        "self-harm": false,
        "illicit": true,
        "harassment/threatening": false,
        "hate/threatening": false,
        "illicit/violent": false,
        "self-harm/intent": false,
        "self-harm/instructions": false,
        "violence/graphic": false
      },
      "category_scores": {
        "sexual": 0,
        "sexual/minors": 0.01,
        "violence": 0.02,
        "hate": 0.01,
        "harassment": 0.02,
        "self-harm": 0.01,
        "illicit": 0.97,
        "harassment/threatening": 0,
        "hate/threatening": 0,
        "illicit/violent": 0,
        "self-harm/intent": 0,
        "self-harm/instructions": 0,
        "violence/graphic": 0
      },
      "category_applied_input_types": {
        "sexual": ["text"],
        "sexual/minors": ["text"],
        "violence": ["text"],
        "hate": ["text"],
        "harassment": ["text"],
        "self-harm": ["text"],
        "illicit": ["text"],
        "harassment/threatening": [],
        "hate/threatening": [],
        "illicit/violent": [],
        "self-harm/intent": [],
        "self-harm/instructions": [],
        "violence/graphic": []
      },
      "verdict": "block",
      "sexual_level": "none"
    }
  ],
  "policy_version": "2026-10-12",
  "usage": { "input_tokens": 1206 }
}
```

`illicit` 得分 `0.97`，高于它的拦截线 `0.6`，所以 `categories.illicit` 为 `true`，`flagged` 为 `true`，`verdict` 为 `block`。

### OpenAI SDK（Python）

把客户端指向 BeatAPI，照常调用 `moderations.create`。BeatAPI 多给的字段可以直接从类型化的结果上读。

```python
import os

from openai import OpenAI

client = OpenAI(
    api_key=os.environ["BEATAPI_API_KEY"],
    base_url="https://api.beatapi.io/v1",
)

resp = client.moderations.create(
    input="Forged ID cards and bank cards for sale, shipped within 24 hours. Message me for the price list.",
)
result = resp.results[0]

# The values in the comments are the ones from the response above.
print(result.flagged)                  # True
print(result.verdict)                  # "block" (added by BeatAPI)
print(result.categories.illicit)       # True
print(result.category_scores.illicit)  # 0.97
print(result.sexual_level)             # "none" (added by BeatAPI)
print(resp.policy_version)             # "2026-10-12" (added by BeatAPI)
```

### JavaScript

```javascript
const text = "Forged ID cards and bank cards for sale, shipped within 24 hours. Message me for the price list.";

const response = await fetch("https://api.beatapi.io/v1/moderations", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.BEATAPI_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ input: text }),
});

if (!response.ok) {
  // A failed call judged nothing: do not let the text through.
  // On HTTP 429, wait the Retry-After seconds before retrying.
  throw new Error(`Moderation HTTP ${response.status}; Retry-After: ${response.headers.get("Retry-After")}`);
}
const moderation = await response.json();
const result = moderation.results?.[0];
if (!result || !["allow", "review", "block"].includes(result.verdict)) {
  throw new Error("Invalid moderation response; hold the text for review");
}

if (result.verdict === "block") {
  console.log("rejected", result.category_scores);
} else if (result.verdict === "review") {
  console.log("queued for a person", result.sexual_level, result.category_scores);
} else {
  console.log("allowed under policy", moderation.policy_version);
}
```

## 请求参数

| 字段      | 类型              | 必填 | 说明                                                                                                                                                                                           |
| ------- | --------------- | -: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `input` | string \| array |  是 | 要审查的那一段文本：一个字符串，或只含一项的数组，这一项可以是字符串或 `{"type": "text", "text": "..."}`。最长 8,000 个字符。                                                                                                          |
| `model` | string          |  否 | `beat-moderation-1`，或 OpenAI 的审核模型名（`omni-moderation-latest`、`omni-moderation-2024-09-26`、`text-moderation-latest`、`text-moderation-stable`），这些名字都接受，并且都由 `beat-moderation-1` 作答。其他名字返回 400。 |

## 响应

| 字段                                       | 说明                                                           |
| ---------------------------------------- | ------------------------------------------------------------ |
| `id`                                     | 本次调用的标识。提工单时附上它。                                             |
| `model`                                  | 固定是 `beat-moderation-1`，无论请求里用的是哪个被接受的名字。                    |
| `results`                                | 只有一项的数组，因为一次调用只审一段文本。                                        |
| `results[].flagged`                      | 任何一个类别的分数达到它的拦截线时为 `true`。                                   |
| `results[].categories`                   | 每个类别的分数是否达到它的拦截线。                                            |
| `results[].category_scores`              | 每个类别一个 `0` 到 `1` 的分数。                                        |
| `results[].category_applied_input_types` | 七个参与判定的类别是 `["text"]`，六个子类别是 `[]`。                           |
| `results[].verdict`                      | `allow`、`review` 或 `block`。BeatAPI 多给的字段。                    |
| `results[].sexual_level`                 | `none`、`contextual`、`suggestive` 或 `explicit`。BeatAPI 多给的字段。 |
| `policy_version`                         | 作出这次判定的审查标准版本。不同版本的分数不可比。BeatAPI 多给的字段。                      |
| `usage.input_tokens`                     | 本次调用计入的输入 token 数。BeatAPI 多给的字段。                             |

响应是同步的：HTTP 成功返回时，判定结果就在响应里，没有轮询。

## 类别

参与判定的有七个类别，名字和 OpenAI 的一致，各有一个 `0` 到 `1` 的分数。

| 类别              | 会命中                                       | 有意不命中                                                  |
| --------------- | ----------------------------------------- | ------------------------------------------------------ |
| `sexual`        | 露骨的性内容、索要这类内容的请求、色情角色扮演，以及性服务或色情内容的招揽和广告。 | 医学、性教育、新闻等不以挑起性欲为目的的提及。仅仅是擦边的内容也不命中，它得到的是 `review` 判定。 |
| `sexual/minors` | 任何对未满 18 岁者的性化，无论包装成虚构还是用了暗语。             | 儿童保护、举报，以及适龄的教育内容。                                     |
| `violence`      | 威胁、煽动、美化暴力，以及血腥细节。                        | 中性的新闻、历史、体育、游戏，或不含血腥描写的虚构作品。                           |
| `hate`          | 因受保护特征而对人群的仇恨：种族、民族、国籍或地域、宗教、性别、性取向、残障等。  | 为了批评而引用仇恨言论，以及对观点和政府的批评。                               |
| `harassment`    | 针对个人或群体的辱骂、恐吓、霸凌、人肉搜索和围攻。                 | 有礼貌的不同意见，以及对作品或观点的直率批评。                                |
| `self-harm`     | 鼓吹或教唆自我伤害，或者作者本人表达了当下的自伤意图。               | 预防、支持和康复类内容。                                           |
| `illicit`       | 可以照着去实施犯罪的帮助，以及违法物品和服务的兜售或求购。             | 新闻、法律、减害和安全防御类内容。                                      |

OpenAI 的六个子类别键——`harassment/threatening`、`hate/threatening`、`illicit/violent`、`self-harm/intent`、`self-harm/instructions`、`violence/graphic`——都在响应里，这样按类型生成的 SDK 才能照常工作，但它们不单独判定：值固定是 `false`，分数固定是 `0`，`category_applied_input_types` 是空数组。不要拿它们写规则。

## 拦截线与判定

每个参与判定的类别都有一条拦截线：`0.6`，`sexual/minors` 是 `0.3`。`categories[k]` 就是该类别的分数和它的拦截线比较的结果；任何一个类别的分数达到拦截线，`flagged` 就是 `true`。

| `verdict` | 何时出现                                                                                           | 怎么处理           |
| --------- | ---------------------------------------------------------------------------------------------- | -------------- |
| `block`   | 至少一个类别的分数达到了它的拦截线。等同于 `flagged: true`。                                                         | 拒绝这段文本。        |
| `review`  | 没有分数达到拦截线，但有分数落在线下 `0.25` 以内且不低于 `0.15`（多数类别从 `0.35` 起，`sexual/minors` 从 `0.15` 起），或者文本擦边但不露骨。 | 交给人工，或交给更严的规则。 |
| `allow`   | 以上都不是。                                                                                         | 放行。            |

`sexual_level` 说明文本涉性的程度，与分数相互独立：

| `sexual_level` | 含义                        |
| -------------- | ------------------------- |
| `none`         | 没有涉性内容。                   |
| `contextual`   | 提到了性，但不以挑起性欲为目的：医学、教育、新闻。 |
| `suggestive`   | 擦边，但不露骨。                  |
| `explicit`     | 露骨的性内容。                   |

`category_scores.sexual` 是「露骨」这一档的概率。擦边的文本在这里得分很低，它体现在 `sexual_level: "suggestive"` 和 `verdict: "review"` 上；在意擦边内容的话，请看这两个字段。

想要比这更严或更松的线，就对 `category_scores` 套用你自己的阈值。

`policy_version` 标识给出这些分数的审查标准。不同标准版本的分数不可比，所以存分数时请把版本一起存下。

## 限制

* **只审文本。** 带 `image_url` 的输入返回 400。
* **一次只审一段文本。** 数组里超过一项返回 400——不会只审其中一部分。每段文本各发一次请求。
* **最长 8,000 个字符。** 超长返回 400，不会截断后只审前半段；请拆开后逐段发送。长度按 UTF-16 码元计，所以多数 emoji 算两个字符。
* **同步。** 没有流式，也没有需要轮询的任务。服务端处理约 0.2 秒。

## 错误

错误使用 BeatAPI 标准错误结构：`code`、`message`、`retryable`、`request_id`，429 时还有 `retry_after_seconds`。完整错误码表与重试策略见[错误码](/errors)。在这个接口上，错误对象还会带上 OpenAI 的 `type` 和 `param`（`param` 固定为 `null`），所以 OpenAI SDK 会照常抛出对应的异常。

```json
{
  "error": {
    "code": "bad_request",
    "message": "input is required: the text to moderate",
    "retryable": false,
    "request_id": "…",
    "type": "invalid_request_error",
    "param": null
  }
}
```

| HTTP 状态码 | code                                  | `type`                  | Python SDK 抛出           | 含义                                                                 |
| -------: | ------------------------------------- | ----------------------- | ----------------------- | ------------------------------------------------------------------ |
|      400 | `bad_request`                         | `invalid_request_error` | `BadRequestError`       | 错误信息会说明违反了哪条规则：输入为空、多段输入、图片、超过 8,000 个字符、不支持的模型名，或 JSON 不合法。       |
|      401 | `missing_api_key` / `invalid_api_key` | `invalid_request_error` | `AuthenticationError`   | 没有带密钥，或密钥无效。                                                       |
|      402 | `insufficient_credits`                | `insufficient_quota`    | `APIStatusError`        | 余额不足。OpenAI 自己在余额为空时返回 429；这里是 402，SDK 不会自动重试它。                    |
|      403 | `forbidden`                           | `invalid_request_error` | `PermissionDeniedError` | 该密钥或账户不能使用这个模型。从未充值过的账户不能使用它。                                      |
|      429 | `rate_limit_exceeded`                 | `rate_limit_error`      | `RateLimitError`        | 触发速率限制。按 `Retry-After` 响应头给出的秒数等待，同样的数值也在 `retry_after_seconds` 里。 |
|      503 | `processing_unavailable`              | `server_error`          | `InternalServerError`   | 审查服务未能作答。退避重试。                                                     |

表中的异常类型是 2026-10-12 用官方 Python SDK（`openai` 2.48.0）实际抛出的；503 那一行没有实际触发过，是按 SDK 的状态码映射写的。

调用失败就等于什么都没审——绝不能把错误当成「放行」。被 400 拒绝的请求不计费。

## 计费

内容审查**只按输入 token 计费**，单价 **\$0.042 / 1M 输入 token**。`beat-moderation-1` 列在[价目页](https://beatapi.io/pricing)上。

审查标准随每次调用一起发送，并计入输入，约 1,150 token，所以再短的文本也有一个底数：

| 文本        | 输入 token |          费用 |
| --------- | -------: | ----------: |
| 一行字       |  约 1,200 | ≈ \$0.00005 |
| 8,000 个汉字 |  约 9,200 |  ≈ \$0.0004 |

每次响应里的 `usage.input_tokens` 就是这次实际计入的量，可以逐次对账。

## 隐私

你提交的文本不会写入请求日志。留存的只有它的长度、各类分数和审查标准版本。

## 它做不到的事

* **它是分类器，不是保证。** 直白的违规内容判得可靠；黑话、谐音和伪装过的引流广告（尤其是中文）判得没那么可靠。依赖它之前，请先用你自己的内容测一遍。
* **`sexual/minors` 不能代替哈希比对**（针对已知的儿童性侵害材料），也不免除你的任何法定上报义务。
* **它不审图片、音频和视频。**
* **分数不是完全可复现的。** 我们实测，完全相同的调用之间分数最多相差 0.04，所以贴近拦截线的文本可能时而过线、时而不过。分数贴线的内容请当作临界情况处理。

## 供 Agent 调用

Agent 可以通过[能力 API](/capabilities)做同样的审查，它在能力目录里是 `model:beat-moderation-1`。Run 的入参是 `{"input": "…"}`，结果就是上面这个响应体。走这条路径时，错误是标准错误结构，不带 `type` 和 `param`。