> ## 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 key로 실행되므로 별도로 가입할 필요가 없습니다. Dodo Payments는 개별 비즈니스에 대해 이를 비활성화할 수 있으며, 이후 호출은 `403`와 `MODERATION_DISABLED`을 반환합니다.

## Moderation API를 만든 이유

AI 생성 제품은 사용자가 입력하는 내용을 바탕으로 새로운 콘텐츠를 만듭니다. 모든 프롬프트를 수동으로 검토할 수는 없으며, 유해한 출력 하나만으로도 비즈니스가 위험에 처할 수 있습니다.

Merchant of Record인 Dodo Payments는 플랫폼을 통해 판매되는 항목에 대해 법적·평판상의 책임을 집니다. [Merchant Acceptance Policy](/miscellaneous/merchant-acceptance)는 AI 콘텐츠 생성 도구를 검토하며, AI로 생성된 콘텐츠를 포함해 사칭, 딥페이크 또는 노골적인 콘텐츠를 허용하지 않습니다. 유해한 콘텐츠, 과도한 chargeback 또는 payment partner의 플래그를 생성하는 계정은 검토 대상이 되거나 정지될 수 있습니다.

모델이 이러한 콘텐츠를 생성하기 전에 차단할 수 있도록 Moderation API를 만들었습니다:

* **생성 전에 검사합니다.** 차단된 프롬프트는 모델에 도달하지 않으므로 유해한 출력이 존재하지 않으며 해당 작업에 컴퓨팅 리소스를 사용할 필요도 없습니다.
* **생성에 중요한 카테고리를 다룹니다.** 검사는 실제 인물의 유사성, 비동의 интим 이미지, 미성년자를 암시하는 언어, 그리고 실제 인물과 성적 콘텐츠의 결합으로 성적 딥페이크를 나타내는 항목을 포함한 17개 카테고리에 점수를 매깁니다.
* **다른 vendor 없이 통합합니다.** API는 Dodo Payments API key로 실행되며 요금은 잔액에서 차감됩니다. 별도의 계약, invoice 또는 계정이 필요하지 않습니다.
* **사용자 콘텐츠를 비공개로 유지합니다.** Dodo Payments는 검사하는 텍스트와 이미지를 저장하거나 log에 기록하지 않습니다.

<Note>
  Moderation API는 자체 정책 시행을 위한 도구입니다. Merchant Acceptance Policy를 대체하지 않으며, 제품이 생성하는 콘텐츠에 대한 책임은 여전히 귀사에 있습니다.
</Note>

## 작동 방식

사용자가 프롬프트를 제출한 후 모델이 실행되기 전에 backend에서 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` field에 판정이 포함됩니다:

| 판정      | 의미                                                | 수행할 작업                                   |
| ------- | ------------------------------------------------- | ---------------------------------------- |
| `allow` | 콘텐츠가 통과했습니다.                                      | 생성합니다.                                   |
| `flag`  | 콘텐츠가 판단이 필요한 카테고리 threshold를 넘었습니다. 소프트 거부가 아닙니다. | 자체 정책을 적용합니다. 차단하거나 검토로 보내거나 생성할 수 있습니다. |
| `deny`  | 콘텐츠를 생성해서는 안 됩니다.                                 | 요청을 차단하고 사용자에게 오류를 표시합니다.                |

<Warning>
  판정을 받지 못한 경우 생성하지 마세요. `503`는 Dodo Payments가 판정을 생성하지 못했다는 의미이며, timeout 또는 network error가 발생하면 판정을 받을 수 없습니다. 이 모든 경우를 차단으로 처리하고 사용자에게 다시 시도하도록 요청하세요.
</Warning>

## 프롬프트 검사

프롬프트를 검사하려면 `POST` request를 `/moderation/screen`로 전송하고, `text`와 `image` 중 하나 이상을 포함합니다. 요청은 세 field를 받습니다:

| Field        | Type   | 설명                                                                                                              |
| ------------ | ------ | --------------------------------------------------------------------------------------------------------------- |
| `text`       | string | 검사할 텍스트로, 최대 8,000자입니다.                                                                                         |
| `image`      | string | base64 형식의 검사할 이미지이며, `data:image/...;base64,` prefix가 있거나 없을 수 있습니다.                                           |
| `request_id` | string | 선택 사항입니다. generation ID와 같은 이 검사에 대한 귀사의 identifier이며, control character 없이 최대 128자입니다. response에서 변경 없이 반환됩니다. |

TypeScript 및 Python SDK는 endpoint를 `client.moderation.screen()`로 제공합니다. 이 예시는 `deny`, `flag` 또는 모든 error 발생 시 생성을 차단합니다:

<Note>
  예시는 live mode만 moderation model을 실행하므로 live mode를 호출합니다. Test mode는 [mock verdicts](#testing-your-integration)를 반환하며 콘텐츠를 검사하지 않습니다. Live mode 검사는 청구됩니다.
</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`와 동일하게 처리합니다. 제품에서 일부 flagged content를 허용하는 경우 `triggered`를 확인하여 카테고리별로 결정하세요.

<Tip>
  사용자가 작성한 텍스트를 검사하고, 그 주위를 감싸는 prompt template은 검사하지 마세요. 자체 template은 모든 호출에서 동일하며 검사에 아무것도 추가하지 않습니다.
</Tip>

### 이미지 검사

업로드된 reference image 또는 생성된 이미지를 사용자에게 표시하기 전에 검사하려면 이미지를 전송합니다. 이미지는 다음 요구 사항을 충족해야 합니다:

* 형식은 JPEG, PNG, WebP, GIF 또는 BMP입니다.
* base64 string은 최대 6,991,530자이며, decode된 이미지는 최대 5 MiB입니다.
* 이미지는 하나의 still frame이어야 합니다. Animated GIF 및 WebP 이미지는 거부됩니다.
* 가장 긴 변은 최소 32 pixel이어야 합니다.

이 검사 중 하나를 통과하지 못한 이미지는 `400`와 `MODERATION_INVALID_IMAGE`를 반환하거나, 너무 큰 경우 `413`와 `MODERATION_INPUT_TOO_LARGE`를 반환합니다.

이미지를 검사하려면 file을 읽고 base64로 encode한 다음 `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>

이미지 검사 error도 텍스트 검사와 동일하게 처리합니다. 호출에서 exception이 발생하면 생성하지 마세요.

## Response 읽기

response에는 판정과 이를 뒷받침하는 근거가 반환됩니다:

| Field                | 설명                                                                                        |
| -------------------- | ----------------------------------------------------------------------------------------- |
| `decision`           | 판정: `allow`, `flag` 또는 `deny`입니다.                                                         |
| `triggered`          | 카테고리 threshold를 초과한 카테고리입니다. 일반 검사에서 `flag`가 반환된 경우 비어 있을 수 있습니다.                         |
| `compound_triggered` | 실제 인물 유사성과 성적 콘텐츠가 함께 결합 threshold를 초과하여 성적 딥페이크 패턴이 된 경우 `true`입니다.                      |
| `categories`         | 콘텐츠가 각 카테고리에 해당할 확률로, 0부터 1까지입니다.                                                         |
| `provenance`         | 각 score가 측정된 방식입니다. 해당 카테고리 하나를 검사한 경우 `targeted`, 모든 카테고리를 다루는 일반 검사를 사용한 경우 `broad`입니다. |
| `notes`              | 결정에 대한 사람이 읽을 수 있는 이유입니다. 문구는 변경될 수 있으므로 parse하지 마세요.                                     |
| `normalized_applied` | invisible 또는 look-alike character를 제거하여 텍스트도 검사한 경우 `true`입니다.                            |
| `passes`             | model이 이 검사에 대해 답변한 yes/no question의 수입니다.                                                |
| `latency_ms`         | 검사를 수행하는 데 걸린 시간(밀리초)입니다.                                                                 |
| `request_id`         | 전송한 `request_id` 또는 `null`입니다.                                                            |

로직은 `decision` 및 `triggered`를 기준으로 작성하세요. 각 카테고리에는 고유한 threshold가 있으므로 code에서 단일 score cutoff를 사용하면 판정과 일치하지 않습니다.

### 카테고리

모든 response는 콘텐츠를 17개 카테고리와 비교하여 score를 계산합니다:

| Category                          | 포함 범위                                                   |
| --------------------------------- | ------------------------------------------------------- |
| `violent_crimes`                  | 폭력 범죄입니다.                                               |
| `sex_related_crimes`              | 성 관련 범죄입니다.                                             |
| `child_sexual_exploitation`       | 아동 성 착취입니다.                                             |
| `suicide_and_self_harm`           | 자살 및 자해입니다.                                             |
| `indiscriminate_weapons`          | 화학, 생물학, 방사능, 핵 또는 폭발성 무기입니다.                           |
| `intellectual_property`           | Copyright 또는 trademark 침해입니다.                           |
| `defamation`                      | 실제 인물의 평판을 훼손할 가능성이 있는 허위 묘사입니다.                        |
| `non_violent_crimes`              | 비폭력 범죄입니다.                                              |
| `hate`                            | 보호되는 특성 때문에 사람을 모욕하는 내용입니다.                             |
| `privacy`                         | 개인에 대한 민감한 private information입니다.                      |
| `specialized_advice`              | 자격 없는 financial, medical, legal 또는 electoral advice입니다. |
| `sexual_content`                  | 성적으로 노골적이거나 포르노그래픽한 콘텐츠입니다.                             |
| `non_consensual_intimate_imagery` | 실제 인물의 옷을 벗기거나, nude로 만들거나, 성적 대상화하는 내용입니다.             |
| `minor_coded_language`            | 대상이 미성년자임을 암시하는 나이 관련 언어입니다.                            |
| `real_person_likeness`            | 실제로 식별 가능하고 이름이 있는 인물의 유사성입니다.                          |
| `living_artist_style`             | 특정 생존 artist의 signature style을 모방하는 내용입니다.              |
| `prompt_injection`                | system의 instruction을 override하거나 manipulate하려는 시도입니다.   |

## Error 처리

Error는 `code` 및 `message`가 포함된 표준 Dodo Payments error body를 반환합니다. 어떤 error도 판정이 아니므로 어느 경우에도 생성을 허용해서는 안 됩니다:

| Status | `code`                       | Cause                                                         | 수행할 작업                                              |
| ------ | ---------------------------- | ------------------------------------------------------------- | --------------------------------------------------- |
| `400`  | `INVALID_REQUEST_PARAMETERS` | 요청 형식이 잘못되었거나 `text`와 `image`가 모두 없습니다.                       | 요청을 수정합니다.                                          |
| `400`  | `MODERATION_INVALID_IMAGE`   | 이미지를 decode할 수 없거나 animated이거나 너무 작습니다.                       | 지원되는 still image를 전송합니다.                            |
| `403`  | `MODERATION_DISABLED`        | Dodo Payments가 귀사의 비즈니스에 대해 Moderation API를 비활성화했습니다.         | 이유를 확인하려면 support에 문의합니다.                           |
| `413`  | `MODERATION_INPUT_TOO_LARGE` | `text`가 8,000자를 초과하거나 `image`가 size limit을 초과했습니다.            | 텍스트를 줄이거나 이미지를 축소합니다.                               |
| `429`  | `MODERATION_OVERLOADED`      | Moderation capacity가 가득 찼습니다. 이는 throughput limit이며 판정이 아닙니다. | `Retry-After` header에 표시된 seconds만큼 기다린 후 retry합니다. |
| `503`  | `MODERATION_UNAVAILABLE`     | 사용 가능한 판정이 없습니다.                                              | 생성하지 않습니다. 나중에 retry합니다.                            |

SDK는 기본적으로 `429` 또는 `503`를 두 번 retry하고 시도 사이에 `Retry-After`만큼 기다립니다. retry가 모두 소진되면 SDK가 error를 발생시키며, code는 요청을 차단해야 합니다.

## 통합 테스트

Test mode는 mock verdicts를 반환하고 moderation model을 호출하지 않으므로 비용 없이 routing을 테스트할 수 있습니다. Test mode API key를 사용하여 `https://test.dodopayments.com`로 request를 전송합니다.

기본 mock verdict는 `allow`입니다. 다른 결과를 받으려면 `text` 어디에든 다음 string 중 하나를 넣습니다:

| `text`의 String         | Response                                                 |
| ---------------------- | -------------------------------------------------------- |
| `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`입니다.                       |

Mock verdict에는 mock임을 알리는 note가 포함되며 모든 category score는 `0`입니다. Test mode는 live mode와 동일한 request validation을 적용합니다. 이미지의 경우 base64 encoding과 format은 확인하지만 frame count나 dimensions는 확인하지 않습니다.

Live mode로 전환하기 전에 통합이 각 case를 처리하는지 확인합니다:

<Steps>
  <Step title="Deny Blocks Generation">
    `dodo_mock_deny`를 전송하고 model이 호출되지 않는지 확인합니다.
  </Step>

  <Step title="Flag Follows Your Policy">
    `dodo_mock_flag`를 전송하고 제품이 policy에 명시된 대로 동작하는지 확인합니다.
  </Step>

  <Step title="Overload Retries">
    `dodo_mock_overloaded`를 전송하고 code가 `Retry-After`를 기다리며 판정 없이 생성하지 않는지 확인합니다.
  </Step>

  <Step title="An Outage Blocks Generation">
    `dodo_mock_not_ready`를 전송하고 model이 호출되지 않는지 확인합니다.
  </Step>

  <Step title="Every Generation Path Screens">
    model에 도달하는 모든 code path가 먼저 Moderation API를 호출하는지 확인합니다.
  </Step>
</Steps>

## Pricing 및 Billing

Moderation API의 요금은 **청구 가능한 검사 1,000건당 \$0.30 USD**입니다. 무료 tier와 minimum은 없습니다.

청구 가능한 검사는 판정을 반환하는 live mode 검사입니다. 다음 검사는 무료이며 계산되지 않습니다:

* Test mode의 검사입니다.
* `429` 및 `503`를 포함해 error를 반환하는 검사입니다.

Dodo Payments는 1,000건 단위로 검사 요금을 전액 청구합니다. 각 단위는 1시간 이내에 청구되며, 단위를 채우지 못한 검사는 단위가 채워질 때까지 청구되지 않습니다. 요금은 USD balance에서 차감되며 event type `moderation_fees`와 함께 [balance ledger](/api-reference/balance-ledger/list-ledger-entries)에 표시됩니다. Payout에는 **Moderation Fees**로 표시됩니다.

### Usage 추적

usage를 확인하려면 `GET /moderation/usage`를 호출합니다. response는 다음을 반환합니다:

| Field                   | 설명                                                      |
| ----------------------- | ------------------------------------------------------- |
| `unbilled_screens`      | Dodo Payments가 아직 청구하지 않은 billable screen입니다.           |
| `screens_to_next_block` | 다음 1,000건 단위를 채우기 위해 아직 필요한 billable screen입니다.         |
| `daily`                 | 최근 30일 동안 UTC day별 billable screen입니다. 검사가 없는 날은 생략됩니다. |

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

Test mode에서는 검사가 기록되지 않으므로 usage endpoint는 test mode activity를 반환하지 않습니다.

## Access 및 Privacy

검사에는 write access가 있는 API key가 필요합니다. read-only key를 포함한 모든 API key는 usage를 읽을 수 있습니다. key를 생성하고 access level을 설정하는 방법은 [Authentication](/api-reference/introduction#authentication)을 참조하세요.

Dodo Payments는 검사하는 텍스트나 이미지를 저장하지 않으며 log에도 기록하지 않습니다. 각 live mode 검사에 대해 billing 및 usage reporting을 위해 시간, 판정 및 `request_id`를 보관합니다.

<CardGroup cols={2}>
  <Card title="Usage-Based Billing" icon="arrow-trend-up" href="/features/usage-based-billing/introduction">
    각 generation에 대해 자체 customer에게 요금을 청구합니다.
  </Card>

  <Card title="Credit-Based Billing" icon="coins" href="/features/credit-based-billing">
    generation credit를 판매하고 사용량별로 차감합니다.
  </Card>
</CardGroup>
