Authors and trust
Infrared keeps a status, a trust level and a history for each of your users, and content rules read them. Block, suspend or trust an author.
An author is one of your users, known to Infrared by the id you send as author_id. For each one Infrared keeps a status, a trust level and a count of what they posted and how it was judged.
Content rules read all three, so a seller with a clean history can skip review while a new account posting a phone number goes to the queue. You can also block or suspend an author, in the dashboard or through the API.
Read an author by your id:
curl https://api.useinfrared.com/v1/authors/user_3107 \
-H "Authorization: Bearer $INFRARED_KEY"const response = await fetch("https://api.useinfrared.com/v1/authors/user_3107", {
headers: { Authorization: `Bearer ${process.env.INFRARED_KEY}` },
});
const author = await response.json();import os
import requests
response = requests.get(
"https://api.useinfrared.com/v1/authors/user_3107",
headers={"Authorization": f"Bearer {os.environ['INFRARED_KEY']}"},
)
author = response.json(){
"object": "author",
"id": "aut_7T0xuCYuTblsZqlQKE3h8T",
"project_id": "prj_0TBAgHGtdMketrbltGFHuk",
"external_id": "user_3107",
"display_name": "Lena Fischer",
"status": "enabled",
"status_reason": null,
"status_until": null,
"trust_level": "regular",
"trust_level_automatic": "regular",
"trust_override": null,
"risk_score": 0.069,
"stats": {
"moderations": 42,
"flagged": 6,
"rejected": 1,
"reviewed": 4,
"reports": 0
},
"first_seen_at": "2026-08-30T08:12:44.213Z",
"last_seen_at": "2026-10-06T09:14:03.120Z",
"metadata": {
"country": "DE",
"verified_email": true
},
"created_at": "2026-08-30T08:12:44.371Z",
"updated_at": "2026-10-06T09:14:03.120Z"
}How an author is created
The first moderation that carries an unknown author_id creates the author. Nothing else is needed. The id is yours, up to 200 characters, and belongs to one project: the same id in two projects is two authors.
To add a name or your own data, or to register a user before they post, use PUT. It creates the author when the id is new and answers 200 either way.
curl -X PUT https://api.useinfrared.com/v1/authors/user_5530 \
-H "Authorization: Bearer $INFRARED_KEY" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Sam Okafor",
"metadata": {
"seller_since": "2021-04-02"
},
"trust_override": "established"
}'const response = await fetch("https://api.useinfrared.com/v1/authors/user_5530", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.INFRARED_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
display_name: "Sam Okafor",
metadata: {
seller_since: "2021-04-02",
},
trust_override: "established",
}),
});
const author = await response.json();import os
import requests
response = requests.put(
"https://api.useinfrared.com/v1/authors/user_5530",
headers={"Authorization": f"Bearer {os.environ['INFRARED_KEY']}"},
json={
"display_name": "Sam Okafor",
"metadata": {
"seller_since": "2021-04-02",
},
"trust_override": "established",
},
)
author = response.json(){
"object": "author",
"id": "aut_671mHijsRVi8tfqptht3Fj",
"external_id": "user_5530",
"display_name": "Sam Okafor",
"status": "enabled",
"trust_level": "established",
"trust_level_automatic": "new",
"trust_override": "established",
"risk_score": null,
"stats": { "moderations": 0, "flagged": 0, "rejected": 0, "reviewed": 0, "reports": 0 },
"first_seen_at": "2026-10-08T08:48:48.185Z",
"metadata": { "seller_since": "2021-04-02" }
}metadata replaces the stored object as a whole. first_seen_at is the moment Infrared first saw the id, not the age of the account in your product, and it cannot be set. For users you already know, pin a level with trust_override, as here.
Status
An author is enabled, suspended or blocked. Three endpoints change it, and each returns the author.
curl https://api.useinfrared.com/v1/authors/user_8841/suspend \
-H "Authorization: Bearer $INFRARED_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Three rejected listings in one day",
"until": "2026-10-13T00:00:00Z"
}'const response = await fetch("https://api.useinfrared.com/v1/authors/user_8841/suspend", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INFRARED_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
reason: "Three rejected listings in one day",
until: "2026-10-13T00:00:00Z",
}),
});
const author = await response.json();import os
import requests
response = requests.post(
"https://api.useinfrared.com/v1/authors/user_8841/suspend",
headers={"Authorization": f"Bearer {os.environ['INFRARED_KEY']}"},
json={
"reason": "Three rejected listings in one day",
"until": "2026-10-13T00:00:00Z",
},
)
author = response.json()| Endpoint | Body |
|---|---|
POST /v1/authors/{external_id}/block | reason, optional. |
POST /v1/authors/{external_id}/suspend | reason and until, both optional. until is an ISO 8601 time in UTC, ending in Z. Without it the suspension lasts until you enable the author. |
POST /v1/authors/{external_id}/enable | reason, optional. |
A suspension with until lifts itself. Decisions read the author as enabled from the moment until passes, and the author object follows within 15 minutes, with the event author.enabled.
These endpoints answer 404 for an id Infrared has not seen. A reason longer than 255 characters answers 500 today, so keep it shorter.
This is a harmless listing from a blocked author, sent to a template that starts with the rule:
curl https://api.useinfrared.com/v1/moderations \
-H "Authorization: Bearer $INFRARED_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "listing",
"type": "text",
"field": "description",
"input": "Mountain bike, 29 inch wheels, new brakes. Collection only.",
"content_id": "listing_20933",
"author_id": "user_2207"
}'const response = await fetch("https://api.useinfrared.com/v1/moderations", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INFRARED_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
template: "listing",
type: "text",
field: "description",
input: "Mountain bike, 29 inch wheels, new brakes. Collection only.",
content_id: "listing_20933",
author_id: "user_2207",
}),
});
const moderation = await response.json();import os
import requests
response = requests.post(
"https://api.useinfrared.com/v1/moderations",
headers={"Authorization": f"Bearer {os.environ['INFRARED_KEY']}"},
json={
"template": "listing",
"type": "text",
"field": "description",
"input": "Mountain bike, 29 inch wheels, new brakes. Collection only.",
"content_id": "listing_20933",
"author_id": "user_2207",
},
)
moderation = response.json(){
"object": "moderation",
"id": "mod_5gqdUlXUBscDXO0OSe9pxt",
"template": { "slug": "listing", "version": 1 },
"status": "completed",
"flagged": false,
"recommendation": "reject",
"severity": "none",
"triggered": [],
"rule": { "id": "r1", "name": "Block banned authors", "action": "reject" },
"reasons": ["rule:r1"],
"author": { "id": "aut_2q8jAQc9LzmBqrMZE2Kmdw", "external_id": "user_2207", "status": "blocked", "trust_level": "new", "known": true }
}The rule decided, so recommendation is reject and rule names it. flagged still describes the content alone and is false here: act on recommendation. Sent to the basic check, the same content from the same author answers allow.
Trust levels
user_310742 moderations1 rejected (2.4%)First seen 37 days agonewWhere every author startsbasic- Met5 moderations
- Met10% rejected at most
regular- Met20 moderations
- Met5% rejected at most
- Met7 days since first seen
established- Not met100 moderations
- Not met2% rejected at most
- Met30 days since first seen
trusted- Not met500 moderations
- Not met1% rejected at most
- Not met90 days since first seen
- The level is the highest one whose conditions all hold.
- Suspended or blocked: the automatic level is
new. trust_overridepins a level.
Sample data
Every author starts at new. After each counted moderation Infrared recomputes the level: the highest one whose three conditions all hold.
| Level | Moderations | Share rejected | Days since first seen |
|---|---|---|---|
basic | 5 or more | 10% at most | Any |
regular | 20 or more | 5% at most | 7 or more |
established | 100 or more | 2% at most | 30 or more |
trusted | 500 or more | 1% at most | 90 or more |
The right scale depends on the product: a chat member posts hundreds of times a day, a seller a few times a month. A Project admin or an organisation role can change any of the twelve numbers for a project in Settings, Project, under Links and trust, Trust rules.
The author object carries three fields:
trust_level_automaticstringThe level computed from history. It is
newwhile the author is suspended or blocked.trust_overridestring or nullA level you pinned, with
PUTor in the dashboard.nullhands the level back to history.trust_levelstringThe effective level, the one content rules read: the override when there is one, else the automatic level.
An override outranks status. A blocked author pinned to trusted still reads as trusted in a trust condition, so keep the rule that reads status above the rules that read trust.
Risk score and counters
stats counts the author's moderations and how they ended.
| Counter | Counts |
|---|---|
moderations | Every counted moderation. |
flagged | Those that were FLAGGED. |
rejected | Those whose recommendation was reject. |
reviewed | Those whose recommendation was review. |
reports | Nothing yet. It is always 0. |
The counters follow the recommendation of each moderation. A reviewer's decision in the queue does not change them.
A moderation is counted unless it was made with a test key, was a dry run, failed validation or failed to run. Those still create the author when the id is new.
risk_score is a number from 0 to 1, for sorting: 0.4 times the share flagged, plus 0.5 times the share rejected, plus 1 for a blocked author or 0.7 for a suspended one, capped at 1. It is null until the first counted moderation or status change, and 0.5 for an author whose status changed before any moderation was counted.
How a content rule reads an author
Before deciding, Infrared takes the author's status and effective trust level as they are before this moderation is counted. The response returns them as author, as in the example above, and a simulation replays them as they were. A request without author_id is decided as enabled and new.
Two conditions read them. Author status is or is not enabled, suspended or blocked. Author trust level is at least, is at most or is one of the five levels. Conditions has the syntax of both, and the rule the four presets start with.
List authors
curl "https://api.useinfrared.com/v1/authors?sort=risk&status=enabled&limit=20" \
-H "Authorization: Bearer $INFRARED_KEY"const response = await fetch("https://api.useinfrared.com/v1/authors?sort=risk&status=enabled&limit=20", {
headers: { Authorization: `Bearer ${process.env.INFRARED_KEY}` },
});
const page = await response.json();import os
import requests
response = requests.get(
"https://api.useinfrared.com/v1/authors",
headers={"Authorization": f"Bearer {os.environ['INFRARED_KEY']}"},
params={
"sort": "risk",
"status": "enabled",
"limit": 20,
},
)
page = response.json()sort is activity (last seen, the default), trust, risk or created, with order desc by default. status and trust_level filter the list. Both trust_level and sort=trust read the automatic level, not the override.
GET /v1/authors/{external_id}/moderations lists an author's moderations, newest first, as short summaries.
Authors in the dashboard
The Authors page lists every author of the project, with a search by your id, a filter by status and four orders: Last seen, Most trusted, Highest risk and Newest.
An author's page has Block, Suspend… and Enable, the four counters with the risk score, and a Trust section with the Automatic level, the Override and the Effective level. Below are the author's recent moderations and the actions run on them. Any member of the project can change status and trust.
Events
| Event | Sent when |
|---|---|
author.blocked, author.suspended, author.enabled | The status changes. data has the author, previous_status, reason, until and source: api, dashboard, or rule when a suspension lifts itself. |
author.trust_level_changed | The effective level changes after a moderation (source is automatic) or an override (manual). |
A status change sends its author.* event and nothing else. It sends no action.executed, unless you made it by running the block_author, suspend_author or enable_author action. It sends no author.trust_level_changed either, although the automatic level drops to new and comes back on enable.
Next
- Content rulesOrdered rules on a template turn scores, author trust and field values into allow, review or reject. Severity triage decides when no rule matches.
- Authors, content ids and metadataFive optional fields tie a moderation to your own data. They say who wrote the content, which object it is, what it belongs to and whether it is stored.
- ActionsAn action is what a decision turns into. Allow, reject, change an author's status or define your own, and act on the webhook each run sends.
- Event typesThe twelve events Infrared sends, when each one fires and what its data holds, with a real payload for moderations, queue items, authors and actions.
Something missing or unclear on this page? Write to support@useinfrared.com.

