Sync and async
Text is answered in the same response and images are queued. Choose with mode, then read a queued result from a webhook event or by polling.
A moderation is finished either inside your request or shortly after it. You do not have to choose: text is answered in the same response, and anything that holds an image is queued. mode lets you decide otherwise, and a queued result reaches you by webhook or by polling.
- Your server to Infrared API: POST /
v1/ moderations type: "text" - Infrared API: Screens the textinside the request
- Infrared API to Your server: 200 OK
status: "completed" - A request with an image
- Your server to Infrared API: POST /
v1/ moderations type: "image" - Infrared API to Your server: 202 Accepted
status: "screening" - Infrared API to Worker: Queues the run
- Worker: Fetches and screens the imagechecks are taken when it ends
- Worker to Your webhook endpoint: moderation.
completed status: "completed" - Or ask for it
- Your server to Infrared API: GET /
v1/ moderations/ {id} - Infrared API to Your server: 200 OK
status: "completed"
Choose with mode
modestringDefaultautoautoanswers in the same response when the submission holds only text and plain values, and queues it when it holds an image.syncalways answers in the same response.asyncalways queues, text included.One of
autosyncasync
| The request was | HTTP status | The body is |
|---|---|---|
| Answered in the same response | 200 | The finished moderation: completed, partial or invalid |
| Queued | 202 | The moderation with status: "screening" |
A submission that fails validation is always answered with 200 at once, whatever mode says.
Use sync with an image when your upload flow needs the verdict before it responds. The request then stays open while each image is fetched and screened. Use async with text when a user-facing request should not wait on moderation.
What a queued request returns
curl https://api.useinfrared.com/v1/moderations \
-H "Authorization: Bearer $INFRARED_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "text",
"input": "Great, see you on Saturday at ten. I will bring a van.",
"mode": "async",
"content_id": "message_77121"
}'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({
type: "text",
input: "Great, see you on Saturday at ten. I will bring a van.",
mode: "async",
content_id: "message_77121",
}),
});
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={
"type": "text",
"input": "Great, see you on Saturday at ten. I will bring a van.",
"mode": "async",
"content_id": "message_77121",
},
)
moderation = response.json(){
"object": "moderation",
"id": "mod_6SvpLuRuABnLWTqwVXnrFy",
"template": { "slug": "basic", "version": 1 },
"type": "text",
"status": "screening",
"flagged": false,
"recommendation": null,
"severity": null,
"policies": {},
"triggered": [],
"rule": null,
"reasons": [],
"author": null,
"language": null,
"usage": { "checks": 0, "cached": false },
"latency_ms": null,
"dry_run": false,
"test": true,
"content_id": "message_77121",
"context_id": null,
"metadata": null,
"created_at": "2026-10-08T08:56:00.172Z",
"completed_at": null
}Read status before anything else. While it is screening, flagged is false and recommendation is null because nothing has been decided, not because the content is safe.
Read the result by webhook
When the run ends, Infrared sends a moderation.completed event to the project's webhook endpoints. data.moderation is the finished moderation, the same object the API returns.
{
"id": "evt_31wPav7P1pPuSPeuiUCeCB",
"type": "moderation.completed",
"created_at": "2026-10-08T08:56:02.554Z",
"api_version": "2026-09-01",
"data": {
"moderation": {
"object": "moderation",
"id": "mod_6SvpLuRuABnLWTqwVXnrFy",
"status": "completed",
"flagged": false,
"recommendation": "allow",
"reasons": ["triage:allow"],
"content_id": "message_77121",
"completed_at": "2026-10-08T08:56:02.028Z"
}
}
}The event is sent for every moderation that finishes, including the ones answered in the same response and the ones that end partial or invalid. Match it to your content by id or by the content_id you sent. Verify the signature before trusting the body.
Read the result by polling
Ask for the moderation by id until status is no longer screening.
curl https://api.useinfrared.com/v1/moderations/mod_6SvpLuRuABnLWTqwVXnrFy \
-H "Authorization: Bearer $INFRARED_KEY"const response = await fetch("https://api.useinfrared.com/v1/moderations/mod_6SvpLuRuABnLWTqwVXnrFy", {
headers: { Authorization: `Bearer ${process.env.INFRARED_KEY}` },
});
const moderation = await response.json();import os
import requests
response = requests.get(
"https://api.useinfrared.com/v1/moderations/mod_6SvpLuRuABnLWTqwVXnrFy",
headers={"Authorization": f"Bearer {os.environ['INFRARED_KEY']}"},
)
moderation = response.json()Reading needs the read scope. An API key that holds only moderate can create moderations and cannot fetch them: it answers 403 insufficient_scope.
When a run fails
A run that cannot finish ends with status: "failed". The moderation then carries an error object whose code is pipeline_error, flagged stays false, recommendation stays null, and no check is used. A moderation.failed event is sent in place of moderation.completed.
failed is not the same as partial. A policy that does not answer, or an image that cannot be fetched, gives a partial result with a recommendation you can act on. Statuses and errors tells them apart.
A request answered in the same response whose run fails answers 500, and the moderation exists with status: "failed". Treat failed as "not screened" and send the content again.
Checks and queued requests
When a request is accepted, the API checks that the organisation has enough checks for it and answers 402 quota_exceeded if not. The checks are taken when the run completes. That is why usage.checks is 0 in a 202 body and final in the finished moderation.
Repeat a request safely
Send an Idempotency-Key header with a value of your own, and repeating the request returns the first response for 24 hours, with the header Idempotent-Replayed: true. For a queued request that is the original 202 body, still screening: read the result by id.
curl https://api.useinfrared.com/v1/moderations \
-H "Authorization: Bearer $INFRARED_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: message-77121-v1" \
-d '{
"type": "text",
"input": "Great, see you on Saturday at ten. I will bring a van.",
"mode": "async",
"content_id": "message_77121"
}'const response = await fetch("https://api.useinfrared.com/v1/moderations", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INFRARED_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "message-77121-v1",
},
body: JSON.stringify({
type: "text",
input: "Great, see you on Saturday at ten. I will bring a van.",
mode: "async",
content_id: "message_77121",
}),
});
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']}",
"Idempotency-Key": "message-77121-v1",
},
json={
"type": "text",
"input": "Great, see you on Saturday at ten. I will bring a van.",
"mode": "async",
"content_id": "message_77121",
},
)
moderation = response.json()An answer in the 4xx range is stored and replayed in the same way. After you fix what caused a 402, 403, 404 or 422, send the request with a new key. Idempotency has the rest.
Next
- WebhooksInfrared posts a signed event to your endpoint when a moderation finishes, a queue item is decided or an author changes. Add an endpoint and receive it.
- 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.
- Statuses and errorsThe five statuses a moderation can have and every error the moderation endpoints return, with the HTTP status, the code and what to do next.
- IdempotencySend an Idempotency-Key when you create a moderation, and a retry returns the first answer in place of a second moderation and a second charge.
Something missing or unclear on this page? Write to support@useinfrared.com.