Audio moderation
Transcribe an audio clip and screen the transcript for prohibited content
Audio moderation transcribes the speech in an audio clip and runs the transcript through the same checks as text moderation.
Audio moderation checks speech only. It transcribes the words in the clip and checks those words. It does not classify music, sound effects, tone of voice or any other sound. Sung words are speech and are checked.
Clips with no speech#
A clip with no speech in it passes. Music without words, sound effects, background noise and
silence give no words to check, so the job completes with is_prohibited: false. It does not fail.
Input#
Submit a multipart/form-data request with:
| Field | Type | Required | Description |
|---|---|---|---|
audio |
string | Yes | Publicly reachable HTTP or HTTPS URL of the clip to scan, up to 100 MB. The API downloads it. |
webhook_url |
string | No | URL to receive the result on completion. |
curl -X POST https://api.omnifence.ai/api/v1/moderate/audio \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "audio=https://storage.example.com/recordings/call.mp3"
The audio field must be a public HTTP or HTTPS URL. URLs using another scheme, or resolving to a
private or internal network address, are rejected with 400 INVALID_REQUEST.
Formats#
mp3, m4a / mp4 audio, wav, flac, ogg (including Opus), webm and aac. The container is
verified server-side; a file whose contents don't match a supported audio container is rejected
before transcription. The job fails with error_code UNSUPPORTED_MEDIA, and so does a file with
no audio track or one that cannot be read. A URL the origin refuses (for example HTTP 404 or an
expired signed link) fails with MEDIA_UNREACHABLE. A webm recorded in a browser with
MediaRecorder is supported, also when its header states no duration.
Limits#
| Limit | Value |
|---|---|
| Size | 100 MB |
| Duration | 30 minutes |
Both are hard limits. A clip over the duration cap fails after the initial probe, before any transcription cost is incurred.
Lossless long-form audio hits the 100 MB size cap well before the 30-minute duration cap. A 30-minute WAV is roughly 300 MB. Compress to a lossy format (for example 64 kbps mono MP3, about 14 MB for 30 minutes) before submitting. Transcription quality is unaffected.
How it's checked#
The clip is transcribed, and the transcript is checked against the prohibited-content policy and
every enabled custom category on your account. A
prohibited transcript rejects the job, with a reason explaining what tripped. When a custom
category caused the rejection, the reason names it by slug.
Output#
{
"is_prohibited": false,
"scores": { "violence": 0.02 },
"job_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"type": "audio",
"status": "completed",
"created_at": "2026-08-03T12:00:00.000Z",
"completed_at": "2026-08-03T12:00:41.000Z"
}
On a rejection, a reason field explains what tripped. See decision logic
for how the prohibition decision is determined.
scores and custom_scores are optional, informational
risk scores per category. They come from the check on the
transcript and use the same keys as text moderation. They never change the decision, and they never
contain any of the transcript.
Transcript privacy#
The transcript is never stored and never returned. The rejection reason is model-generated text, scrubbed of verbatim quotation on a best-effort basis, and may paraphrase what was said.
Concretely: a transcript exists only in short-lived, automatically-expiring storage for the duration
of the job, and is deleted when the job finishes. No transcript is written to the job record,
returned on the job response, or included in a webhook delivery. The reason on a rejection is
checked server-side for verbatim excerpts of the transcript, which are replaced with a generic
category description. But a short excerpt or a paraphrase can still appear. If your content is
sensitive enough that even a paraphrase is unacceptable, surface only is_prohibited to your users
and discard reason.
Latency#
Audio moderation is slower than the other modalities: transcription time scales with clip length.
Register a webhook rather than polling tightly. The job reports queued for
the whole run and then flips straight to a terminal status, with no incremental progress to observe,
so frequent polling only burns rate limit.