
Image Moderation API Tutorial: Python and JavaScript Example
How an image moderation API works: which harm categories apply to images, URL-only input, per-image billing, score thresholds, and Python and JavaScript code.
An image moderation API takes a picture, scores it against a list of harm categories, and tells you whether to publish, hold or block it. The catch with a general-purpose model like omni-moderation-latest is that not every category it reports actually looks at the image: OpenAI's documentation marks 6 of its 13 categories as "Text and images" and the other 7 as "Text only"[1]. If you send a photo with no caption, those 7 text-only categories come back as 0 by design, not because the photo is clean[1].
This guide covers what that means in practice: which categories you can rely on for images, how to send an image (reAPI takes public URLs only), how a request is billed, how to turn scores into decisions, and a runnable Python and JavaScript example that submits an image and polls for the verdict. It ends with how image-specialist services such as Sightengine, Hive, Azure AI Content Safety and Amazon Rekognition differ.
TL;DR
omni-moderation-latestscores images for 6 categories:sexual,self-harm,self-harm/intent,self-harm/instructions,violenceandviolence/graphic. The other 7, includingsexual/minors, are text only[1].- On reAPI, an image goes in as an
image_urlblock with a public http(s) URL. Base64 anddata:URIs are rejected[2]. - Billing is per moderation unit: one unit per image, one per 1,000 words of text, at least one unit per request[2]. Current rates are on the model page.
- The call is asynchronous:
POST /api/v1/moderations, then pollGET /api/v1/tasks/{id}untilstatusiscompleted[2]. flaggedis the model's default verdict. For your own policy, thresholdcategory_scores(0 to 1) per category and recalibrate when the model updates[1].- Whether OpenAI's own endpoint is free is covered in Is the OpenAI moderation API free?.
Which harm categories an image moderation API can see
OpenAI lists the inputs each omni-moderation-latest category supports[1]:
| Category | Inputs |
|---|---|
sexual | Text and images |
self-harm, self-harm/intent, self-harm/instructions | Text and images |
violence, violence/graphic | Text and images |
sexual/minors | Text only |
harassment, harassment/threatening | Text only |
hate, hate/threatening | Text only |
illicit, illicit/violent | Text only |
Two consequences matter for an image pipeline.
First, a zero is not always a measurement. OpenAI states that if you send only images, without accompanying text, the model returns a score of 0 for the text-only categories[1]. A hate symbol in a meme or a written threat inside a screenshot will not be caught by the hate or harassment score of an image-only request. The response tells you which input types each category was evaluated against in category_applied_input_types; in OpenAI's image-only example, the six image categories list ["image"] and the rest list an empty array[1]. Check that field before treating a low score as a pass.
Second, sexual/minors is text only[1]. OpenAI's guide also says not to send known or suspected child sexual abuse material to the Moderation API, because it is not designed for CSAM detection and is not a substitute for dedicated child-safety safeguards[1]. If your platform accepts user uploads, plan a separate child-safety control.
OpenAI documents an image file limit of 20 MB for the model[1].
Sending images: public URLs only
The request body has one content field, input. For images you send an array of content blocks[2]:
{
"model": "omni-moderation-latest",
"input": [
{ "type": "image_url", "image_url": { "url": "https://example.com/upload.jpg" } }
]
}OpenAI's own API reference says image_url.url can be either an image URL or base64-encoded image data[3]. reAPI accepts only the first form: image_url.url must be a public http(s) URL, and base64 or data: URIs fail validation with error 20003[2]. That rule applies to media inputs on every reAPI model.
For user uploads this means the order of operations is: store the file, get a URL the internet can fetch, then moderate. Signed URLs work as long as they are still valid when the task runs[2]. If the URL cannot be fetched, the task fails with 80007 and the charge is refunded[2]. Keep the object private and unpublished until the verdict comes back.
You can also put a caption and its photo in the same array. reAPI's docs note that a content-block array returns one result for the whole set, so the model judges text and image together[2]. That is what you want for a post with a caption. It is not what you want for ten unrelated uploads: one combined verdict will not tell you which image triggered it, so send those as separate requests.
How image moderation is billed
reAPI bills omni-moderation-latest per moderation unit[2]:
| Input | Units |
|---|---|
Each image_url block | 1 unit |
| Text | 1 unit per 1,000 words, summed across the request; each text item counts as at least 50 words |
| Any request | At least 1 unit |
Units are converted to credits once per request and rounded up, polling is free, and failed requests are refunded automatically[2]. One request takes up to 500 items[2]. The per-unit rate is on the model page. For a cross-vendor price and free-tier comparison, see Is the OpenAI moderation API free?.
Image moderation API example in Python and JavaScript
Both examples do the same thing: submit one image, poll the task every 3 seconds, and return the first result. reAPI's task docs recommend polling no faster than every 2 to 3 seconds, because the polling endpoint is cached for 5 seconds while a task is in flight[4]. A moderation check usually finishes within a few seconds[2].
Python
import time
import requests
API = "https://reapi.ai/api/v1"
HEADERS = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
}
def moderate_image(image_url, timeout_s=60):
submit = requests.post(
f"{API}/moderations",
headers=HEADERS,
json={
"model": "omni-moderation-latest",
"input": [{"type": "image_url", "image_url": {"url": image_url}}],
},
timeout=30,
)
submit.raise_for_status()
task_id = submit.json()["id"]
deadline = time.time() + timeout_s
while time.time() < deadline:
time.sleep(3)
task = requests.get(f"{API}/tasks/{task_id}", headers=HEADERS, timeout=30).json()
if task["status"] == "completed":
return task["output"]["moderation"]["results"][0]
if task["status"] == "failed":
raise RuntimeError(f'{task["error"]["code"]}: {task["error"]["message"]}')
raise TimeoutError(f"task {task_id} still processing")
result = moderate_image("https://example.com/upload.jpg")
print(result["flagged"], result["category_scores"]["violence"])JavaScript (Node.js 18+, saved as an .mjs module)
const API = "https://reapi.ai/api/v1";
const headers = {
Authorization: `Bearer ${process.env.REAPI_API_KEY}`,
"Content-Type": "application/json",
};
async function moderateImage(imageUrl, timeoutMs = 60_000) {
const submit = await fetch(`${API}/moderations`, {
method: "POST",
headers,
body: JSON.stringify({
model: "omni-moderation-latest",
input: [{ type: "image_url", image_url: { url: imageUrl } }],
}),
});
if (!submit.ok) throw new Error(`submit failed: ${submit.status} ${await submit.text()}`);
const { id } = await submit.json();
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
await new Promise((r) => setTimeout(r, 3000));
const task = await (await fetch(`${API}/tasks/${id}`, { headers })).json();
if (task.status === "completed") return task.output.moderation.results[0];
if (task.status === "failed") throw new Error(`${task.error.code}: ${task.error.message}`);
}
throw new Error(`task ${id} still processing`);
}
const result = await moderateImage("https://example.com/upload.jpg");
console.log(result.flagged, result.category_scores.violence);Run the JavaScript version on your server, not in the browser. The API key is a Bearer token, and shipping it in front-end code exposes it to anyone who opens developer tools. The same applies to the React question in the FAQ below. The full request schema, including text and mixed input, is in the API docs.
Reading the result and setting thresholds
Each result carries four fields[1][2]:
| Field | What it tells you |
|---|---|
flagged | true when the model considers the input harmful in at least one category |
categories | The model's true/false verdict per category |
category_scores | The model's confidence per category, from 0 to 1 |
category_applied_input_types | Which input types (text, image) each category was evaluated against |
flagged is a reasonable first pass, but it encodes the model's defaults, which may be stricter or looser than your policy[2]. OpenAI's guidance is to treat the scores as signals for your application's policy rather than as an automatic blocking decision, and to route flagged content to review where needed[1].
A common shape is three bands per image category: allow below a low score, send to human review in the middle, block above a high score. A minimal version:
IMAGE_CATEGORIES = [
"sexual", "self-harm", "self-harm/intent", "self-harm/instructions",
"violence", "violence/graphic",
]
REVIEW_AT = 0.3 # example values only; calibrate on your own labeled images
BLOCK_AT = 0.8
def decide(result):
applied = result["category_applied_input_types"]
scores = result["category_scores"]
checked = [c for c in IMAGE_CATEGORIES if "image" in applied.get(c, [])]
top = max((scores[c] for c in checked), default=0.0)
if top >= BLOCK_AT:
return "block"
if top >= REVIEW_AT or result["flagged"]:
return "review"
return "allow"The numbers above are illustrative, not recommendations. Pick yours by running a few hundred images you have already labeled and reading where the scores fall. Re-run that check periodically: OpenAI says it plans to keep upgrading the underlying model and that custom policies built on category_scores may need recalibration over time[1]. Scores are not rounded or remapped on the way through reAPI[2], so a calibration you do once applies directly to the values your code reads.
How image-specialist moderation APIs differ
A general model and an image-specialist service answer different questions. omni-moderation-latest scores six visual categories and can judge a caption and its image together[1]. Image-specialist APIs detect many more visual classes, such as weapons, drugs, hate symbols or text inside the image, and differ in how they take the file and how they report confidence. From each vendor's documentation:
| Service | What it detects in images | How the image is sent | Output | Billing unit |
|---|---|---|---|---|
omni-moderation-latest on reAPI | sexual, self-harm (3), violence (2)[1] | Public http(s) URL[2] | Boolean + 0–1 score per category[1] | Per image (moderation unit)[2] |
| Sightengine | Nudity across 29 classes, violence, weapons, hate and offensive signs, gore, self-harm, drugs, alcohol, tobacco, gambling, text in images, QR codes[5] | Direct upload or a publicly accessible URL[6] | Results per selected model | Operations; models in the same group count once per request[7] |
| Hive Visual Moderation | Sexual content, violent imagery, drugs, hate imagery and image attributes, split into named classes[8] | Public or signed URL, or a local file upload[9] | 0–1 confidence per class[9] | Annual contract; Hive points smaller customers to its self-serve VLM[8] |
| Azure AI Content Safety (Analyze Image) | Hate, SelfHarm, Sexual, Violence[10] | Base64 bytes or a blob URL, one or the other[10] | Severity 0, 2, 4 or 6 per category[11] | Per image submitted[12] |
| Amazon Rekognition (DetectModerationLabels) | Three-level label taxonomy; top level includes Explicit, Violence, Visually Disturbing, Drugs & Tobacco, Alcohol, Rude Gestures, Gambling and Hate Symbols[13] | Base64 image bytes or an Amazon S3 object; JPEG or PNG[14] | Labels with confidence; MinConfidence defaults to 50[14] | Per image per API call[15] |
Three differences tend to decide the choice.
Visual coverage. If you need to catch weapons, drugs, hate symbols or text burned into an image, the specialist services list those classes explicitly[5][8][13]. omni-moderation-latest does not have image categories for them[1].
Score shape. Hive returns a 0–1 confidence per class and suggests starting around 0.90 to flag a class[8]. Rekognition drops labels below MinConfidence, which defaults to 50 on a 0–100 scale[14]. Azure returns coarse severity levels for images: 0, 2, 4 or 6[11]. A threshold tuned on one service does not transfer to another.
Input handling. Azure and Rekognition accept base64 bytes[10][14], and Sightengine and Hive accept a direct file upload[6][9]. reAPI takes URLs only[2], which suits pipelines where the upload already lands in object storage.
omni-moderation-latest fits when your content is mostly text with images attached, when the six visual categories match your policy, or when you already call other models through reAPI with the same key. For a broader comparison by modality and use case, see Best content moderation API.
FAQ
Image moderation api free
OpenAI states that its own moderation endpoint is free to use[1]. On reAPI, omni-moderation-latest is pay-as-you-go at one unit per image, and failed requests are refunded[2]. Free tiers and rate limits across vendors are compared in Is the OpenAI moderation API free?.
Uploaded image moderation free api
Moderating an upload is a three-step flow: store the file privately, create a URL the API can fetch (a signed URL works while it is valid), then submit that URL and publish only after the verdict[2]. reAPI does not accept the file bytes or a base64 string directly[2]. For which services have a free allowance, see Is the OpenAI moderation API free?.
OpenAI image moderation api
Yes, OpenAI's moderation model handles images. omni-moderation-latest accepts text and image inputs, does not classify audio, and scores images for the sexual, self-harm and violence categories only[1]. The model ID on reAPI is the same, omni-moderation-latest, behind an async task endpoint[2].
Image moderation api python
The Python example above is complete: it posts an image_url block to /api/v1/moderations, polls /api/v1/tasks/{id}, and returns output.moderation.results[0]. It needs only the requests package. The API docs show the same call in cURL, Node.js and Go.
Image moderation api react
Call the moderation API from your backend, not from a React component. Your React app uploads the file to your server or storage, your server calls the API with the key, and the client receives only the decision. Putting the Bearer key in browser code exposes it to every visitor.
Can ChatGPT API analyze images?
Yes. OpenAI's vision-capable models analyze images through the Responses API and the Chat Completions API, from a URL, a base64 data URL or a file ID[16]. That is open-ended image understanding. For a fixed set of harm categories with 0–1 scores, the moderation endpoint is the purpose-built option[1].
Best image moderation api
It depends on what you need to detect. A general model such as omni-moderation-latest covers sexual, self-harm and violence content in images and adds text categories for captions[1]; image-specialist services cover more visual classes (see the table above). The full selection guide is in Best content moderation API.
Wiring image moderation into an upload flow
A working image moderation API setup is short: upload to private storage, moderate the URL, act on the scores, then publish. With omni-moderation-latest, remember that only six categories look at pixels, so add a caption or OCR text to the request if text inside images matters to you, and keep a separate control for child safety. Use flagged to start, move to per-category thresholds once you have labeled data, and recheck those thresholds when the model changes.
To try it, paste an image URL into the playground on the Content Moderation model page, then move the Python or JavaScript example above into your backend. The API docs list every field this image moderation API accepts.
References
- OpenAI. Moderation. Retrieved October 2026 from developers.openai.com/api/docs/guides/moderation
- reAPI. omni-moderation-latest API docs. reapi.ai/docs/content-moderation
- OpenAI. Moderations API reference. Retrieved October 2026 from developers.openai.com/api/reference/resources/moderations
- reAPI. Tasks API reference. reapi.ai/docs/api/tasks
- Sightengine. Visual Moderation models. Retrieved October 2026 from sightengine.com/docs/models
- Sightengine. What are the ways to send an image to the API? Retrieved October 2026 from sightengine.com/faq/ways-to-send-image-to-api
- Sightengine. What is an operation? Retrieved October 2026 from sightengine.com/faq/what-is-an-operation
- Hive. Visual Moderation - Overview. Retrieved October 2026 from docs.thehive.ai/docs/visual-content-moderation
- Hive. Using Hive's Visual Moderation API. Retrieved October 2026 from docs.thehive.ai/docs/visual-moderation-api
- Microsoft. Image Operations - Analyze Image (REST API). Retrieved October 2026 from learn.microsoft.com/en-us/rest/api/contentsafety/image-operations/analyze-image
- Microsoft. Harm categories in Azure AI Content Safety. Retrieved October 2026 from learn.microsoft.com/en-us/azure/ai-services/content-safety/concepts/harm-categories
- Microsoft. Azure AI Content Safety pricing. Retrieved October 2026 from azure.microsoft.com/en-us/pricing/details/content-safety
- Amazon Web Services. Using the image and video moderation APIs. Retrieved October 2026 from docs.aws.amazon.com/rekognition/latest/dg/moderation-api.html
- Amazon Web Services. DetectModerationLabels. Retrieved October 2026 from docs.aws.amazon.com/rekognition/latest/APIReference/API_DetectModerationLabels.html
- Amazon Web Services. Amazon Rekognition pricing. Retrieved October 2026 from aws.amazon.com/rekognition/pricing
- OpenAI. Images and vision. Retrieved October 2026 from developers.openai.com/api/docs/guides/images-vision
Author

Categories
.mjs module)Reading the result and setting thresholdsHow image-specialist moderation APIs differFAQImage moderation api freeUploaded image moderation free apiOpenAI image moderation apiImage moderation api pythonImage moderation api reactCan ChatGPT API analyze images?Best image moderation apiWiring image moderation into an upload flowReferencesMore Posts

Unbelievable: Run Kimi K3 — 2.8 Trillion Parameters on a Single 4GB GPU
Can Kimi K3 really run on one 4GB GPU? Learn the 1.4TB weight math, what layer offloading changes, current tool support, and the practical API route today.


NSFW Video API Guide: Seedream 5.0 Pro to Seedance, Wan, H3
Build a reliable NSFW video API workflow: first create adult-only keyframes in Seedream 5.0 Pro, animate with Seedance, Wan, or H3, and add consent checks.


MiniMax H3 vs Seedance 2.5: Which AI Video Model Wins?
Compare MiniMax H3 vs Seedance 2.5 on duration, 2K output, native audio, multimodal references, editing, pricing, API access, and best use cases.
