Comment Health
Every community draws its lines in a different place. A gaming forum may be comfortable with casual swearing that a children's education site would never accept; a debate club may want every logical fallacy pointed out, while a support group may not. Comment health lets you tell Respectify how much of each kind of problem your site accepts, and how good a comment has to be, so that comment scoring reflects your community rather than a single, one-size-fits-all standard.
Comment health is made of ten items: nine kinds of problem, plus the comment's quality. You give each item a level in the tolerances parameter of each request. Every comment score then tells you how the comment scored on each item, the lowest score each item accepts, and a single verdict, exceeds_tolerance, for whether the comment is beyond what your site accepts.
Tolerances are request parameters only. Nothing is saved on your API key, so send the levels you want with every request. Any item you leave out is standard.
Tolerances apply to comment scoring: the commentscore endpoint, and the comment score part of megacall. Spam, relevance (off-topic and banned topics) and dogwhistle are separate checks, not health items, and tolerances do not affect them.
Health Items
| Item | What it covers |
|---|---|
threat | Threatening or wishing violence or harm on someone, or urging self-harm. Floored. |
identity_attack | Hostility toward people for their race, ethnicity, nationality, religion, gender, sexual orientation or disability, including slurs. Floored. |
sexual_content | Explicit or crude sexual language, or sexual remarks about a person. Floored. |
harassment | Targeted or sustained demeaning of a person, or othering them ("you don't belong here"). Floored. |
insult | A one-off insult or put-down of a person. |
profanity | Swearing or crude language that is not aimed at an identity and is not sexual. |
dismissive_tone | Sarcasm or dismissal toward someone in the discussion, instead of engaging with them. |
logical_fallacy | Logical fallacies. |
low_effort | Empty posts such as "me too" or "+1". |
quality | The comment's overall quality: how well it engages with the topic and contributes to the conversation, on a 1-5 rubric. |
The first nine are problem categories. Each objectionable phrase and negative tone phrase in a comment score is assigned one of the first seven: the most serious one that fits. A slur, for example, is always identity_attack, never profanity. Logical fallacies and low effort are judged on the comment as a whole, so they have their own categories and are not assigned to phrases.
Floored categories
threat, identity_attack, sexual_content and harassment are floored: they can be made stricter, but never relaxed or turned off. Their only levels are standard, strict and zero_tolerance, and at every one of them they accept no finding at all: even a minor threat or slur fails. These cover content that causes real harm to real people, and Respectify will not let any site switch off reporting of it.
Setting a floored category to permissive, relaxed or off is rejected with HTTP 400 (see Errors). The client libraries check this before sending.
Levels
There are six levels, from loosest to strictest: off, permissive, relaxed, standard, strict and zero_tolerance. For a problem category, a level says which findings are accepted; for quality, it sets the lowest quality score accepted.
| Level | Problem category | Quality |
|---|---|---|
off | Not scored, not coached, not counted. Its findings are removed from the response. | Not scored. |
permissive | Filters only the very worst: accepts minor, clear and severe findings; only extreme ones fail. | Accepts 2 and above. |
relaxed | Accepts minor and clear findings; severe and extreme ones fail. | Accepts 2 and above. |
standard | The default. Accepts minor findings; clear, severe and extreme ones fail. | Accepts 3 and above. |
strict | Accepts no findings. | Accepts 4 and above. |
zero_tolerance | Accepts no findings, and any hint of the problem sends the comment for a full assessment (see First-pass screening). | Accepts only 5. |
off, permissive and relaxed are not available for the floored categories.
Quality has only four distinct minimums, so permissive and relaxed both accept a quality of 2 and above.
Item scores
Each item that is not off gets a score from 1 to 5, measured from the comment, where 5 is best:
- A problem category scores 5 when nothing of that kind was found. Otherwise its worst finding sets the score: a minor finding (severity 1) scores 4, a clear finding (severity 2) scores 3, a severe finding (severity 3) scores 2, and an extreme finding (severity 4) scores 1. A comment that
appears_low_effortcounts as a clearlow_effortfinding. - Quality scores the comment on Respectify's 1-5 quality rubric. A 3 is a normal, good-quality comment; 4 and 5 are exceptional.
Each level sets the item's minimum: the lowest score it accepts. An item is acceptable when its score is at or above its minimum.
| Level | Problem category minimum | Floored category minimum | Quality minimum |
|---|---|---|---|
permissive | 2 | (not allowed) | 2 |
relaxed | 3 | (not allowed) | 2 |
standard | 4 | 5 | 3 |
strict | 5 | 5 | 4 |
zero_tolerance | 5 | 5 | 5 |
Item scores measure the comment whatever your levels are. A finding your level accepts still lowers its item's score; it just does not make the item fail.
Severity
Every finding Respectify reports carries a severity:
| Severity | Meaning | Examples |
|---|---|---|
1 | Minor: a mild jab, a stray swear word, a fallacy that is incidental to the point | "that's a silly take, mate", "this is a damn mess" |
2 | Clear: an unmistakable insult or put-down, crude language, a fallacy the point depends on | "you're an idiot" |
3 | Severe: a slur or dehumanising language, a threat, sexual harassment, urging self-harm, sustained abuse | |
4 | Extreme: the same at full force, such as a slur combined with a threat or a call for violence, a credible threat against a named person, or graphic sexual abuse |
Respectify rates severity after deciding what to report, so severity never changes whether something is found; your levels then decide which findings are accepted.
Reading the Response
A comment score includes, alongside the findings:
health_items: an object with one entry for each item that is notoff, by item name. Each entry is a HealthItemScore:score(1-5),minimum(1-5, the lowest score its level accepts) andis_acceptable(whetherscoreis at or aboveminimum).health_score: comment health as a number from 1.0 to 5.0 (see below). It is left out of the response, not set tonull, when every item isoff.overall_score: comment health as a whole number from 1 to 5:health_score, rounded half up.exceeds_tolerance: whether the comment is beyond what your site accepts. This is the verdict to act on.tolerances: the level each of the ten items was judged under.- On each finding, a
severity; on each objectionable phrase and negative tone phrase, acategory.
Findings your levels accept are removed before the response is returned, so every finding in the response is one there is something to coach. For low_effort, appears_low_effort is reported as false when the level accepts it.
health_score and overall_score
health_score is the geometric mean of two numbers: the quality score, and the score of the worst problem item. Taking the worst problem, rather than averaging all nine, stops one severe problem from being diluted by eight clean categories, while quality still counts. Items that are off are left out: with quality off, health is the worst problem item alone; with every problem category off, it is quality alone. health_score is given to two decimal places.
overall_score is health_score rounded half up, so it is comment health, not the raw quality score. For the quality score alone, read health_items.quality.score. If every item is off, there is no health_score, and overall_score is the quality rubric's score.
For example, a comment with quality 3 and a minor insult (insult score 4) has a health_score of √(3 × 4) ≈ 3.46 and an overall_score of 3. The same comment with a severe insult (score 2) has a health_score of √(3 × 2) ≈ 2.45 and an overall_score of 2, and the same comment with an extreme threat instead (threat score 1) has √(3 × 1) ≈ 1.73 and an overall_score of 2.
Responses from older Respectify versions do not include health_items or health_score, and their overall_score is the quality score. Treat both fields as optional.
exceeds_tolerance
exceeds_tolerance is the site-specific verdict, and it is what your integration should act on when deciding whether to coach the commenter, ask them to revise, or hold the comment. It is true when either:
- any item in
health_itemshasis_acceptableset tofalse; or - the comment is toxic:
toxicity_scoreis 0.7 or higher, or it is 0.5 or higher and none of the comment's findings was accepted by your levels.
toxicity_score is never adjusted
toxicity_score is Respectify's own judgment of the comment and is the same whatever your levels are. If you relax profanity, a sweary but otherwise harmless comment can still have a moderate toxicity score, and deciding on toxicity_score alone would undo your choice.
That is why exceeds_tolerance treats toxicity carefully:
- A toxicity score of 0.7 or higher always exceeds tolerance, whatever your levels. Relaxing insults never lets a severely harmful comment through.
- A score from 0.5 up to 0.7 exceeds tolerance only when none of the comment's findings was accepted, because otherwise the toxicity may come from the very findings your site chose to accept.
Use exceeds_tolerance, not toxicity_score or overall_score alone, to decide what to do with a comment. Use health_items to show which items failed, and toxicity_score as an additional signal, for example to choose between asking for a revision and blocking.
Setting Tolerances
Send tolerances with each request: an object mapping item names to levels. Only the items you name change; every other item is standard.
- TypeScript
- Python
- PHP
- REST API
import type { ModerationTolerances } from "@respectify/client";
const tolerances: ModerationTolerances = {
profanity: "relaxed",
low_effort: "off",
quality: "strict",
threat: "zero_tolerance",
};
const score = await client.evaluateComment(
"This is a damn good point, honestly",
articleId,
undefined, // replyToComment
tolerances,
);
if (score.exceeds_tolerance) {
// Show the commenter what to improve and invite them to revise
for (const phrase of score.objectionable_phrases) {
console.log(`${phrase.category} (severity ${phrase.severity}): ${phrase.quoted_objectionable_phrase}`);
}
}
// Per-item results; optional, since older servers do not send them
for (const [item, result] of Object.entries(score.health_items ?? {})) {
console.log(`${item}: ${result.score} (minimum ${result.minimum}) ${result.is_acceptable ? "ok" : "fails"}`);
}
The ModerationTolerances type rejects permissive, relaxed and off for a floored category at compile time, and the client throws BadRequestError before sending if the tolerances are invalid. validateModerationTolerances() runs the same check on its own, for example on tolerances loaded from configuration.
- Blocking Client
- Async Client
from respectify import ModerationTolerances, ToleranceLevel
tolerances = ModerationTolerances(
profanity=ToleranceLevel.RELAXED,
low_effort=ToleranceLevel.OFF,
quality=ToleranceLevel.STRICT,
threat=ToleranceLevel.ZERO_TOLERANCE,
)
score = client.evaluate_comment(
"This is a damn good point, honestly",
article_id,
tolerances=tolerances,
)
if score.exceeds_tolerance:
# Show the commenter what to improve and invite them to revise
for phrase in score.objectionable_phrases:
print(f"{phrase.category.value} (severity {phrase.severity}): {phrase.quoted_objectionable_phrase}")
for item, result in score.health_items.items():
print(f"{item}: {result.score} (minimum {result.minimum}) {'ok' if result.is_acceptable else 'fails'}")
print(score.health_score) # None when every item is off
from respectify import ModerationTolerances, ToleranceLevel
tolerances = ModerationTolerances(
profanity=ToleranceLevel.RELAXED,
low_effort=ToleranceLevel.OFF,
quality=ToleranceLevel.STRICT,
threat=ToleranceLevel.ZERO_TOLERANCE,
)
score = await client.evaluate_comment(
"This is a damn good point, honestly",
article_id,
tolerances=tolerances,
)
if score.exceeds_tolerance:
# Show the commenter what to improve and invite them to revise
for phrase in score.objectionable_phrases:
print(f"{phrase.category.value} (severity {phrase.severity}): {phrase.quoted_objectionable_phrase}")
for item, result in score.health_items.items():
print(f"{item}: {result.score} (minimum {result.minimum}) {'ok' if result.is_acceptable else 'fails'}")
print(score.health_score) # None when every item is off
Only the items you pass to ModerationTolerances(...) are sent; the server treats the rest as standard. ModerationTolerances validates when it is constructed, so ModerationTolerances(threat=ToleranceLevel.OFF) raises Pydantic's ValidationError and nothing is sent.
use Respectify\Moderation\ModerationCategory;
use Respectify\Moderation\ModerationTolerances;
use Respectify\Moderation\ToleranceLevel;
$tolerances = new ModerationTolerances([
ModerationCategory::PROFANITY => ToleranceLevel::RELAXED,
ModerationCategory::LOW_EFFORT => ToleranceLevel::OFF,
'quality' => ToleranceLevel::STRICT,
ModerationCategory::THREAT => ToleranceLevel::ZERO_TOLERANCE,
]);
$client->evaluateComment(
$articleId,
"This is a damn good point, honestly",
null, // reply_to_comment
$tolerances
)->then(function ($score) {
if ($score->exceedsTolerance) {
// Show the commenter what to improve and invite them to revise
foreach ($score->objectionablePhrases as $phrase) {
echo $phrase->category . ' (severity ' . $phrase->severity . '): '
. $phrase->quotedObjectionablePhrase . "\n";
}
}
print_r($score->tolerances->allLevels()); // all ten items, as judged
});
$client->run();
ModerationTolerances validates in its constructor and throws BadRequestException for an unknown item or level, or permissive, relaxed or off on a floored category, so an invalid request is never sent.
commentscore also accepts form-encoded requests, but tolerances is an object, so send the request as JSON to include it. A form-encoded request is judged with every item at standard.
curl -X POST https://app.respectify.ai/v0.2/commentscore \
-H "X-User-Email: your@email.com" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"article_context_id": "550e8400-e29b-41d4-a716-446655440000",
"comment": "This is a damn good point, honestly",
"tolerances": {
"profanity": "relaxed",
"low_effort": "off",
"quality": "strict",
"threat": "zero_tolerance"
}
}'
Here the comment's minor swear word is accepted (and so removed from objectionable_phrases), but its quality of 3 is below the strict minimum of 4, so it exceeds tolerance. low_effort is off, so it has no entry in health_items:
{
"logical_fallacies": [],
"objectionable_phrases": [],
"negative_tone_phrases": [],
"appears_low_effort": false,
"overall_score": 3,
"toxicity_score": 0.05,
"toxicity_explanation": "",
"exceeds_tolerance": true,
"health_score": 3.46,
"health_items": {
"threat": { "score": 5, "minimum": 5, "is_acceptable": true },
"identity_attack": { "score": 5, "minimum": 5, "is_acceptable": true },
"sexual_content": { "score": 5, "minimum": 5, "is_acceptable": true },
"harassment": { "score": 5, "minimum": 5, "is_acceptable": true },
"insult": { "score": 5, "minimum": 4, "is_acceptable": true },
"profanity": { "score": 4, "minimum": 3, "is_acceptable": true },
"dismissive_tone": { "score": 5, "minimum": 4, "is_acceptable": true },
"logical_fallacy": { "score": 5, "minimum": 4, "is_acceptable": true },
"quality": { "score": 3, "minimum": 4, "is_acceptable": false }
},
"tolerances": {
"threat": "zero_tolerance",
"identity_attack": "standard",
"sexual_content": "standard",
"harassment": "standard",
"insult": "standard",
"profanity": "relaxed",
"dismissive_tone": "standard",
"logical_fallacy": "standard",
"low_effort": "off",
"quality": "strict"
}
}
With Megacall
Megacall takes the same tolerances, applied to its comment score. They are used only when the comment score is requested; for other checks they have no effect.
- TypeScript
- Python
- PHP
- REST API
const result = await client.megacall({
comment: "This is a damn good point, honestly",
articleId: articleId,
includeSpam: true,
includeCommentScore: true,
tolerances: { profanity: "relaxed", quality: "strict" },
});
if (result.comment_score?.exceeds_tolerance) {
// Coach the commenter
}
- Blocking Client
- Async Client
from respectify import ModerationTolerances, ToleranceLevel
result = client.megacall(
"This is a damn good point, honestly",
article_id,
include_spam=True,
include_comment_score=True,
tolerances=ModerationTolerances(profanity=ToleranceLevel.RELAXED, quality=ToleranceLevel.STRICT),
)
if result.comment_score and result.comment_score.exceeds_tolerance:
... # Coach the commenter
from respectify import ModerationTolerances, ToleranceLevel
result = await client.megacall(
"This is a damn good point, honestly",
article_id,
include_spam=True,
include_comment_score=True,
tolerances=ModerationTolerances(profanity=ToleranceLevel.RELAXED, quality=ToleranceLevel.STRICT),
)
if result.comment_score and result.comment_score.exceeds_tolerance:
... # Coach the commenter
use Respectify\Moderation\ModerationCategory;
use Respectify\Moderation\ModerationTolerances;
use Respectify\Moderation\ToleranceLevel;
$tolerances = new ModerationTolerances([
ModerationCategory::PROFANITY => ToleranceLevel::RELAXED,
'quality' => ToleranceLevel::STRICT,
]);
$client->megacall(
"This is a damn good point, honestly",
$articleId,
['spam', 'commentscore'],
null, // banned topics
null, // reply_to_comment
null, // sensitive topics
null, // dogwhistle examples
$tolerances
)->then(function ($result) {
if ($result->commentScore && $result->commentScore->exceedsTolerance) {
// Coach the commenter
}
});
$client->run();
curl -X POST https://app.respectify.ai/v0.2/megacall \
-H "X-User-Email: your@email.com" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"comment": "This is a damn good point, honestly",
"article_context_id": "550e8400-e29b-41d4-a716-446655440000",
"run_spam_check": true,
"run_comment_score": true,
"tolerances": { "profanity": "relaxed", "quality": "strict" }
}'
First-Pass Screening
Every comment score starts with a fast first-pass screen. A comment the screen is confident has nothing your levels would fail is answered straight from the screen, without the full assessment; its finding lists and toxicity_explanation are empty, since there is nothing to explain. A comment with a possible problem, or one the screen is unsure about, gets the full assessment, which quotes what is wrong and suggests improvements.
Your levels move the screen too:
- An item that is
offis not screened. permissiveandrelaxedlet more comments through on the screen alone.strictsends borderline comments for the full assessment.zero_tolerancesends any hint of the problem for the full assessment. This is the only difference betweenstrictandzero_tolerance: both accept no findings.
The same screen runs first for spam, relevance and dogwhistle; see Megacall.
Errors
Invalid tolerances are rejected with HTTP 400 and nothing is evaluated. The message says what was wrong, naming only Respectify's own items and levels (it never repeats what you sent):
{
"error": "Invalid Parameter",
"message": "Invalid tolerances: These categories can be made stricter but not relaxed or turned off: threat",
"code": 400
}
A 400 is returned when:
- a floored category (
threat,identity_attack,sexual_content,harassment) is set topermissive,relaxedoroff; - an item is not one of the ten listed above;
- a level is not one of
off,permissive,relaxed,standard,strict,zero_tolerance; tolerancesis not a JSON object.
The client libraries run the same checks before sending, as described in each language tab above. See Error Handling for how 400s surface in each library.
Changes from Moderation Tolerances
This page replaces the earlier Moderation Tolerances page. If you integrated against that version:
- Nothing is saved per API key. The dashboard's per-key tolerances are gone, and a request's
tolerancesare no longer merged over saved levels. Send the levels you want with every request. qualityandoffare new.qualityreplaces a client-side minimum overall score;offremoves an item from comment health entirely.standardnow accepts minor findings, andstrictaccepts none, so the levels change which findings are reported, not only how comments are screened.overall_scoreis comment health, not the raw quality score. The quality score ishealth_items.quality.score.health_itemsandhealth_scoreare new response fields.permissiveand severity 4 (extreme) are new.permissivefails only extreme findings. With four severities, a clear finding now scores 3 and a severe one 2, sorelaxedhas a problem category minimum of 3.
WordPress
The WordPress plugin sends tolerances with each request for you. See Configuring the Plugin.