# Perspective API replacement

Google is shutting down the Perspective API on 31 December 2026. layahost implements the same `comments:analyze` method, request and response, so existing code keeps working after you change the host and the API key.

Endpoints

```http
POST https://layahost.com/v1alpha1/comments:analyze
POST https://layahost.com/v1alpha1/comments:suggestscore
POST https://layahost.com/batch
GET  https://layahost.com/$discovery/rest?version=v1alpha1
```

layahost is not affiliated with Google or Jigsaw. The scores come from a different model, the open Laya model, calibrated so that they read like Perspective's. Check your thresholds on your own comments before you switch; see [Accuracy](#accuracy).

## Migrate

Using Coral, HELM, lm-evaluation-harness or another tool with Perspective built in? See [Perspective integrations](https://layahost.com/docs/perspective-integrations) for the exact setting.

1. [Create an account](https://layahost.com/register) and create a key on the [API keys](https://layahost.com/keys) page.
2. Replace `commentanalyzer.googleapis.com` with `layahost.com` and your Google API key with the layahost key. The key can stay in `?key=`; `X-Goog-Api-Key` and `Authorization: Bearer` work too.

comments:analyze

```python
from googleapiclient import discovery

client = discovery.build(
    "commentanalyzer",
    "v1alpha1",
    developerKey=LAYAHOST_API_KEY,
    # was: https://commentanalyzer.googleapis.com/$discovery/rest?version=v1alpha1
    discoveryServiceUrl="https://layahost.com/$discovery/rest?version=v1alpha1",
    static_discovery=False,
)

response = client.comments().analyze(body={
    "comment": {"text": "You are an idiot and nobody wants you here."},
    "requestedAttributes": {"TOXICITY": {}, "INSULT": {}},
}).execute()

print(response["attributeScores"]["TOXICITY"]["summaryScore"]["value"])
```

comments:analyze

```bash
curl "https://layahost.com/v1alpha1/comments:analyze?key=$LAYAHOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "comment": {"text": "You are an idiot and nobody wants you here."},
    "requestedAttributes": {"TOXICITY": {}, "INSULT": {}},
    "doNotStore": true
  }'
```

comments:analyze

```javascript
// was: https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze
const res = await fetch(
  `https://layahost.com/v1alpha1/comments:analyze?key=${process.env.LAYAHOST_API_KEY}`,
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      comment: { text: "You are an idiot and nobody wants you here." },
      requestedAttributes: { TOXICITY: {}, INSULT: {} },
    }),
  },
);

const data = await res.json();
console.log(data.attributeScores.TOXICITY.summaryScore.value);
```

Response · 200

```json
{
  "attributeScores": {
    "TOXICITY": {
      "spanScores": [{ "begin": 0, "end": 43, "score": { "value": 0.90833, "type": "PROBABILITY" } }],
      "summaryScore": { "value": 0.90833, "type": "PROBABILITY" }
    },
    "INSULT": {
      "spanScores": [{ "begin": 0, "end": 43, "score": { "value": 0.91625, "type": "PROBABILITY" } }],
      "summaryScore": { "value": 0.91625, "type": "PROBABILITY" }
    }
  },
  "languages": ["en"],
  "detectedLanguages": ["en"]
}
```

## Attributes

| Attribute | Meaning |
| --- | --- |
| `TOXICITY` | Rude, disrespectful or unreasonable, likely to make people leave a discussion. |
| `SEVERE_TOXICITY` | Very hateful, aggressive or abusive. Much less sensitive than TOXICITY to mild rudeness or casual swearing. The least accurate attribute; see Accuracy. |
| `IDENTITY_ATTACK` | Negative or hateful towards people because of their identity, such as race, religion, gender or sexual orientation. |
| `INSULT` | Insulting, inflammatory or demeaning towards a person or a group of people. |
| `PROFANITY` | Swear words, curse words or other obscene language. |
| `THREAT` | An intention to inflict pain, injury or violence on a person or group. |
| `SEXUALLY_EXPLICIT` | References to sexual acts, sexual body parts or other lewd content. |

The `_EXPERIMENTAL` names of these attributes, such as `TOXICITY_EXPERIMENTAL`, are answered by the same attribute. Perspective attributes we don't score (`FLIRTATION`, `ATTACK_ON_AUTHOR`, `SPAM`, the bridging attributes and the rest) are left out of `attributeScores` and not billed, so clients that request them by default keep working. A name Perspective never had gets `400`. For other decisions, such as spam or your own categories, use [POST /v1/decide](https://layahost.com/docs/decide).

## What is supported

| Field | Behaviour |
| --- | --- |
| `comment.text`, `comment.type` | Up to 32,000 characters. `HTML` is reduced to its text before scoring. |
| `requestedAttributes` | Any of the attributes above. `scoreThreshold` leaves out scores below it. `scoreType` must be `PROBABILITY` or unset. |
| `languages` | Used as a hint. Without it, the language is detected and returned in `detectedLanguages`. |
| `doNotStore` | Honoured. We never store comment text by default anyway; with `doNotStore` it is not stored even if you turned on text logging. |
| `clientToken` | Echoed back. |
| `spanScores` | One span covering the whole comment, with the same score as `summaryScore`. |
| `context`, `communityId`, `sessionId`, `spanAnnotations` | Accepted and ignored. |
| `comments:suggestscore` | Accepted so feedback code keeps working. Free, and nothing is stored. |
| Batches | `POST /batch` speaks Google's batch protocol, so `new_batch_http_request()` works. Up to 100 requests per batch, each authenticated and billed on its own. |

## Accuracy

Every score is `P(attribute)` from Laya, calibrated against the [Civil Comments](https://huggingface.co/datasets/google/civil_comments) dataset, where each label is the share of human raters who marked the comment, the same meaning as a Perspective probability. A score of 0.8 means that about 8 in 10 raters would call the comment toxic.

Held-out results on 580 comments from the Civil Comments test split, never used for tuning (2026-09-25):

| Attribute | ROC AUC | Rank correlation | Mean abs. error |
| --- | --- | --- | --- |
| `TOXICITY` | 0.92 | 0.77 | 0.09 |
| `SEVERE_TOXICITY` | 0.57 | 0.34 | 0.03 |
| `IDENTITY_ATTACK` | 0.93 | 0.50 | 0.04 |
| `INSULT` | 0.95 | 0.79 | 0.07 |
| `PROFANITY` | 0.94 | 0.54 | 0.03 |
| `THREAT` | 0.98 | 0.52 | 0.03 |
| `SEXUALLY_EXPLICIT` | 0.88 | 0.42 | 0.02 |

ROC AUC is the chance that a comment raters flagged scores higher than one they did not (1.0 is perfect, 0.5 is a coin flip). Mean absolute error is the average distance between our score and the share of raters, on the natural mix of comments.

**`SEVERE_TOXICITY` is barely better than chance** at telling very hateful comments from merely toxic ones. It is there so requests that ask for it keep working; don't base decisions on it. Use `TOXICITY` together with `INSULT`, `THREAT` and `IDENTITY_ATTACK` instead.

`PROFANITY` combines the model with a list of common swear words: a match sets the score to at least 0.391, the average share of raters who called such comments obscene. Raters were conservative here, so a single swear word scores well below 0.5.

### Other languages

Text that isn't English goes to Laya's multilingual checkpoint, with a separate calibration for `TOXICITY` and `INSULT`. On a held-out sample of the [TextDetox](https://huggingface.co/datasets/textdetox/multilingual_toxicity_dataset) dataset (300 comments per language, half of them toxic), `TOXICITY` gives:

| Language | ROC AUC | Toxic caught at 0.5 | Clean flagged at 0.5 | Toxic caught at 0.7 | Clean flagged at 0.7 |
| --- | --- | --- | --- | --- | --- |
| English | 1.00 | 83% | 0% | 57% | 0% |
| German | 0.85 | 37% | 5% | 23% | 3% |
| Spanish | 0.91 | 63% | 5% | 45% | 3% |
| French | 0.93 | 71% | 3% | 51% | 1% |
| Italian | 0.82 | 63% | 13% | 44% | 7% |

English is clearly the strongest. French, Spanish and Italian work with a lower threshold than you would use in English; German misses many toxic comments. Italian flags more clean comments than the others. The other attributes are calibrated for the multilingual checkpoint too, but only measured in English. For a site in another language, check a threshold on a few hundred of its own comments first.

Perspective was trained on this kind of data and scores better on it. Expect different numbers for the same comment, and a different best threshold. Before switching, run a sample of your own comments through both and pick thresholds from ours. The model is English-first; see [other languages](#languages).

## Pricing

Each requested attribute is one decision, $5 per million. A comment scored for `TOXICITY` alone costs $0.000005; all 7 attributes cost $0.000035. Failed requests and `suggestscore` are free. New accounts get $1 of credit when they verify their email.

## Errors

Errors use Google's shape, so `googleapiclient` raises its usual `HttpError`:

Response · 400

```json
{
  "error": {
    "code": 400,
    "message": "Unknown attribute: TOXCITY. Supported: TOXICITY, SEVERE_TOXICITY, IDENTITY_ATTACK, INSULT, PROFANITY, THREAT, SEXUALLY_EXPLICIT.",
    "status": "INVALID_ARGUMENT"
  }
}
```

| Status | When |
| --- | --- |
| `400 INVALID_ARGUMENT` | Invalid body, unknown attribute, or an invalid API key. |
| `401 UNAUTHENTICATED` | No API key. |
| `402 FAILED_PRECONDITION` | Your balance is too low. Top up on the [Billing](https://layahost.com/billing) page. |
| `429 RESOURCE_EXHAUSTED` | Rate limit (600 requests per minute per key) or a busy backend. Retry after `retry-after` seconds. |
| `503 UNAVAILABLE` | Backend unavailable. Retry shortly. |
