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.
POST https://layahost.com/v1alpha1/comments:analyze
POST https://layahost.com/v1alpha1/comments:suggestscore
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.
Migrate
- Create an account and create a key on the API keys page.
- Replace
commentanalyzer.googleapis.comwithlayahost.comand your Google API key with the layahost key. The key can stay in?key=;X-Goog-Api-KeyandAuthorization: Bearerwork too.
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"])
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
}'
// 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);
{
"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. |
Perspective's other experimental attributes (FLIRTATION, ATTACK_ON_AUTHOR and the rest) answer 400. For other decisions, such as spam or your own categories, use POST /v1/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. |
Accuracy
Every score is P(attribute) from Laya, calibrated against the 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.
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: other languages are scored, but these numbers are for English.
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:
{
"error": {
"code": 400,
"message": "Unknown attribute: FLIRTATION. 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 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. |