Skip to content

Content rules

Ordered rules on a template turn scores, author trust and field values into allow, review or reject. Severity triage decides when no rule matches.

Scores say what was found. Content rules say what you do about it: reject everything from a blocked author, send links from new accounts to review, let established authors through. Every template has its own ordered list.

Rules run from top to bottom, and the first one whose conditions all hold decides the recommendation. When none holds, severity triage decides from the scores alone.

You write rules in the template editor. The API returns them with the template, and every moderation says which rule decided.

The rules of a template

A template made from the Listing preset starts with six rules. The editor prints them like this, in the order they run:

RuleIfThen
01 Block banned authorsauthor is not enabledReject
02 Zero tolerance for hateHate speech is flaggedReject
03 Illicit goodsIllicit & regulated goods is flaggedReject
04 No sexual contentSexual content is flaggedReject
05 Allow established authorstrust level is at least regularAllow
06 Links from new accountstrust level is at most new and Links & contact details is flaggedReview
Which rule decides
Sample data
  1. 01

    Block banned authorsReject

    if author is not enabled

    Does not hold:author: enabled
  2. 02

    Zero tolerance for hateReject

    if Hate speech is flagged

    Does not hold:hate_speech0
  3. 03

    Illicit goodsReject

    if Illicit & regulated goods is flagged

    Does not hold:illicit0
  4. 04

    No sexual contentReject

    if Sexual content is flagged

    Does not hold:sexual0
  5. 05

    Allow established authorsAllow

    if trust level is at least regular

    Does not hold:trust level: new
  6. 06

    Links from new accountsReviewDecides

    if trust level is at most new and Links & contact details is flagged

    Holds:trust level: newlinks_contact0.90flagged
Severity triageOnly when no rule holds.
Not reached
Allow under 0.40, review from 0.40, reject from 0.80. Severity is high here, so triage would have said reject.

Reviewrecommendation: "review"

rule:r6Links from new accounts

flagged: truefrom triggers, not from the rule

Sample data

One moderation read against the six rules: the first rule that holds decides, and severity triage waits below for the case where none does.

The API returns the same rules with the template. Their ids, r1 to r6, are what a moderation reports. Three of the six are shown here.

curl https://api.useinfrared.com/v1/templates/listing \
  -H "Authorization: Bearer $INFRARED_KEY"
200 OK, trimmedJSON
{
  "object": "template",
  "slug": "listing",
  "version": 1,
  "rules": [
    {
      "id": "r1",
      "name": "Block banned authors",
      "action": "reject",
      "enabled": true,
      "conditions": [{ "op": "is_not", "type": "author_status", "value": "enabled" }]
    },
    {
      "id": "r5",
      "name": "Allow established authors",
      "action": "allow",
      "enabled": true,
      "conditions": [{ "op": "at_least", "type": "author_trust", "value": "regular" }]
    },
    {
      "id": "r6",
      "name": "Links from new accounts",
      "action": "review",
      "enabled": true,
      "conditions": [
        { "op": "at_most", "type": "author_trust", "value": "new" },
        { "type": "policy_flagged", "value": true, "policy": "links_contact" }
      ]
    }
  ],
  "severity": { "reject": 0.8, "review": 0.4 }
}

What a moderation reports

A listing text with a link, sent with no author:

curl https://api.useinfrared.com/v1/moderations \
  -H "Authorization: Bearer $INFRARED_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "listing",
    "type": "text",
    "input": "Vintage oak dining table, seats six. More photos at https://example.org/oak-table"
  }'
200 OK, trimmedJSON
{
  "object": "moderation",
  "id": "mod_07UTbSR4w600QSYxGtjTjF",
  "flagged": true,
  "recommendation": "review",
  "severity": "high",
  "triggered": ["links_contact", "guideline/no-off-site-deals", "instructions"],
  "rule": {
    "id": "r6",
    "name": "Links from new accounts",
    "action": "review"
  },
  "reasons": [
    "trigger:links_contact:flag",
    "trigger:guideline/no-off-site-deals:flag",
    "trigger:instructions:flag",
    "rule:r6"
  ],
  "author": null
}

A request that names no author counts as an enabled author with the trust level new. Rules r1 to r5 did not hold. r6 did: the trust level is at most new and links_contact is flagged. Its action became the recommendation, and rule and the last entry of reasons say so.

Severity is high here, so triage would have rejected. The rule decided first.

The same text sent with "author_id": "user_5530", an author whose trust level is established, stops one rule earlier:

200 OK, trimmedJSON
{
  "flagged": true,
  "recommendation": "allow",
  "rule": {
    "id": "r5",
    "name": "Allow established authors",
    "action": "allow"
  },
  "reasons": [
    "trigger:links_contact:flag",
    "trigger:guideline/no-off-site-deals:flag",
    "trigger:instructions:flag",
    "rule:r5"
  ]
}

flagged is still true. A rule sets the recommendation and nothing else: flagged comes from triggers, and a rule never changes it.

A rule

idstring

Assigned when the rule is first saved (r1, r2 and so on) and kept across versions. It is what rule.id and reasons carry.

namestringRequired

Up to 120 characters. Returned as rule.name.

enabledbooleanDefault true

A disabled rule is skipped.

conditionsarrayRequired

From 1 to 20 conditions. All of them must hold.

actionstringRequired

The recommendation when the rule decides.

One ofallowreviewreject

A template holds up to 100 rules. There is no "or" inside a rule: write two rules with the same action.

Conditions

TypeTestsOperatorsValue
policy_flaggedWhether a policy is flaggednonetrue or false
policy_scoreA policy's scoregte, gt, lte, ltA number from 0 to 1
author_statusThe author's statusis, is_notenabled, suspended or blocked
author_trustThe author's trust levelat_least, at_most, isnew, basic, regular, established or trusted, in that order
field_valueThe value submitted in a fieldeq, neq, contains, not_contains, gte, lte, in, not_in, empty, not_empty, matchesA string, a number, a boolean or a list
languageThe detected languagein, not_inA list of ISO 639-1 codes
One condition of each typeJSON
[
  { "type": "policy_flagged", "policy": "links_contact", "value": true },
  { "type": "policy_score", "policy": "made_with_ai", "op": "gte", "value": 0.9 },
  { "type": "author_status", "op": "is_not", "value": "enabled" },
  { "type": "author_trust", "op": "at_most", "value": "new" },
  { "type": "field_value", "field": "price", "op": "gte", "value": 10000 },
  { "type": "language", "op": "in", "value": ["pt", "es"] }
]

What each one reads:

  • Flagged means the policy matched a pattern, or its score is at or over the policy's own threshold. It does not depend on the policy's triggers or on its mode. A rule can therefore act on a policy in Observe mode, which is how a template sends photos that look generated to review while made_with_ai only observes.
  • A policy in a condition must be one the template lists. To write a rule on the AI instructions, turn AI instructions on in the Policies tab first.
  • An author the request did not name counts as enabled with the trust level new. Authors and context covers how to name one.
  • A field value is compared as text by eq, neq, in and not_in, and as a number by gte and lte. contains ignores case. contains and matches read text fields only, and only their first 500 characters.
  • A regular expression for matches is not checked when you save. An invalid one never matches.
  • A single object sent without field has no named fields, so a field_value condition sees an empty value.
  • Language is empty for a text too short to tell. in is then false and not_in is true.

How rules are evaluated

  1. Rules are read from top to bottom. A disabled rule is skipped.
  2. A rule holds when every one of its conditions holds.
  3. The first rule that holds decides. The rules under it are not read.
  4. When no rule holds, severity triage decides.

Two things happen after that. A client_action on the request can override the result or make it stricter. And when a policy in Trigger mode failed to run, an allow that no rule decided becomes a review. Decisions follows the whole path.

Rules apply to the submission as a whole. In a whole form, the per-field moderations are decided by triggers and triage, without rules.

Severity triage

Severity is the highest score × weight among the template's policies in Trigger mode. A pattern policy or a wordlist that matched counts as at least 0.85 before its weight. A policy in Observe mode adds nothing.

A template can set a policy's weight. The defaults:

WeightPolicies
1.00toxicity, hate_speech, violence, self_harm, sexual, illicit, instructions, every guideline and every wordlist
0.95links_contact
0.90personal_data
0.80profanity, spam, lookalike_characters
0.70duplicate
0.60made_with_ai
0.50off_topic, gibberish, screenshot, image_quality, watermark

Triage compares severity with the template's two thresholds, severity.review and severity.reject:

SeverityRecommendationseverity in the response
0allownone
Under 0.40allowlow
From 0.40reviewmedium
From 0.80rejecthigh, or critical from 0.95

0.40 and 0.80 are the defaults. One more thing can move the result: a trigger with the effect review that fired turns an allow into a review.

The weights explain the outcomes you will see. A gibberish score of 1.00 is a severity of 0.50, so meaningless text goes to review and is never rejected by triage. One URL is a severity of at least 0.81, so it is rejected unless a rule decides first.

An insult, on the same template, with no rule that covers it:

curl https://api.useinfrared.com/v1/moderations \
  -H "Authorization: Bearer $INFRARED_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "listing",
    "type": "text",
    "input": "Oak desk for sale. Time wasters are complete idiots and should stay away."
  }'
200 OK, trimmedJSON
{
  "object": "moderation",
  "id": "mod_1Nrd5iHg2oiIlvRK7Byk0a",
  "flagged": true,
  "recommendation": "reject",
  "severity": "high",
  "policies": {
    "toxicity": 0.81
  },
  "triggered": ["toxicity"],
  "rule": null,
  "reasons": ["trigger:toxicity:flag", "triage:reject"]
}

rule is null and the last reason is triage:reject: 0.81 at a weight of 1.00 is over the reject line.

Write rules in the editor

  1. Open the Rules tab

    Open Templates, choose the template and open Rules. The list is numbered in the order the rules run, and each row prints its rule as a sentence: "if trust level is at most new and Links & contact details is flagged".

    The Rules tab of a template: eight content rules in running order with their actions, and the severity triage thresholds under them.
    Dashboard, TemplatesThe rules of a template in running order, with severity triage under them.
  2. Add a rule

    Choose Add rule and give it a Name. Under If all of these hold, pick what the condition tests: Policy is flagged, Policy score, Author status, Author trust level, Field value or Language. Add condition adds another one, joined by "and". Under Then, pick Allow, Review or Reject.

    The Reason box is a note for your team. It is printed in the rule list and is not returned by the API.

  3. Put it in order

    A new rule goes to the end of the list. Move it with the Move up and Move down arrows of its row. The switch at the start of a row disables a rule without removing it.

  4. Set the triage thresholds

    Under the list, Severity triage has Review from and Reject from. Review cannot be higher than reject.

  5. Save and publish

    Save draft keeps the change in the draft. Before Publish version makes it live, replay last week's decisions against the draft with a simulation.

Next

Something missing or unclear on this page? Write to support@useinfrared.com.