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

# Migrate from Perspective API

> Move an existing Perspective API integration to Moderation API: swap the request, map the response fields, and map each Perspective attribute to a policy.

Perspective API gives you toxicity scores you threshold in your own code. Moderation API returns the same scores for the same categories, so that part of your integration carries over. It also runs many more policies, accepts images, video, and audio, and includes a review queue, a rules engine, and custom models. None of that is needed to migrate.

This guide shows how to replace the Perspective call, where each field and attribute goes, and how to carry your thresholds across.

<Note>
  Google Jigsaw has announced that Perspective API will no longer be in service
  after 2026. Existing integrations keep working until then, but new usage and
  quota increases are no longer approved.
</Note>

<Tip>
  You don't have to do this yourself. Send us the code that calls Perspective
  and we'll write the migration for you, validate the scores against your
  historical data, and stay on call through cutover. [Book a migration
  call](https://moderationapi.com/sales) or email
  [support@moderationapi.com](mailto:support@moderationapi.com).
</Tip>

## Hand it to a coding agent

This guide is available as Markdown, so a coding agent working in your repository can do the migration for you. Paste the prompt below into Claude Code, Cursor, Codex, or whatever agent you use, and it will read the guide and rewrite your Perspective call sites.

```text Prompt wrap theme={"theme":"nord"}
Migrate my Perspective API integration to Moderation API. Read the guide at https://docs.moderationapi.com/guides/migrate-from-perspective-api.md and follow it: swap the request, map the response fields, and map each Perspective attribute to the policy in the guide's table. Keep my existing threshold logic by reading probability from the mapped policies, and don't change behavior beyond the API swap. Tell me about anything in my code that doesn't map cleanly, such as attributes or fields the guide marks as unsupported.
```

Use the **Copy page** option at the top of this page if your agent can't fetch URLs.

***

## At a glance

|                      | Perspective API                                                         | Moderation API                                                                             |
| -------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Endpoint             | `POST https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze` | `POST https://api.moderationapi.com/v1/moderate`                                           |
| Authentication       | API key in the `?key=` query parameter                                  | `Authorization: Bearer <key>` header                                                       |
| What you request     | Attributes, per request (`requestedAttributes`)                         | Policies, per channel (configured in the dashboard)                                        |
| Scores               | `attributeScores.<ATTRIBUTE>.summaryScore.value` (0 to 1)               | `policies[].probability` (0 to 1), plus `flagged` per policy                               |
| Decision             | You threshold the score yourself                                        | `recommendation.action` (`allow`, `review`, `reject`), or threshold `probability` yourself |
| Conversation context | Sent inline in `context.entries`                                        | Retrieved from earlier submissions with the same `conversationId`                          |
| Language             | Optional `languages` hint                                               | Detected automatically, returned in `insights`                                             |
| Content types        | Text                                                                    | Text, image, video, audio, and mixed objects                                               |

***

## Before you start

Create a project in the [dashboard](https://dash.moderationapi.com) and copy its API key. Store it as `MODAPI_SECRET_KEY`, which is the environment variable the official SDKs read.

See [Authentication](/api-reference/authentication) for details.

### Enable the policies that match your attributes

With Perspective you list the attributes you want in every request. With Moderation API the policies are configured once, on the project's channel, and every request runs all of them.

Open **Policies** in the dashboard and enable the policies that correspond to the attributes you request today. Use the [attribute mapping table](#map-attributes-to-policies) below to find each one.

***

## Swap the request

Replace the Perspective call with a call to `/moderate`. Pick your language below, then switch between the **Perspective** and **Moderation API** tabs to compare the two calls.

<Tabs>
  <Tab title="TypeScript">
    <CodeGroup>
      ```typescript Perspective theme={"theme":"nord"}
      import { google } from "googleapis";

      const client = await google.discoverAPI(
        "https://commentanalyzer.googleapis.com/$discovery/rest?version=v1alpha1"
      );

      const { data } = await client.comments.analyze({
        key: process.env.PERSPECTIVE_API_KEY,
        resource: {
          comment: { text: "You are an idiot" },
          requestedAttributes: {
            TOXICITY: {},
            SEVERE_TOXICITY: {},
            INSULT: {},
            THREAT: {},
            IDENTITY_ATTACK: {},
          },
          clientToken: "comment-123",
          doNotStore: true,
        },
      });

      const toxicity =
        data.attributeScores.TOXICITY.summaryScore.value;
      ```

      ```typescript Moderation API theme={"theme":"nord"}
      import ModerationAPI from "@moderation-api/sdk";

      // Reads MODAPI_SECRET_KEY from the environment
      const moderationApi = new ModerationAPI();

      const result = await moderationApi.content.submit({
        content: { type: "text", text: "You are an idiot" },
        contentId: "comment-123",
        doNotStore: true,
      });

      const toxicity = result.policies.find(
        (p) => p.id === "toxicity"
      )?.probability;

      // Or skip thresholds entirely
      if (result.recommendation.action === "reject") {
        // Block the content
      }
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Python">
    <CodeGroup>
      ```python Perspective theme={"theme":"nord"}
      import os
      from googleapiclient import discovery

      client = discovery.build(
          "commentanalyzer", "v1alpha1",
          developerKey=os.environ["PERSPECTIVE_API_KEY"],
          discoveryServiceUrl=(
              "https://commentanalyzer.googleapis.com/"
              "$discovery/rest?version=v1alpha1"
          ),
          static_discovery=False,
      )

      response = client.comments().analyze(body={
          "comment": {"text": "You are an idiot"},
          "requestedAttributes": {
              "TOXICITY": {}, "SEVERE_TOXICITY": {},
              "INSULT": {}, "THREAT": {}, "IDENTITY_ATTACK": {},
          },
          "clientToken": "comment-123",
          "doNotStore": True,
      }).execute()

      toxicity = (
          response["attributeScores"]["TOXICITY"]["summaryScore"]["value"]
      )
      ```

      ```python Moderation API theme={"theme":"nord"}
      import os
      from moderation_api import ModerationAPI

      client = ModerationAPI(
          secret_key=os.environ["MODAPI_SECRET_KEY"],
      )

      response = client.content.submit(
          content={"type": "text", "text": "You are an idiot"},
          content_id="comment-123",
          do_not_store=True,
      )

      toxicity = next(
          (p.probability for p in response.policies if p.id == "toxicity"),
          None,
      )

      # Or skip thresholds entirely
      if response.recommendation.action == "reject":
          pass  # Block the content
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Ruby">
    <CodeGroup>
      ```ruby Perspective theme={"theme":"nord"}
      require "net/http"
      require "json"

      uri = URI(
        "https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze" \
        "?key=#{ENV['PERSPECTIVE_API_KEY']}"
      )

      response = Net::HTTP.post(
        uri,
        {
          comment: { text: "You are an idiot" },
          requestedAttributes: {
            TOXICITY: {}, SEVERE_TOXICITY: {},
            INSULT: {}, THREAT: {}, IDENTITY_ATTACK: {},
          },
          clientToken: "comment-123",
          doNotStore: true,
        }.to_json,
        "Content-Type" => "application/json",
      )

      data = JSON.parse(response.body)
      toxicity = data["attributeScores"]["TOXICITY"]["summaryScore"]["value"]
      ```

      ```ruby Moderation API theme={"theme":"nord"}
      require "moderation_api"

      client = ModerationAPI::Client.new(
        secret_key: ENV["MODAPI_SECRET_KEY"],
      )

      response = client.content.submit(
        content: { type: "text", text: "You are an idiot" },
        content_id: "comment-123",
        do_not_store: true,
      )

      toxicity = response.policies
        .find { |p| p.id == "toxicity" }
        &.probability

      # Or skip thresholds entirely
      if response.recommendation.action == "reject"
        # Block the content
      end
      ```
    </CodeGroup>
  </Tab>

  <Tab title="PHP">
    <CodeGroup>
      ```php Perspective theme={"theme":"nord"}
      <?php
      $ch = curl_init(
          "https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze"
          . "?key=" . getenv("PERSPECTIVE_API_KEY")
      );

      curl_setopt_array($ch, [
          CURLOPT_POST => true,
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
          CURLOPT_POSTFIELDS => json_encode([
              "comment" => ["text" => "You are an idiot"],
              "requestedAttributes" => [
                  "TOXICITY" => (object) [],
                  "SEVERE_TOXICITY" => (object) [],
                  "INSULT" => (object) [],
                  "THREAT" => (object) [],
                  "IDENTITY_ATTACK" => (object) [],
              ],
              "clientToken" => "comment-123",
              "doNotStore" => true,
          ]),
      ]);

      $data = json_decode(curl_exec($ch), true);
      $toxicity = $data["attributeScores"]["TOXICITY"]["summaryScore"]["value"];
      ```

      ```php Moderation API theme={"theme":"nord"}
      <?php
      use ModerationAPI\Client;

      $client = new Client(secretKey: getenv("MODAPI_SECRET_KEY"));

      $response = $client->content->submit([
          "content" => ["type" => "text", "text" => "You are an idiot"],
          "contentId" => "comment-123",
          "doNotStore" => true,
      ]);

      $toxicity = null;
      foreach ($response->policies as $policy) {
          if ($policy->id === "toxicity") {
              $toxicity = $policy->probability;
          }
      }

      // Or skip thresholds entirely
      if ($response->recommendation->action === "reject") {
          // Block the content
      }
      ```
    </CodeGroup>
  </Tab>

  <Tab title="cURL">
    <CodeGroup>
      ```bash Perspective theme={"theme":"nord"}
      curl -X POST \
        "https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze?key=$PERSPECTIVE_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "comment": { "text": "You are an idiot" },
          "requestedAttributes": {
            "TOXICITY": {}, "SEVERE_TOXICITY": {},
            "INSULT": {}, "THREAT": {}, "IDENTITY_ATTACK": {}
          },
          "clientToken": "comment-123",
          "doNotStore": true
        }'
      ```

      ```bash Moderation API theme={"theme":"nord"}
      curl -X POST https://api.moderationapi.com/v1/moderate \
        -H "Authorization: Bearer $MODAPI_SECRET_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "content": { "type": "text", "text": "You are an idiot" },
          "contentId": "comment-123",
          "doNotStore": true
        }'
      ```
    </CodeGroup>
  </Tab>
</Tabs>

<Tip>
  Rather not touch the call sites yourself? [Send us your
  code](https://moderationapi.com/sales) and we'll write the migration for you.
</Tip>

***

## Read the response

Perspective returns one score per requested attribute. Moderation API returns one entry per enabled policy in the `policies` array, plus an overall `evaluation` and a `recommendation` you can act on directly.

<Columns cols={2}>
  <Column>
    ```json Perspective response theme={"theme":"nord"}
    {
      "attributeScores": {
        "TOXICITY": {
          "summaryScore": {
            "value": 0.91,
            "type": "PROBABILITY"
          }
        },
        "SEVERE_TOXICITY": {
          "summaryScore": {
            "value": 0.42,
            "type": "PROBABILITY"
          }
        }
      },
      "languages": ["en"],
      "detectedLanguages": ["en"],
      "clientToken": "comment-123"
    }
    ```
  </Column>

  <Column>
    ```json Moderation API response (trimmed) theme={"theme":"nord"}
    {
      "content": { "id": "comment-123" },
      "evaluation": {
        "flagged": true,
        "flag_probability": 0.91,
        "severity_score": 0.62
      },
      "recommendation": {
        "action": "review",
        "reason_codes": ["severity_review"]
      },
      "policies": [
        {
          "id": "toxicity",
          "type": "classifier",
          "probability": 0.91,
          "flagged": true
        },
        {
          "id": "toxicity_severe",
          "type": "classifier",
          "probability": 0.42,
          "flagged": false
        }
      ],
      "insights": [
        { "id": "language", "type": "insight", "value": "en", "probability": 0.99 }
      ]
    }
    ```
  </Column>
</Columns>

See [Understanding API responses](/content-moderation/acting-on-responses) for the full response shape, and the [field mapping](#map-request-and-response-fields) below for every field.

***

## If you rely on the scores

Most Perspective integrations read `summaryScore.value` for each attribute and compare it against a threshold in their own code, often a different one per attribute. You can keep doing exactly that.

Each policy in the `policies` array carries a `probability` between 0 and 1, the same range Perspective uses. Build the same score object you have today from the mapped policies, and your downstream logic doesn't need to change:

<Columns cols={2}>
  <Column>
    ```javascript Perspective theme={"theme":"nord"}
    const scores = data.attributeScores;

    const toxicity = scores.TOXICITY.summaryScore.value;
    const severe = scores.SEVERE_TOXICITY.summaryScore.value;
    const identity = scores.IDENTITY_ATTACK.summaryScore.value;

    if (severe >= 0.9 || identity >= 0.8) {
      reject();
    } else if (toxicity >= 0.7) {
      review();
    }
    ```
  </Column>

  <Column>
    ```javascript Moderation API theme={"theme":"nord"}
    const score = (id) =>
      result.policies.find((p) => p.id === id)?.probability ?? 0;

    const toxicity = score("toxicity");
    const severe = score("toxicity_severe");
    const identity = score("hate");

    if (severe >= 0.9 || identity >= 0.8) {
      reject();
    } else if (toxicity >= 0.7) {
      review();
    }
    ```
  </Column>
</Columns>

<Warning>
  The range is the same, but the models are not. A comment Perspective scored
  `0.72` for `TOXICITY` won't necessarily score `0.72` for `toxicity`, so treat
  the thresholds you tuned for Perspective as a starting point. Run both APIs
  on the same traffic before you cut over. See [Validate before you cut
  over](#validate-before-you-cut-over).
</Warning>

<Note>
  `probability` is always returned, even below the threshold. Perspective's
  `scoreThreshold` option, which drops scores under a cutoff, has no
  equivalent. Filter on your side if you need it.
</Note>

### Use the dashboard to find the right thresholds

You can set threshold values in the dashboard and see what they would do before committing to them. Each policy has a **Threshold** tab under **Policies** in your project, showing a histogram of the confidence scores from your recent traffic. As you move the slider, the bars to the right are what would be flagged and the bars to the left what wouldn't, with counts for how many recent items would flip sides.

<Frame>
  <img src="https://mintcdn.com/moderationapi/TAlbkkhuoVUU4PzM/images/policy-threshold.png?fit=max&auto=format&n=TAlbkkhuoVUU4PzM&q=85&s=34c4691769926f208187f147603f76f5" alt="Policy threshold slider with histogram and would-flag / would-not-flag counts" width="844" height="548" data-path="images/policy-threshold.png" />
</Frame>

This works well as a calibration tool even if you keep your thresholds in code: send real traffic, find the cutoff on the histogram that matches the behavior you had with Perspective, then copy that number into your integration. See [Per-policy thresholds](/content-moderation/thresholds#per-policy-thresholds).

<Tip>
  We can also calibrate the thresholds for you. Share a sample of content with
  your Perspective scores or your moderators' decisions, and we'll run the
  comparison against Moderation API and hand back the threshold per policy that
  best reproduces your current behavior. [Get in
  touch](https://moderationapi.com/sales) to set it up.
</Tip>

### Or let the API make the decision

Once the thresholds live in the dashboard, you can drop the score comparison entirely. The API sets `flagged` on each policy, combines everything into a `severity_score`, and returns a `recommendation.action` of `allow`, `review`, or `reject`.

```javascript theme={"theme":"nord"}
switch (result.recommendation.action) {
  case "reject":
    // Block the content
    break;
  case "review":
    // Save it and send it to the review queue
    break;
  case "allow":
    // Publish
    break;
}
```

Thresholds become something you tune against real data instead of a constant in your code, and [content rules](/content-moderation/rules) cover the cases a single score can't express, like "reject `toxicity_severe` from new accounts, review it from everyone else."

***

## Validate before you cut over

Run both APIs side by side on live traffic before you remove Perspective:

1. Enable **dry-run** in your project settings. The API still analyzes and stores everything, but `flagged` is always `false`, so nothing is blocked while you compare.
2. Send your production content to `/moderate` alongside your existing Perspective call and compare scores on the content you already have decisions for.
3. Adjust per-policy thresholds using the histogram on each policy's **Threshold** tab, then turn dry-run off.

<Snippet file="dry-run.mdx" />

If you'd rather test one policy at a time, [shadow flagging](/content-moderation/thresholds#flag-vs-shadow-flag) lets a policy score traffic without affecting `flagged`.

***

## Map request and response fields

### Request

| Perspective API                             | Moderation API                                                                               | Notes                                                                                                                                                                                                  |
| ------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `comment.text`                              | `content.text` with `content.type: "text"`                                                   | Up to 100,000 characters per text.                                                                                                                                                                     |
| `comment.type` (`PLAIN_TEXT`, `HTML`)       | —                                                                                            | Send plain text.                                                                                                                                                                                       |
| `context.entries[]`                         | `conversationId` + [context awareness](/content-moderation/submit-content#context-awareness) | Perspective takes prior messages inline. Moderation API looks up earlier submissions with the same `conversationId` (or `authorId`), so submit each message as it arrives.                             |
| `requestedAttributes`                       | Channel policies                                                                             | Enabled per channel in the dashboard, not per request. See the [attribute mapping](#map-attributes-to-policies). Enterprise plans can override channel policies per request with the `policies` field. |
| `requestedAttributes.<ATTR>.scoreThreshold` | Per-policy [detection threshold](/content-moderation/thresholds#per-policy-thresholds)       | Perspective omits scores below the threshold; Moderation API always returns `probability` and sets `flagged` when it crosses the threshold.                                                            |
| `requestedAttributes.<ATTR>.scoreType`      | —                                                                                            | Scores are always probabilities between 0 and 1.                                                                                                                                                       |
| `languages`                                 | —                                                                                            | Language is detected automatically and returned as the `language` insight.                                                                                                                             |
| `doNotStore`                                | `doNotStore`                                                                                 | Same name. Content is analyzed but not stored, so it won't appear in the dashboard or review.                                                                                                          |
| `clientToken`                               | `contentId`                                                                                  | Echoed back as `content.id`. Resubmitting the same `contentId` updates the stored item instead of creating a duplicate.                                                                                |
| `sessionId`                                 | `conversationId` or `authorId`                                                               | Use `conversationId` to group a thread, `authorId` to group by user. Both power context awareness and the review queue.                                                                                |
| `communityId`                               | `channel`                                                                                    | Route the request to a specific channel configuration. Defaults to the project's default channel.                                                                                                      |
| `spanAnnotations`                           | <Badge color="yellow" size="sm">WIP</Badge>                                                  | Classifier policies don't return per-span scores yet. Entity matchers such as `personal_information` and `url` do return `span` offsets for each match.                                                |
| —                                           | `metaType`, `metadata`, `timestamp`, `clientAction`                                          | New. See [Submitting content](/content-moderation/submit-content#content-metadata).                                                                                                                    |

### Response

| Perspective API                             | Moderation API                                                      | Notes                                                                                                                            |
| ------------------------------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `attributeScores.<ATTR>.summaryScore.value` | `policies[].probability` where `policies[].id` is the mapped policy | Both are probabilities between 0 and 1.                                                                                          |
| `attributeScores.<ATTR>.summaryScore.type`  | —                                                                   | Always a probability.                                                                                                            |
| `attributeScores.<ATTR>.spanScores[]`       | <Badge color="yellow" size="sm">WIP</Badge>                         | See `spanAnnotations` above.                                                                                                     |
| —                                           | `policies[].flagged`                                                | New. `true` when `probability` crossed the policy's threshold.                                                                   |
| —                                           | `evaluation.flagged`, `evaluation.severity_score`                   | New. Whether any policy flagged, and a single 0 to 1 severity across all of them.                                                |
| —                                           | `recommendation.action`, `recommendation.reason_codes`              | New. The suggested action and why. See [Use the recommendation](/content-moderation/acting-on-responses#use-the-recommendation). |
| `languages`, `detectedLanguages`            | `insights[]` entry with `id: "language"`                            | The detected language code is in `value`.                                                                                        |
| `clientToken`                               | `content.id`                                                        | The `contentId` you sent, or a generated ID if you didn't send one.                                                              |

### Sending feedback

Perspective's `comments:suggestscore` method lets you report a score you disagree with. Moderation API has no equivalent call. Instead, decisions your moderators make in the [review queue](/review/overview) are stored as cases in the [casebook](/casebook/overview), which applies those rulings to similar content automatically. You can resolve items programmatically with the [resolve a queue item](/api-reference/review-queues/resolve-a-queue-item) endpoint.

***

## Map attributes to policies

Each Perspective attribute maps to one Moderation API policy. Enable the policy in the dashboard, then read its entry in the `policies` array using the `id` below.

| Perspective attribute | Moderation API policy                   |                      Status                      | Notes                                                                              |
| --------------------- | --------------------------------------- | :----------------------------------------------: | ---------------------------------------------------------------------------------- |
| `TOXICITY`            | [`toxicity`](/policies/toxicity)        | <Badge color="green" size="sm">Supported</Badge> | Rude, disrespectful, or hostile language.                                          |
| `SEVERE_TOXICITY`     | [`toxicity_severe`](/policies/toxicity) | <Badge color="green" size="sm">Supported</Badge> | A stricter classifier for abusive content.                                         |
| `IDENTITY_ATTACK`     | [`hate`](/policies/toxicity)            | <Badge color="green" size="sm">Supported</Badge> | Hate and discrimination targeting protected groups. Also runs on images and video. |
| `INSULT`              | [`toxicity`](/policies/toxicity)        | <Badge color="green" size="sm">Supported</Badge> | Insults are scored by the `toxicity` policy; there is no separate insult policy.   |
| `PROFANITY`           | [`profanity`](/policies/nsfw)           | <Badge color="green" size="sm">Supported</Badge> | Swearing and vulgar language.                                                      |
| `THREAT`              | [`violence`](/policies/nsfw)            | <Badge color="green" size="sm">Supported</Badge> | Threats and violent content. Also runs on images and video.                        |
| `SEXUALLY_EXPLICIT`   | [`sexual`](/policies/nsfw)              | <Badge color="green" size="sm">Supported</Badge> | Also runs on images, video, and audio.                                             |
| `FLIRTATION`          | [`flirtation`](/policies/nsfw)          | <Badge color="green" size="sm">Supported</Badge> | Pickup lines, compliments on looks, innuendo.                                      |

### Bridging attributes

Perspective's experimental bridging attributes score constructive qualities rather than harms. Moderation API doesn't have equivalents yet.

| Perspective attribute         | Moderation API policy |                    Status                   | Notes                                                      |
| ----------------------------- | --------------------- | :-----------------------------------------: | ---------------------------------------------------------- |
| `AFFINITY_EXPERIMENTAL`       | —                     | <Badge color="yellow" size="sm">WIP</Badge> | Shared interests or outlook between the author and others. |
| `COMPASSION_EXPERIMENTAL`     | —                     | <Badge color="yellow" size="sm">WIP</Badge> | Concern, empathy, or support for others.                   |
| `CURIOSITY_EXPERIMENTAL`      | —                     | <Badge color="yellow" size="sm">WIP</Badge> | Follow-up questions to understand another person or idea.  |
| `NUANCE_EXPERIMENTAL`         | —                     | <Badge color="yellow" size="sm">WIP</Badge> | Multiple points of view or useful context.                 |
| `PERSONAL_STORY_EXPERIMENTAL` | —                     | <Badge color="yellow" size="sm">WIP</Badge> | A personal experience used to support the comment.         |
| `REASONING_EXPERIMENTAL`      | —                     | <Badge color="yellow" size="sm">WIP</Badge> | Specific, well-reasoned points without provocation.        |
| `RESPECT_EXPERIMENTAL`        | —                     | <Badge color="yellow" size="sm">WIP</Badge> | Deference or appreciation toward others.                   |

<Note>
  If you rely on any attribute marked WIP, [let us
  know](mailto:support@moderationapi.com). It helps us prioritize.
</Note>

Once you've migrated, consider enabling policies Perspective never offered:

<CardGroup cols={2}>
  <Card title="Self-harm" icon="heart-pulse" href="/policies/nsfw">
    Self-harm, suicide, and eating-disorder content, which often deserves a
    support resource rather than a rejection.
  </Card>

  <Card title="Personal information" icon="user-shield" href="/policies/privacy">
    Detect and mask emails, phone numbers, and addresses.
  </Card>

  <Card title="Phishing and code abuse" icon="shield-halved" href="/policies/spam">
    Scam messages, prompt injection, and malicious code.
  </Card>

  <Card title="Guidelines" icon="pen-ruler" href="/policies/guidelines">
    Write your own rules in plain language and get them back as policies.
  </Card>
</CardGroup>

***

## Other differences worth knowing

* **Rate limits.** Every response includes `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers, and a `429` includes `Retry-After`. See [Rate limits](/api-reference/rate-limits).
* **Partial failures.** If one policy fails, the rest still return. Check `meta.status` for `partial_success` and read the `errors` array. See [Handle errors](/content-moderation/acting-on-responses#handle-errors).
* **Mixed content.** Send a profile or listing with several fields as a single `object` and get `flagged_fields` back per policy. See [Object](/content-moderation/submit-content#object).
* **Review queue.** Every stored submission is available for human review without any extra integration work. See [Review](/review/overview).
