Moderation API
Moderation API
Send one piece of text and get back a judgement you can act on: a score from 0 to 1 for each of seven categories, whether each one crossed its line, and a single verdict — allow, review or block. It is built for the checks around user-written and model-written text: a comment before it is published, a prompt before it reaches a model, a reply before a user sees it.
POST https://api.beatapi.io/v1/moderations
The request and response follow OpenAI’s Moderation API, so the official OpenAI SDKs work with only the base URL and the API key changed. This was verified on 2026-10-12 with the official Python SDK (openai 2.48.0).
A failed call judged nothing. Never treat an error — a timeout, a 4xx, a 503 — as “allowed”. Hold the content, or retry.
Quick start
The response below is a real one, returned for an advertisement of this kind that offered forged ID cards and bank cards.
illicit scored 0.97, above its line of 0.6, so categories.illicit is true, flagged is true and the verdict is block.
OpenAI SDK (Python)
Point the client at BeatAPI and call moderations.create as usual. The fields BeatAPI adds are readable on the typed result.
JavaScript
Request
Response
The response is synchronous: a successful HTTP response carries the judgement. There is nothing to poll.
Categories
Seven categories are judged, named as OpenAI names them, each with a score from 0 to 1.
OpenAI’s six sub-category keys — harassment/threatening, hate/threatening, illicit/violent, self-harm/intent, self-harm/instructions and violence/graphic — are present so that typed SDKs keep working, but they are not judged separately. They are always false, with a score of 0 and an empty category_applied_input_types list. Do not build rules on them.
Lines and verdicts
Every judged category has a line: 0.6, or 0.3 for sexual/minors. categories[k] is the comparison of that category’s score with its line, and flagged is true when any category’s score reaches its line.
sexual_level says how sexual the text is, independently of the score:
category_scores.sexual is the probability of the explicit level. Suggestive text scores low there and shows up as sexual_level: "suggestive" and verdict: "review" instead, so read those two fields when suggestive content matters to you.
If you want stricter or looser lines than these, apply your own to category_scores.
policy_version identifies the policy that produced the scores. Scores from different policy versions are not comparable, so keep the version next to any score you store.
Limits
- Text only. An
image_urlinput is refused with a 400. - One text per call. An array with more than one item is refused with a 400 — nothing is partially judged. Send one request per text.
- At most 8,000 characters. Longer text is refused with a 400 rather than cut short; split it and send each part. Length is counted in UTF-16 code units, so most emoji count as two.
- Synchronous. There is no streaming and no task to poll. Server-side processing takes about 0.2 seconds.
Errors
Errors use the standard BeatAPI error envelope: code, message, retryable, request_id, and retry_after_seconds on a 429. See Errors for the full table and the retry policy. On this endpoint the error object also carries OpenAI’s type and param (param is always null), so the OpenAI SDKs raise their usual exceptions.
The exceptions in the table are the ones the official Python SDK (openai 2.48.0) raised on 2026-10-12, except the 503 row, which follows the SDK’s status mapping and was not triggered.
A failed call judged nothing — never treat an error as “allowed”. A request refused with a 400 is not billed.
Billing
Moderation is billed on input tokens only, at $0.042 per 1M input tokens. beat-moderation-1 is listed on the pricing page.
The moderation policy travels with every call and counts as input, about 1,150 tokens, so even a short text has a floor:
usage.input_tokens in every response tells you exactly what the call counted, so you can reconcile spend per call.
Privacy
The text you submit is not written to request logs. Only its length, the scores and the policy version are kept.
What it does not do
- It is a classifier, not a guarantee. Plain violations are caught reliably. Coded slang, homophones and disguised spam — especially in Chinese — are caught less reliably. Test it on your own content before relying on it.
sexual/minorsdoes not replace hash-matching for known abuse material, or any legal reporting duty you have.- It does not check images, audio or video.
- Scores are not exactly repeatable. In our tests they moved by up to 0.04 between identical calls, so a text that scores close to a line can land on either side of it. Treat a score near a line as borderline.
Agents
Agents reach the same check through the Capabilities API, where it is in the catalogue as model:beat-moderation-1. The Run input is {"input": "…"} and the result is the same body as above. Errors on that path use the standard envelope without type and param.

