DEV Community

Paxmod
Paxmod

Posted on Originally published at paxmod.com

Perspective API alternative: how to migrate before the Dec 31 shutdown (with code)

Google's Perspective API stops working after December 31, 2026. Jigsaw closed usage and quota requests in February 2026 and isn't offering migration support. If your forum, comment section or game chat calls commentanalyzer.googleapis.com, that call starts failing on January 1.

Disclosure: we build Paxmod, a content moderation API for real-time chat, so this guide migrates you to Paxmod. The checklist at the end works whichever API you pick.

Short version

Paxmod isn't a drop-in clone of Perspective. The request and response look different. But in most codebases the switch is one function:

Perspective Paxmod
Endpoint POST https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze POST https://www.paxmod.com/api/v1/text
Auth ?key=KEY query param Authorization: Bearer KEY header
Text field comment.text message
Who and where sessionId (optional) user_id and context_id (optional, recommended)
Attributes requestedAttributes on every request Categories and thresholds set per project in the console
Score you read attributeScores.TOXICITY.summaryScore.value content_moderation.categories["Harassment"].score
Decision You compare scores to thresholds result is "flagged" or "not_flagged"
Max length 20 KB 5,000 characters (split longer text)

Use the www host. The apex domain redirects, and plenty of POST clients won't follow it.

Mapping Perspective attributes to Paxmod categories

Perspective Paxmod Notes
TOXICITY Harassment, Hate and Discrimination, Profanities Take the highest, or just use result
SEVERE_TOXICITY Threatening, Violence, Hate and Discrimination Take the highest
IDENTITY_ATTACK Hate and Discrimination Direct match
INSULT Harassment Harassment is a little broader
THREAT Threatening, Violence Take the highest
PROFANITY Profanities Dictionary matcher: 0.8 on a match, 0 otherwise, so keep the threshold below 0.8
SEXUALLY_EXPLICIT Sexual Direct match
FLIRTATION none fixed Write it as a plain-English rule in Advanced mode

Don't copy your thresholds across. Paxmod's scores come from different models, so a 0.8 in Perspective isn't a 0.8 in Paxmod. Each category has its own threshold (default 0.5, flagged when the score goes above it).

Before and after: curl

Before:

curl -X POST \
  "https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze?key=$PERSPECTIVE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "comment": { "text": "you are trash, uninstall" },
    "languages": ["en"],
    "requestedAttributes": { "TOXICITY": {}, "INSULT": {}, "THREAT": {} }
  }'
Enter fullscreen mode Exit fullscreen mode

After:

curl -X POST "https://www.paxmod.com/api/v1/text" \
  -H "Authorization: Bearer $PAXMOD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "you are trash, uninstall",
    "user_id": "player_123",
    "context_id": "lobby_7"
  }'
Enter fullscreen mode Exit fullscreen mode

Before and after: Node

Before:

const res = await fetch(
  `https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze?key=${process.env.PERSPECTIVE_API_KEY}`,
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      comment: { text },
      languages: ["en"],
      requestedAttributes: { TOXICITY: {} },
    }),
  }
);
const data = await res.json();
const isToxic = data.attributeScores.TOXICITY.summaryScore.value > 0.8;
Enter fullscreen mode Exit fullscreen mode

After:

const res = await fetch("https://www.paxmod.com/api/v1/text", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.PAXMOD_API_KEY}`,
  },
  body: JSON.stringify({ message: text, user_id: userId, context_id: channelId }),
});
const data = await res.json();
if (data.status === "error") throw new Error(data.error.code);

const isToxic = data.result === "flagged";
const harassment = data.content_moderation.categories["Harassment"]?.score ?? 0;
Enter fullscreen mode Exit fullscreen mode

Before and after: Python

Before:

import os, requests

r = requests.post(
    "https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze",
    params={"key": os.environ["PERSPECTIVE_API_KEY"]},
    json={
        "comment": {"text": text},
        "languages": ["en"],
        "requestedAttributes": {"TOXICITY": {}},
    },
    timeout=5,
)
is_toxic = r.json()["attributeScores"]["TOXICITY"]["summaryScore"]["value"] > 0.8
Enter fullscreen mode Exit fullscreen mode

After:

import os, requests

r = requests.post(
    "https://www.paxmod.com/api/v1/text",
    headers={"Authorization": f"Bearer {os.environ['PAXMOD_API_KEY']}"},
    json={"message": text, "user_id": user_id, "context_id": channel_id},
    timeout=5,
)
data = r.json()
is_toxic = data.get("result") == "flagged"
Enter fullscreen mode Exit fullscreen mode

What the Paxmod response looks like

Example values. You get back the categories enabled on your project:

{
  "status": "success",
  "id": "5f0c2a8e-1d9b-4c47-9a51-3c1f6f2b7e10",
  "service": "pax",
  "request": {
    "original_message": "you are trash, uninstall",
    "processed_message": "you are trash, uninstall",
    "timestamp": "2026-10-08T02:00:00.000Z"
  },
  "result": "flagged",
  "reason": "Content flagged for Harassment",
  "degraded": false,
  "unavailable_categories": [],
  "content_moderation": {
    "flagged": true,
    "categories": {
      "Harassment": { "flagged": true, "threshold": 0.5, "score": 0.93 },
      "Threatening": { "flagged": false, "threshold": 0.5, "score": 0.04 },
      "Personally Identifiable Information": { "flagged": false, "threshold": 0.5, "score": 0.01 }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
  • result is flagged when any enabled category goes above its threshold, and reason names which ones.
  • degraded: true means an upstream classifier was briefly unavailable, and the categories listed in unavailable_categories weren't scored on that call.
  • Errors look like {"status": "error", "error": {"code": "INVALID_API_KEY", ...}}.

The 6-step migration plan

  1. Start a trial and create a project. Copy the API key.
  2. Turn on the categories that match the Perspective attributes you used. For game chat, start with the Gaming chat preset.
  3. Swap the request: new URL, Bearer header, message plus user_id and context_id.
  4. Swap the parsing: read content_moderation.categories[...], or act on result.
  5. Shadow-run until December 31. Send the same messages to both APIs, compare decisions on real traffic, and set thresholds from that. This is the step people skip, and it only works while Perspective is still answering.
  6. Cut over and delete the Perspective key before January 1, 2027.

What Perspective never did

  • Personal information. "where do you live" gets flagged under Personally Identifiable Information. Perspective had no attribute for that, and in kids' game chat it matters more than swearing.
  • Game chat. Paxmod runs on custom Minecraft servers and Roblox games, with a Roblox module and a Unity integration. If you run a Minecraft plugin that used Perspective (ChatProtect and AutoMod both can), there's a Minecraft-specific guide.
  • Player-aware moderation. Each message carries a user_id and a context_id. The Studio plan adds conversation context, which checks a message against the player's recent history instead of on its own.
  • Your own rules. Advanced mode checks messages against a policy you write in plain English.
  • Harder to dodge. Text is normalised before scoring, including Unicode lookalike characters and hidden zero-width characters.

What it costs

Perspective was free, and Paxmod isn't. Indie is $29/month with 50,000 message credits, and Studio is $149/month with 500,000. Every plan starts with a 14-day free trial that includes 10,000 messages and every feature. Full details are on the pricing page.

Checklist, whatever API you pick

  • [ ] Find every call to commentanalyzer.googleapis.com (code, plugins, config).
  • [ ] Note which attributes and thresholds each one uses.
  • [ ] Check how your code handles errors. Plenty of integrations treat a failed call as "clean", so moderation can quietly stop on January 1.
  • [ ] Shadow-run the replacement while Perspective still works.
  • [ ] Cut over before December 31.

The full guide, with the mapping table and FAQ, is on paxmod.com.

Top comments (0)