# Perspective API alternative — comments:analyze drop-in (`dropin-apis/perspective-compat`) Actor

Perspective API alternative that accepts POST /v1alpha1/comments:analyze and returns the same attributeScores JSON. Google shuts the original down after 31 Dec 2026 with no migration. Change the host. $1 per 1,000 comments.

- **URL**: https://apify.com/dropin-apis/perspective-compat.md
- **Developed by:** [drop-in apis](https://apify.com/dropin-apis) (community)
- **Categories:** AI, Developer tools
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 comment analyzeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Perspective API alternative — comments:analyze drop-in

**Perspective API alternative** for `commentanalyzer.googleapis.com`: this Actor accepts `POST /v1alpha1/comments:analyze` and returns the same `attributeScores` JSON (TOXICITY, INSULT, THREAT, PROFANITY and the rest), so existing code keeps working after Google shuts Perspective down after 31 December 2026 ([Google's notice](https://perspectiveapi.com/)).

Last updated: 2026-10-02 · **Full migration guide:** https://alidaram99.github.io/api-alternatives/perspective-api-alternative/

Errors use the same Google error envelope, so your moderation code, thresholds and error handling keep working. You change the host and add your Apify token.

- ✅ Same request and response shape as Perspective `v1alpha1`. Google's own Python client (`googleapiclient.discovery.build`) works against it.
- ✅ TOXICITY, SEVERE\_TOXICITY, INSULT, PROFANITY, THREAT, IDENTITY\_ATTACK, SEXUALLY\_EXPLICIT
- ✅ English plus Spanish, French, Italian, Portuguese, Russian and Turkish, with automatic language detection
- ✅ About 10–30 ms of model time per comment; no rate limits or quota forms
- ✅ **$1 per 1,000 analyzed comments.** You pay only for successful analyses; errors are free.
- 🔒 We never store comment text. Scoring runs inside the Actor's container, and no third-party AI API is called.

### Who is this for?

- Forums, comment sections and community platforms (Discourse, Coral, custom CMS) that pre-screen comments with Perspective
- Discord, Twitch and Telegram moderation bots that call `comments:analyze`
- Researchers and LLM-evaluation pipelines (RealToxicityPrompts-style benchmarks) that need a TOXICITY score after Perspective is gone
- Anyone who tuned thresholds such as "flag above 0.85 TOXICITY" and doesn't want to rewrite code for a different API

### Migrate in one minute

Your endpoint is the Actor's Standby URL. You'll find it on the Actor's **Standby** tab, for example `https://<username>--perspective-compat.apify.actor`. Authenticate with your [Apify API token](https://console.apify.com/settings/integrations), either as `?token=…` or as an `Authorization: Bearer …` header. The `key=` parameter is accepted and ignored.

#### curl / any HTTP client

```bash
## Before
curl -X POST "https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze?key=$GOOGLE_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"comment":{"text":"You are an idiot"},"requestedAttributes":{"TOXICITY":{},"INSULT":{}},"languages":["en"]}'

## After: same body, same response
curl -s -X POST "https://<username>--perspective-compat.apify.actor/v1alpha1/comments:analyze?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"comment":{"text":"You are an idiot"},"requestedAttributes":{"TOXICITY":{},"INSULT":{}},"languages":["en"]}'
```

Response (exact shape Perspective returns):

```json
{
  "attributeScores": {
    "TOXICITY": {
      "spanScores": [{ "begin": 0, "end": 16, "score": { "value": 0.9963, "type": "PROBABILITY" } }],
      "summaryScore": { "value": 0.9963, "type": "PROBABILITY" }
    }
  },
  "languages": ["en"],
  "detectedLanguages": ["en"]
}
```

#### Python: Google's own client (`google-api-python-client`)

Keep your Perspective quickstart code. Point `discoveryServiceUrl` at the Actor and pass your Apify token as credentials:

```python
import os
from googleapiclient import discovery
from google.oauth2.credentials import Credentials

BASE = "https://<username>--perspective-compat.apify.actor"
APIFY_TOKEN = os.environ["APIFY_TOKEN"]

client = discovery.build(
    "commentanalyzer", "v1alpha1",
    credentials=Credentials(APIFY_TOKEN),  # was: developerKey=GOOGLE_API_KEY
    discoveryServiceUrl=BASE + "/$discovery/rest?version={apiVersion}&token=" + APIFY_TOKEN,
    static_discovery=False,
)
response = client.comments().analyze(body={
    "comment": {"text": "friendly greetings from python"},
    "requestedAttributes": {"TOXICITY": {}},
}).execute()
print(response["attributeScores"]["TOXICITY"]["summaryScore"]["value"])  # 0.01015937
```

#### Node.js: `fetch`

```js
const BASE = 'https://<username>--perspective-compat.apify.actor';
const res = await fetch(`${BASE}/v1alpha1/comments:analyze`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.APIFY_TOKEN}` },
  body: JSON.stringify({ comment: { text: 'You are an idiot' }, requestedAttributes: { TOXICITY: {} } }),
});
const data = await res.json();
console.log(data.attributeScores.TOXICITY.summaryScore.value); // 0.99683121
```

#### Node.js: `perspective-api-client` (npm)

The package hard-codes Google's URL and **bundles its own copy of axios** (0.18.x), so an interceptor on your app's `axios` never sees its requests. Attach the redirect to the axios instance the library itself loads, once at startup:

```js
const libAxios = require(require.resolve('axios', { paths: [require.resolve('perspective-api-client')] }));
const BASE = 'https://<username>--perspective-compat.apify.actor';

libAxios.interceptors.request.use((cfg) => {
  if (cfg.url && cfg.url.startsWith('https://commentanalyzer.googleapis.com')) {
    cfg.url = cfg.url.replace('https://commentanalyzer.googleapis.com', BASE);
    cfg.headers.Authorization = `Bearer ${process.env.APIFY_TOKEN}`;
  }
  return cfg;
});

// unchanged application code:
const Perspective = require('perspective-api-client');
const perspective = new Perspective({ apiKey: 'unused-but-required-by-the-library' });
perspective.analyze('You are a complete moron', { attributes: ['toxicity', 'insult'] })
  .then((r) => console.log(r.attributeScores.TOXICITY.summaryScore.value)); // 0.99700852
```

#### Discourse (`discourse-perspective-api` plugin)

The plugin hard-codes `GOOGLE_API_DOMAIN = "https://commentanalyzer.googleapis.com"` in `lib/discourse_perspective.rb`. In your fork, change that constant to your Standby URL and append `token=<APIFY_TOKEN>` to the query it sends. The rest of the plugin, including the `TOXICITY` and `SEVERE_TOXICITY` models and the thresholds, works unchanged.

### Supported attributes and languages

| Attribute | en | es fr it pt ru tr | Notes |
|---|---|---|---|
| TOXICITY | ✅ | ✅ | |
| SEVERE\_TOXICITY | ✅ | ✅ | |
| INSULT | ✅ | ✅ | |
| PROFANITY | ✅ | ✅ | Approximated from the Detoxify "obscene" head |
| THREAT | ✅ | ✅ | |
| IDENTITY\_ATTACK | ✅ | ✅ | |
| SEXUALLY\_EXPLICIT | ✅ | — | English only, as in Perspective |
| `*_EXPERIMENTAL` aliases (e.g. `TOXICITY_EXPERIMENTAL`) | ✅ | ✅ | Served by the matching base attribute and returned under the name you requested |
| FLIRTATION and the NYT models (ATTACK\_ON\_AUTHOR, INCOHERENT, INFLAMMATORY, LIKELY\_TO\_REJECT, OBSCENE, SPAM, UNSUBSTANTIAL), bridging attributes | ❌ | ❌ | Returns Perspective's own `LANGUAGE_NOT_SUPPORTED_BY_ATTRIBUTE` error, which existing clients already handle |

If you leave out `languages`, the language is detected automatically. For any other language you get Perspective's standard error, so your existing fallback logic keeps working:

```json
{"error":{"code":400,"message":"Attribute TOXICITY does not support request languages: fi","status":"INVALID_ARGUMENT",
 "details":[{"@type":"type.googleapis.com/google.commentanalyzer.v1alpha1.Error","errorType":"LANGUAGE_NOT_SUPPORTED_BY_ATTRIBUTE",
 "languageNotSupportedByAttributeError":{"detectedLanguages":["fi"],"attribute":"TOXICITY"}}]}}
```

### Request fields supported

`comment.text`, `requestedAttributes{…: {scoreType, scoreThreshold}}`, `languages`, `spanAnnotations` (per-sentence `spanScores`), `clientToken` (echoed back), `doNotStore`, `sessionId` and `communityId`. The last three are accepted, and nothing is ever stored. `context` is accepted and ignored. Both camelCase and snake\_case work. The maximum is 20 KB of text per comment, the same as Perspective.

Only `scoreType: "PROBABILITY"` is supported. Other score types return Perspective's "Requested score type … is not supported" error. `comments:suggestscore` is accepted and acknowledged, but feedback is not stored.

### How it compares

| Option | Same `comments:analyze` request and JSON? | Price | Notes |
|---|---|---|---|
| **This Actor** | Yes: change the host, add an Apify token | $1 per 1,000 comments | Open Detoxify models; scores are close to Perspective's, not identical |
| Google Perspective API | It is the original | Free | Stops after 31 Dec 2026; no migration tool |
| OpenAI Moderation API | No: different request, categories and scores | Free (rate-limited) | Rewrite parsing and re-tune thresholds |
| Azure AI Content Safety | No: severity levels for Hate/Sexual/SelfHarm/Violence | Paid per text record | Different URL, auth and JSON |
| Other APIs marketed as "Perspective alternatives" | Usually a different request/response shape | Varies | Check whether your client code has to change |

### Pricing

**$1.00 per 1,000 analyzed comments** (pay-per-event, `comment-analyzed`). Failed requests (4xx errors) are not charged. Apify also adds its standard small per-start fee to each run. In Standby mode you don't pay for idle time. Set a maximum total charge on the run, and once it is reached the API returns `429 RESOURCE_EXHAUSTED`.

For comparison: Perspective was free until it shut down. Paid moderation APIs charge from about $0.38 to $2 per 1,000 texts, and none of them use Perspective's request and response format.

### Batch mode (no code)

Prefer a spreadsheet? Run the Actor normally with a list of `comments`. You get one dataset row per comment, with a column per attribute, and can export it as CSV, Excel or JSON.

```json
{ "comments": ["Thanks, this helped!", "You are an idiot."], "requestedAttributes": ["TOXICITY", "INSULT"] }
```

### Accuracy: read before switching thresholds

The scores come from the open-source [Detoxify](https://github.com/unitaryai/detoxify) models, which were trained on the same Jigsaw/Civil Comments data that Perspective was built on. They run as quantized ONNX:

- English: `unbiased-toxic-roberta`
- Other languages: `multilingual-toxic-xlm-roberta`

Scores are well calibrated probabilities, but **they are not identical to Perspective's numbers.** Clear-cut toxic text scores 0.95–0.99 and benign text below 0.05, while borderline comments can differ by ±0.1–0.2. If you used a high cut-off such as 0.85–0.9, it usually carries over. If you auto-reject at a low threshold, re-check it on a sample of your own comments first. Batch mode makes that easy.

Comments longer than about 512 tokens are scored sentence by sentence in chunks. The summary is the highest chunk score, so a toxic sentence inside a long post is still caught.

### Latency and cold start

On a warm instance, the model scores a comment in about 10–30 ms, and the API usually answers in under 100 ms including network. After about 5 minutes without requests the instance sleeps, and the next request waits a few seconds while it starts. The first non-English request after a start also loads the multilingual model, which takes about 1–2 s. If you moderate live chat synchronously, send a request every few minutes to keep the instance warm, or score asynchronously.

### FAQ

#### What is a Perspective API alternative that keeps comments:analyze?

This Actor. It serves the same `POST /v1alpha1/comments:analyze` path and returns the same JSON; you authenticate with an Apify token instead of a Google key. $1 per 1,000 successful calls.

#### When does Google shut down the Perspective API?

After 31 December 2026. Google's own page says there is no migration path: https://perspectiveapi.com/

#### Are the toxicity scores identical to Google's?

No. They come from open Detoxify models trained on the same Jigsaw/Civil Comments data family. Clear cases agree; borderline comments can differ. Re-check any threshold you tuned on Google's numbers, for example with batch mode.

#### Which languages does the toxicity API support?

English, Spanish, French, Italian, Portuguese, Russian and Turkish. SEXUALLY\_EXPLICIT is English only, as in Perspective. Other languages return Perspective's own `LANGUAGE_NOT_SUPPORTED_BY_ATTRIBUTE` error.

#### Does it work with google-api-python-client?

Yes. Set `discoveryServiceUrl` to this Actor's Standby URL and pass your Apify token as credentials (see the Python example above). The request body stays the same.

#### How much does the Perspective API alternative cost?

$0.001 per analyzed comment ($1 per 1,000). Errors are not charged. Apify also charges its standard Actor start event ($0.00005), shown on the Pricing tab.

#### Can I keep using `?key=`?

The `key` parameter is accepted and ignored. Authentication happens through your Apify token (`?token=` or an `Authorization: Bearer` header).

#### Do you store my comments?

No. Text is scored in memory and discarded. The Standby API writes nothing to disk. In batch mode, results are saved only to your own run's dataset.

#### Is there a rate limit?

There is no Perspective-style QPS quota. Throughput scales with Apify Standby instances.

#### Is this affiliated with Google or Jigsaw?

No. It is an independent, compatible implementation of the public `commentanalyzer` v1alpha1 interface. If something doesn't match Perspective's behaviour, open an issue on this Actor's Issues tab with the request body.

# Actor input Schema

## `comments` (type: `array`):

Comment texts to score (plain text, up to 20 KB each). One dataset item and one 'comment-analyzed' event per successfully scored comment.

## `requestedAttributes` (type: `array`):

Perspective attribute names to score: TOXICITY, SEVERE\_TOXICITY, IDENTITY\_ATTACK, INSULT, PROFANITY, THREAT, SEXUALLY\_EXPLICIT (English only). \*\_EXPERIMENTAL aliases are accepted.

## `languages` (type: `array`):

ISO 639-1 codes (en, es, fr, it, pt, ru, tr). Leave empty to auto-detect per comment, like Perspective.

## Actor input object example

```json
{
  "comments": [
    "Thanks for the detailed answer, this helped a lot!",
    "You are an idiot and nobody wants you here."
  ],
  "requestedAttributes": [
    "TOXICITY",
    "INSULT"
  ]
}
```

# Actor output Schema

## `results` (type: `string`):

All items in the run's default dataset.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "comments": [
        "Thanks for the detailed answer, this helped a lot!",
        "You are an idiot and nobody wants you here."
    ],
    "requestedAttributes": [
        "TOXICITY",
        "INSULT"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("dropin-apis/perspective-compat").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "comments": [
        "Thanks for the detailed answer, this helped a lot!",
        "You are an idiot and nobody wants you here.",
    ],
    "requestedAttributes": [
        "TOXICITY",
        "INSULT",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("dropin-apis/perspective-compat").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "comments": [
    "Thanks for the detailed answer, this helped a lot!",
    "You are an idiot and nobody wants you here."
  ],
  "requestedAttributes": [
    "TOXICITY",
    "INSULT"
  ]
}' |
apify call dropin-apis/perspective-compat --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dropin-apis/perspective-compat"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/BZPw57vtyNNXx1BXP/builds/T68pFWkHr0Z6e0sjx/openapi.json
