> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Moderation API

> 在您的 AI 产品生成内容之前筛查提示词和图像，并针对 17 个内容类别返回允许、标记或拒绝的判定及分数。

<CardGroup cols={2}>
  <Card title="Screen a Prompt" icon="shield-check" href="/api-reference/moderation/screen">
    发送文本、图像或两者，并获取判定结果。
  </Card>

  <Card title="Get Moderation Usage" icon="chart-column" href="/api-reference/moderation/get-usage">
    查看您的计费筛查次数和下一笔费用。
  </Card>
</CardGroup>

## 概览

Moderation API 会在您的 AI 产品根据用户输入生成内容之前，对用户输入进行筛查。您可以发送提示词文本、图像或两者，Dodo Payments 会返回 `allow`、`flag` 或 `deny` 判定，以及每个内容类别的分数。

您可以将其用于任何接受用户输入的图像、视频或文本生成模型之前。默认情况下，Moderation API 对每个商户启用，并使用您现有的 Dodo Payments API 密钥，因此无需注册。Dodo Payments 可以针对单个商户将其关闭，此时调用会返回 `403` 和 `MODERATION_DISABLED`。

## 我们构建 Moderation API 的原因

AI 生成产品会根据用户输入的任何内容创建新内容。您不可能手动审核每条提示词，而一次有害的输出就可能让您的业务面临风险。

作为您的 Merchant of Record，Dodo Payments 对通过平台销售的内容承担法律和声誉责任。[Merchant Acceptance Policy](/miscellaneous/merchant-acceptance) 会审核 AI 内容生成工具，不允许冒充、深度伪造或露骨内容，包括 AI 生成的内容。生成有害内容、导致大量拒付或被支付合作伙伴标记的账户，可能会被置于审核状态或暂停。

我们构建 Moderation API，帮助您在模型创建此类内容之前将其拦截：

* **在生成之前进行筛查。** 被拦截的提示词永远不会到达您的模型，因此不会产生有害输出，也不会为其消耗计算资源。
* **覆盖生成场景中重要的类别。** 筛查会对 17 个类别评分，包括真人相似度、未经同意的私密图像、暗示未成年人的语言，以及真人与色情内容的组合（这会构成色情深度伪造）。
* **无需接入其他供应商。** API 使用您的 Dodo Payments API 密钥运行，费用从您的余额中扣除。无需单独签订合同、开具发票或创建账户。
* **保护用户内容隐私。** Dodo Payments 不会存储或记录您筛查的文本和图像。

<Note>
  Moderation API 是供您自行执行政策的工具。它不能替代 Merchant Acceptance Policy，您仍需对产品生成的内容负责。
</Note>

## 工作原理

用户提交提示词后、模型运行前，从您的后端调用 Moderation API：

```mermaid theme={null}
flowchart LR
  A[User submits a prompt] --> B[Your backend calls POST /moderation/screen]
  B -->|allow| C[Generate]
  B -->|flag| D[Apply your own policy]
  B -->|deny| E[Block the request]
  B -->|error, no verdict| E
```

每次调用算作一次**筛查**。在同一次调用中发送的文本和图像算作一次筛查。

### 判定结果

`decision` 字段包含判定结果：

| 判定      | 含义                       | 操作                        |
| ------- | ------------------------ | ------------------------- |
| `allow` | 内容通过筛查。                  | 生成内容。                     |
| `flag`  | 内容超过了需要人工判断的类别阈值。这不是软拒绝。 | 应用您自己的政策。您可以拦截、发送审核或生成内容。 |
| `deny`  | 不得生成该内容。                 | 拦截请求并向用户显示错误。             |

<Warning>
  未收到判定结果时不要生成内容。`503` 表示 Dodo Payments 无法生成判定结果，超时或网络错误也会导致您没有判定结果。将所有这些情况视为拦截，并要求用户重试。
</Warning>

## 筛查提示词

要筛查提示词，请向 `/moderation/screen` 发送 `POST` 请求，并至少提供 `text` 或 `image` 之一。请求接受三个字段：

| 字段           | 类型     | 描述                                                        |
| ------------ | ------ | --------------------------------------------------------- |
| `text`       | string | 要筛查的文本，最多 8,000 个字符。                                      |
| `image`      | string | 要筛查的图像，以 base64 编码，可带或不带 `data:image/...;base64,` 前缀。     |
| `request_id` | string | 可选。您为此次筛查指定的标识符，例如生成 ID，最多 128 个字符，且不得包含控制字符。响应会原样返回该标识符。 |

TypeScript 和 Python SDK 将该端点公开为 `client.moderation.screen()`。此示例会在 `deny`、`flag` 以及任何错误情况下拦截生成：

<Note>
  示例调用实时模式，因为只有实时模式会运行审核模型。测试模式返回[模拟判定结果](#testing-your-integration)，不会筛查内容。实时模式的筛查会计费。
</Note>

<CodeGroup>
  ```typescript Node.js expandable theme={null}
  import DodoPayments from 'dodopayments';

  const client = new DodoPayments({
    bearerToken: process.env.DODO_PAYMENTS_API_KEY,
    environment: 'live_mode',
  });

  async function generateImage(prompt: string, generationId: string) {
    let verdict;
    try {
      verdict = await client.moderation.screen({
        text: prompt,
        request_id: generationId,
      });
    } catch (err) {
      // No verdict: do not generate.
      throw new Error('Moderation is unavailable. Try again in a moment.');
    }

    if (verdict.decision !== 'allow') {
      throw new Error('This prompt cannot be generated. Revise it and try again.');
    }

    return myModel.generate(prompt);
  }
  ```

  ```python Python expandable theme={null}
  import os
  from dodopayments import DodoPayments, APIError

  client = DodoPayments(
      bearer_token=os.environ["DODO_PAYMENTS_API_KEY"],
      environment="live_mode",
  )

  def generate_image(prompt: str, generation_id: str):
      try:
          verdict = client.moderation.screen(text=prompt, request_id=generation_id)
      except APIError:
          # No verdict: do not generate.
          raise RuntimeError("Moderation is unavailable. Try again in a moment.")

      if verdict.decision != "allow":
          raise ValueError("This prompt cannot be generated. Revise it and try again.")

      return my_model.generate(prompt)
  ```

  ```bash cURL theme={null}
  curl -X POST https://live.dodopayments.com/moderation/screen \
    -H "Authorization: Bearer $DODO_PAYMENTS_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "text": "a watercolor painting of a lighthouse at sunset",
      "request_id": "gen_7Hc2k9"
    }'
  ```
</CodeGroup>

示例将 `flag` 与 `deny` 同样处理。如果您的产品允许某些被标记的内容，请检查 `triggered`，按类别作出决定。

<Tip>
  筛查用户编写的文本，而不是您在其外层包装的提示词模板。您自己的模板在每次调用中都相同，不会为筛查增加任何信息。
</Tip>

### 筛查图像

发送图像以筛查用户上传的参考图像，或在展示生成的图像之前进行筛查。图像必须满足以下要求：

* 格式为 JPEG、PNG、WebP、GIF 或 BMP。
* base64 字符串最多 6,991,530 个字符，解码后的图像最多 5 MiB。
* 图像必须是单个静态帧。动画 GIF 和 WebP 图像会被拒绝。
* 最长边至少为 32 像素。

不符合上述任一检查的图像会返回 `400` 和 `MODERATION_INVALID_IMAGE`；图像过大时会返回 `413` 和 `MODERATION_INPUT_TOO_LARGE`。

要筛查图像，请读取文件，将其编码为 base64，然后在 `image` 中发送。要同时筛查图像及其提示词，请在同一次调用中发送 `text` 和 `image`。这算作一次筛查。此示例使用上一个示例中的 `client`：

<CodeGroup>
  ```typescript Node.js expandable theme={null}
  import { readFile } from 'node:fs/promises';

  async function screenImage(path: string, prompt: string, generationId: string) {
    const image = (await readFile(path)).toString('base64');

    const verdict = await client.moderation.screen({
      image, // or `data:image/png;base64,${image}`
      text: prompt, // optional: screen the prompt with the image
      request_id: generationId,
    });

    return verdict.decision === 'allow';
  }
  ```

  ```python Python expandable theme={null}
  import base64

  def screen_image(path: str, prompt: str, generation_id: str) -> bool:
      with open(path, "rb") as f:
          image = base64.b64encode(f.read()).decode("ascii")

      verdict = client.moderation.screen(
          image=image,  # or f"data:image/png;base64,{image}"
          text=prompt,  # optional: screen the prompt with the image
          request_id=generation_id,
      )
      return verdict.decision == "allow"
  ```

  ```bash cURL expandable theme={null}
  # Builds the JSON body with jq, so a large image does not hit the shell argument limit.
  base64 < reference.png | tr -d '\n' \
    | jq -Rs '{image: ., text: "turn this photo into a watercolor painting", request_id: "gen_7Hc2k9"}' \
    | curl -X POST https://live.dodopayments.com/moderation/screen \
        -H "Authorization: Bearer $DODO_PAYMENTS_API_KEY" \
        -H "Content-Type: application/json" \
        --data @-
  ```
</CodeGroup>

以处理文本筛查的方式处理图像筛查错误：如果调用抛出错误，请不要生成内容。

## 读取响应

响应会返回判定结果及其依据：

| 字段                   | 描述                                                           |
| -------------------- | ------------------------------------------------------------ |
| `decision`           | 判定结果：`allow`、`flag` 或 `deny`。                                |
| `triggered`          | 分数超过相应类别阈值的类别。对于一般检查返回的 `flag`，可能为空。                         |
| `compound_triggered` | 当真人相似度和色情内容共同超过组合阈值（即色情深度伪造模式）时为 `true`。                     |
| `categories`         | 内容属于每个类别的概率，范围为 0 到 1。                                       |
| `provenance`         | 每个分数的测量方式：`targeted` 表示通过针对该单一类别的检查，`broad` 表示通过覆盖所有类别的一般检查。 |
| `notes`              | 对判定结果的可读原因。措辞可能发生变化，因此不要解析它。                                 |
| `normalized_applied` | 当文本也经过移除混淆处理的筛查时为 `true`，例如移除不可见字符或形似字符。                     |
| `passes`             | 模型针对此次筛查回答的是非题的数量。                                           |
| `latency_ms`         | 筛查耗时，单位为毫秒。                                                  |
| `request_id`         | 您发送的 `request_id`，或 `null`。                                  |

请基于 `decision` 和 `triggered` 编写逻辑。每个类别都有自己的阈值，因此在代码中使用单一分数阈值不会与判定结果匹配。

### 类别

每个响应都会根据 17 个类别对内容评分：

| 类别                                | 涵盖内容                  |
| --------------------------------- | --------------------- |
| `violent_crimes`                  | 暴力犯罪。                 |
| `sex_related_crimes`              | 性相关犯罪。                |
| `child_sexual_exploitation`       | 儿童性剥削。                |
| `suicide_and_self_harm`           | 自杀和自残。                |
| `indiscriminate_weapons`          | 化学、生物、放射性、核武器或爆炸性武器。  |
| `intellectual_property`           | 侵犯版权或商标权。             |
| `defamation`                      | 可能损害真人声誉的虚假描绘。        |
| `non_violent_crimes`              | 非暴力犯罪。                |
| `hate`                            | 因受保护特征而贬低他人。          |
| `privacy`                         | 关于某人的敏感私密信息。          |
| `specialized_advice`              | 未经资质说明的金融、医疗、法律或选举建议。 |
| `sexual_content`                  | 色情或露骨性内容。             |
| `non_consensual_intimate_imagery` | 脱衣、裸化或性化真人。           |
| `minor_coded_language`            | 暗示主体为未成年人的年龄相关语言。     |
| `real_person_likeness`            | 真实、可识别且有姓名的人的相似形象。    |
| `living_artist_style`             | 模仿特定在世艺术家的标志性风格。      |
| `prompt_injection`                | 试图覆盖或操纵系统指令。          |

## 处理错误

错误会返回标准的 Dodo Payments 错误正文，其中包含 `code` 和 `message`。任何错误都不是判定结果，因此都不允许生成内容：

| 状态    | `code`                       | 原因                                     | 操作                            |
| ----- | ---------------------------- | -------------------------------------- | ----------------------------- |
| `400` | `INVALID_REQUEST_PARAMETERS` | 请求格式错误，或既没有 `text` 也没有 `image`。        | 修正请求。                         |
| `400` | `MODERATION_INVALID_IMAGE`   | 无法解码图像、图像为动画或尺寸过小。                     | 发送受支持的静态图像。                   |
| `403` | `MODERATION_DISABLED`        | Dodo Payments 已为您的业务关闭 Moderation API。 | 联系支持团队了解原因。                   |
| `413` | `MODERATION_INPUT_TOO_LARGE` | `text` 超过 8,000 个字符，或 `image` 超出大小限制。  | 缩短文本或压缩图像。                    |
| `429` | `MODERATION_OVERLOADED`      | Moderation 已达到容量上限。这是吞吐量限制，不是判定结果。     | 等待 `Retry-After` 标头中的秒数，然后重试。 |
| `503` | `MODERATION_UNAVAILABLE`     | 没有可用的判定结果。                             | 不要生成内容。稍后重试。                  |

SDK 默认会对 `429` 或 `503` 重试两次，并在尝试之间等待 `Retry-After`。重试次数耗尽后，SDK 会抛出错误，您的代码必须拦截请求。

## 测试集成

测试模式会返回模拟判定结果，且不会调用审核模型，因此您可以免费测试路由逻辑。使用测试模式 API 密钥向 `https://test.dodopayments.com` 发送请求。

默认模拟判定结果为 `allow`。要获取其他结果，请将以下字符串之一放在 `text` 的任意位置：

| `text` 中的字符串           | 响应                                                |
| ---------------------- | ------------------------------------------------- |
| `dodo_mock_flag`       | `200`，且 `decision` 设置为 `flag`                     |
| `dodo_mock_deny`       | `200`，且 `decision` 设置为 `deny`                     |
| `dodo_mock_overloaded` | `429` `MODERATION_OVERLOADED`，包含 `Retry-After: 1` |
| `dodo_mock_not_ready`  | `503` `MODERATION_UNAVAILABLE`                    |

模拟判定结果会附带说明，表明该结果为模拟结果，且其所有类别分数均为 `0`。测试模式执行与实时模式相同的请求验证。对于图像，它会检查 base64 编码和格式，但不会检查帧数或尺寸。

上线前，请确认您的集成能够处理每种情况：

<Steps>
  <Step title="Deny Blocks Generation">
    发送 `dodo_mock_deny`，并确认不会调用您的模型。
  </Step>

  <Step title="Flag Follows Your Policy">
    发送 `dodo_mock_flag`，并确认您的产品执行方式符合您的政策。
  </Step>

  <Step title="Overload Retries">
    发送 `dodo_mock_overloaded`，并确认您的代码会等待 `Retry-After`，且在没有判定结果时不会生成内容。
  </Step>

  <Step title="An Outage Blocks Generation">
    发送 `dodo_mock_not_ready`，并确认不会调用您的模型。
  </Step>

  <Step title="Every Generation Path Screens">
    检查所有到达您模型的代码路径是否都会先调用 Moderation API。
  </Step>
</Steps>

## 定价和计费

Moderation API 的费用为**每 1,000 次可计费筛查 0.30 美元**。没有免费层级，也没有最低消费。

可计费筛查是返回判定结果的实时模式筛查。以下筛查免费且不计入费用：

* 测试模式中的筛查。
* 返回错误的筛查，包括 `429` 和 `503`。

Dodo Payments 按每 1,000 次筛查的完整区块计费。每个完整区块会在一小时内计费，未填满区块的筛查会一直保持未计费状态，直到区块填满。费用从您的 USD 余额中扣除，并以事件类型 `moderation_fees` 显示在您的[余额账本](/api-reference/balance-ledger/list-ledger-entries)中。Payouts 会将其显示在 **Moderation Fees** 下。

### 跟踪用量

要查看用量，请调用 `GET /moderation/usage`。响应会返回：

| 字段                      | 描述                                       |
| ----------------------- | ---------------------------------------- |
| `unbilled_screens`      | Dodo Payments 尚未向您收取费用的可计费筛查次数。          |
| `screens_to_next_block` | 填满下一个 1,000 次区块所需的剩余可计费筛查次数。             |
| `daily`                 | 最近 30 天内您按 UTC 日期统计的可计费筛查次数。没有筛查的日期会被省略。 |

<CodeGroup>
  ```bash cURL theme={null}
  curl https://live.dodopayments.com/moderation/usage \
    -H "Authorization: Bearer $DODO_PAYMENTS_API_KEY"
  ```

  ```typescript Node.js theme={null}
  const usage = await client.moderation.retrieveUsage();
  console.log(usage.unbilled_screens, usage.screens_to_next_block);
  ```

  ```python Python theme={null}
  usage = client.moderation.retrieve_usage()
  print(usage.unbilled_screens, usage.screens_to_next_block)
  ```
</CodeGroup>

测试模式不会记录筛查次数，因此用量端点不会返回测试模式活动。

## 访问权限和隐私

筛查需要具有写入权限的 API 密钥。任何 API 密钥（包括只读密钥）都可以读取用量。请参阅[身份验证](/api-reference/introduction#authentication)，了解如何创建密钥并设置其访问级别。

Dodo Payments 不会存储您筛查的文本或图像，也不会将其写入日志。对于每次实时模式筛查，Dodo Payments 会保留时间、判定结果以及您提供的 `request_id`，用于计费和用量报告。

<CardGroup cols={2}>
  <Card title="Usage-Based Billing" icon="arrow-trend-up" href="/features/usage-based-billing/introduction">
    向您自己的客户收取每次生成的费用。
  </Card>

  <Card title="Credit-Based Billing" icon="coins" href="/features/credit-based-billing">
    销售生成额度，并按使用次数扣除。
  </Card>
</CardGroup>
