Moderations
Moderate thousands of texts or images in one batch with batch.moderations — normalized flagged verdicts, category flags, and confidence scores per request.
Submit a moderation batch
batch.moderations() mirrors batch(): pass a moderation model and a list of inputs, and it returns the same BatchJob handle immediately. Each request produces one verdict on result.moderation, correlated by customId. Moderation is the most batch-shaped workload there is — huge volumes, latency-insensitive, per-item results — and the batch rate makes an already-cheap endpoint cheaper still.
import { batch } from "batchwork";
const job = await batch.moderations({
model: "openai/omni-moderation-latest",
requests: [
{ customId: "a", value: "What a lovely day for a picnic." },
{ customId: "b", value: "…user-generated content…" },
],
});
const results = await job.wait().then(() => job.collect());
for (const r of results) {
console.log(r.customId, r.moderation?.flagged);
}
Everything on the job handle works unchanged — wait(), poll(), results(), collect(), and cancel() — as does rehydration with getBatch / getBatchResults / cancelBatch.
Supported providers
Batch moderation is available for the providers whose batch API accepts a moderation endpoint:
| Provider | Example model | Notes |
|---|---|---|
| OpenAI | omni-moderation-latest |
/v1/moderations. Text and/or images per request. |
| Mistral | mistral-moderation-latest |
/v1/moderations, model set on the job. Text-only. |
No other provider has a standalone moderation API; calling batch.moderations() with any of them throws UnsupportedProviderError before any network request. (Guard models like Llama Guard are chat models — run those through batch().)
Request shape
Each request is a value (text), imageUrls (OpenAI omni moderation only), or both:
| Field | Notes |
|---|---|
value |
The text to moderate. |
imageUrls |
Image URLs moderated alongside (or instead of) value. OpenAI only — Mistral throws before submitting. |
customId |
Correlates the result. Auto-generated as request-<index> if omitted; must be unique within a batch. |
providerOptions |
Provider-specific fields merged into the request body. |
limits and metadata work exactly as in batch().
Results
Moderations reuse the normalized BatchResult. The verdict lands on result.moderation:
interface BatchModeration {
flagged: boolean; // provider's flag (OpenAI), or any category flag (Mistral)
categories: Record<string, boolean>; // provider-native category names
categoryScores: Record<string, number>;
}
Category names are provider-native — OpenAI reports sexual, hate, violence, self-harm/intent, …; Mistral reports sexual, hate_and_discrimination, violence_and_threats, pii, financial, law, … — so thresholding logic is per-provider by design. OpenAI includes its own top-level flagged boolean; Mistral doesn’t, so Batchwork computes it as “any category flagged”.
for await (const result of job.results()) {
if (result.status === "succeeded" && result.moderation?.flagged) {
const hits = Object.entries(result.moderation.categories)
.filter(([, flagged]) => flagged)
.map(([category]) => category);
quarantine(result.customId, hits);
}
}
text, embedding, and images are undefined for moderation batches; the full provider payload (e.g. OpenAI’s category_applied_input_types) is on result.response.
Images (OpenAI omni moderation)
OpenAI’s omni moderation models accept images. Pass imageUrls — with or without accompanying text — and Batchwork builds the content-part input for you:
await batch.moderations({
model: "openai/omni-moderation-latest",
requests: [
{
customId: "upload-42",
value: "profile photo caption",
imageUrls: ["https://example.com/uploads/42.png"],
},
],
});
The URLs must be reachable by OpenAI while the batch processes. Mistral moderation is text-only; requests with imageUrls throw before any network call.
How it’s built
Like transcriptions, moderation bodies are built directly rather than captured from an AI SDK call (there is none to capture). Both providers take { input } — a bare string, or OpenAI’s content-part array when images are involved; OpenAI carries the model on each line while Mistral sets it on the job. Everything downstream — JSONL upload, job lifecycle, result parsing — reuses each provider’s standard batch flow.