Text moderation

Screen text for policy violations

Text moderation evaluates text against your configured moderation policies, including any custom categories you have configured. Text judged prohibited is rejected, and the reason is returned on the completed job.

Input#

Submit a multipart/form-data request with:

Field Type Required Description
text string Yes The text to moderate.
webhook_url string No URL to receive the result on completion.
Shell
curl -X POST https://api.omnifence.ai/api/v1/moderate/text \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "text=A sunset over the ocean"

How it's checked#

The text is checked against your policy configuration and every enabled custom category on your account. If it is judged prohibited, the job is rejected with is_prohibited: true and a short reason. When a custom category caused the rejection, the reason names it by slug.

Output#

JSON
{
  "is_prohibited": true,
  "reason": "The text requests sexual content involving a minor.",
  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "type": "text",
  "status": "completed",
  "created_at": "2026-05-19T12:00:00.000Z",
  "completed_at": "2026-05-19T12:00:01.500Z"
}

When is_prohibited is true, reason explains why the text was prohibited. Text that passes returns is_prohibited: false with no reason. See decision logic for how the prohibition decision is determined.

A completed job can also carry the optional scores and custom_scores fields:

JSON
{
  "is_prohibited": false,
  "scores": { "violence": 0.04, "self_harm": 0.01 },
  "custom_scores": { "coffee": 0.02 },
  "job_id": "d4e5f6a7-b8c9-0123-def0-234567890123",
  "type": "text",
  "status": "completed",
  "created_at": "2026-11-09T12:00:00.000Z",
  "completed_at": "2026-11-09T12:00:01.200Z"
}

They are informational risk scores and never change the decision. A text job rejected by the blocked-keyword list, or very long text, has no scores.

Batch text moderation#

Send up to 99 texts in one request with POST /api/v1/moderate/text/batch. Use a batch when one action in your app produces several texts that each need their own decision, for example the fields of a profile form.

  • Each item is checked on its own, exactly as a single /moderate/text submit checks it. The model never sees the other items of the batch.
  • Each item gets its own decision under the key you give it, so you can show which field was rejected.
  • One batch request counts as one request against your rate limit. Each item is billed as one text moderation job.
  • Each item is an ordinary text job with its own job_id. GET /api/v1/job/{job_id} reads it, and the response names its batch_id and batch_key.

Input#

Send a JSON body:

Field Type Required Description
items array Yes 1 to 99 items.
items[].key string Yes Your name for the item, unique in the batch. 1 to 64 letters, digits, _, ., : or -.
items[].text string Yes The text to moderate. At most 20,000 characters.
webhook_url string No URL that receives one webhook when every item is settled. Falls back to your registered webhook.
Shell
curl -X POST https://api.omnifence.ai/api/v1/moderate/text/batch \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "key": "name", "text": "Captain Mira" },
      { "key": "backstory", "text": "A pilot who left the fleet." }
    ],
    "webhook_url": "https://your-app.com/webhooks/omnifence"
  }'
JavaScript
const response = await fetch('https://api.omnifence.ai/api/v1/moderate/text/batch', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.OMNIFENCE_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    items: [
      { key: 'name', text: 'Captain Mira' },
      { key: 'backstory', text: 'A pilot who left the fleet.' },
    ],
  }),
});
const { batch_id, items } = await response.json();

The API accepts the batch with 202 and returns the batch ID and the job ID of each item:

JSON
{
  "batch_id": "96cf89a8-4baa-44a4-a953-010f4486882d",
  "status": "processing",
  "items": [
    { "key": "name", "job_id": "b4c38526-b18b-431c-ab54-9e3b82bf26f0" },
    { "key": "backstory", "job_id": "6c3f2e78-a17a-44a9-b259-d3344eee8bf3" }
  ]
}

Validation#

The batch is accepted whole or refused whole. One invalid item refuses the request with 400, no job is created and nothing is charged. The message names the item.

Code Cause
BATCH_EMPTY items holds no item.
BATCH_TOO_LARGE items holds more than 99 items.
DUPLICATE_ITEM_KEY Two items have the same key.
TEXT_TOO_LONG An item's text is longer than 20,000 characters.
INVALID_REQUEST The body is not JSON, items is missing, or a key or text is invalid or empty.

A credit balance at or below zero refuses the whole batch with 402 PAYMENT_REQUIRED, and no item is accepted. With fixed pricing, the balance must also cover the price of every item. With multiplier pricing, each item is charged when it completes, as a single job is.

Output#

When every item is completed or failed, the API sends one webhook with every item's decision. See batch webhook. Items send no webhook of their own.

You can also read the batch with GET /api/v1/moderate/text/batch/{batch_id}:

JSON
{
  "type": "text_batch",
  "batch_id": "96cf89a8-4baa-44a4-a953-010f4486882d",
  "status": "completed",
  "created_at": "2026-10-06T14:02:01.249Z",
  "completed_at": "2026-10-06T14:02:03.494Z",
  "items": [
    {
      "key": "backstory",
      "job_id": "6c3f2e78-a17a-44a9-b259-d3344eee8bf3",
      "status": "completed",
      "is_prohibited": true,
      "reason": "The text contained a blacklisted keyword."
    },
    {
      "key": "name",
      "job_id": "b4c38526-b18b-431c-ab54-9e3b82bf26f0",
      "status": "completed",
      "is_prohibited": false,
      "scores": { "violence": 0.01 }
    }
  ]
}

Each item carries is_prohibited, plus reason on a rejection and error_code on a failure. A completed item can also carry the optional scores and custom_scores risk scores, as a single text job does. A failed item has status: "failed" and is_prohibited: null, it is refunded, and it does not change the decision of any other item. Resubmit the failed text to get its decision.