Skip to content

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.

Sync and async
Your server
Infrared API
Worker
Your webhook endpoint
  1. Your server to Infrared API: POST /v1/moderationstype: "text"
  2. Infrared API: Screens the textinside the request
  3. Infrared API to Your server: 200 OKstatus: "completed"
  4. A request with an image
  5. Your server to Infrared API: POST /v1/moderationstype: "image"
  6. Infrared API to Your server: 202 Acceptedstatus: "screening"
  7. Infrared API to Worker: Queues the run
  8. Worker: Fetches and screens the imagechecks are taken when it ends
  9. Worker to Your webhook endpoint: moderation.completedstatus: "completed"
  10. Or ask for it
  11. Your server to Infrared API: GET /v1/moderations/{id}
  12. Infrared API to Your server: 200 OKstatus: "completed"
A text request answered in the same response, then an image request that is queued and reported by webhook or read by polling.

Choose with mode

modestringDefault auto

auto answers in the same response when the submission holds only text and plain values, and queues it when it holds an image. sync always answers in the same response. async always queues, text included.

One ofautosyncasync

The request wasHTTP statusThe body is
Answered in the same response200The finished moderation: completed, partial or invalid
Queued202The 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"
  }'
202 AcceptedJSON
{
  "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.

POST to your endpoint, trimmedJSON
{
  "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"

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"
  }'

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

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