Speech to text

Upload an audio file and get a transcript back — language auto-detected (falling back to Persian), m4a and webm handled without conversion.

POST /v1/audio/transcriptions takes one audio file and returns its text — the same shape OpenAI's Whisper endpoint uses, multipart form and all, so an existing client library already knows how to call it.

POST/v1/audio/transcriptions
ParameterTypeDescription
file
Required
fileThe audio. Any container ffmpeg reads: m4a, mp3, wav, webm/opus, and more.
model
Optional
stringThe deployment name. The engine underneath is Whisper large-v3 — see below for why that's the default rather than the Persian fine-tune that wins on short clips.
Default: data-stt
language
Optional
stringAn ISO 639-1 code (fa, en, es, …) or a Whisper language name (persian, english, spanish, …), case-insensitively. Left unset — or set to `auto` — the audio is auto-detected from up to its first 30 seconds; detection only overrides the Persian default when it is both confident and not one of Persian's own confusables (Arabic, Urdu, Pashto, Tajik), so real Persian audio is never misdetected into one of its close neighbours. An unrecognised value is a 400, not a silent guess.
Default: auto
response_format
Optional
"json" | "text"json returns {text, duration, language, usage, words, segments} — the gateway normalises this to the OpenAI transcription shape, so words and segments are always null here; there is no verbose_json. text returns the transcript as a plain body.
Default: json
fusion
Optional
booleanReconcile three Whisper variants instead of running one — see below. Roughly doubles the time, and is worth it for anything unattended. The fused result comes back as text like any other request; the individual per-model candidates the service computes along the way are not surfaced through this gateway endpoint.
Default: false
Transcribing a file
curl -s https://api.data.larsima.com/v1/audio/transcriptions \
  -H "Authorization: Bearer $DATA_API_KEY" \
  -F file=@voice.m4a \
  -F model=data-stt \
  -F language=persian \
  -F response_format=json

# {"text":"مهلت تجدیدنظرخواهی از رأی دادگاه بیست روز است.",
#  "duration":4.58,"language":"fa","usage":null,"words":null,"segments":null}

The phone recording that usually breaks this

A voice note from a phone is m4a, and m4a keeps its index in a trailing atom at the *end* of the file — a decoder has to seek backwards to find it. Fed through a pipe, ffmpeg cannot seek, exits 0 having produced nothing, and the request "succeeds" with an empty transcript and no error anywhere. This service always writes the upload to a temp file first and decodes from that, specifically so this doesn't happen. Send the file exactly as your phone recorded it — m4a, webm/opus, whatever your caller produces — there is no need to convert it yourself.

Long audio: cut on silence, not on a clock

There's no length limit beyond the edge's 200MB upload cap. Long audio is cut at the quietest gaps — never longer than 25 seconds a segment, never mid-word — rather than at fixed 30-second boundaries. That distinction is not cosmetic: a fixed-window chunker on the same audio, same model, silently dropped a whole passage from the middle of a recording — 69.6% word error rate against 38.5% for the version that cuts on silence.

fusion: four ears instead of one

Set fusion=true and three Whisper variants transcribe the same audio independently; a fourth model reads all three candidates and reconstructs what none of them got right alone. Measured on short clips, this took the error rate from 34.4% to 15.6%. It roughly doubles the time, and it's off by default for that reason — but for anything unattended, it earns the cost. On one real voice note the raw transcript garbled "کلاهبرداری" (fraud) into "نکلاه برداری", and reading that garbled word changed a downstream model's answer from *the law protects you* to *you may be prosecuted*. Fusion is the guard against exactly that.

cURL
curl -s https://api.data.larsima.com/v1/audio/transcriptions \
  -H "Authorization: Bearer $DATA_API_KEY" \
  -F file=@voice.m4a \
  -F model=data-stt \
  -F language=persian \
  -F fusion=true

# {"text":"...", "duration":4.58, "language":"fa",
#  "usage":null, "words":null, "segments":null}
The service itself also computes candidates — the three raw transcripts that went into reconciling text — but this endpoint is reached through the gateway, which reshapes every response to the OpenAI transcription schema, and candidates is not part of it. Only the fused text comes back; there is currently no supported way to retrieve the individual candidates over the public API.

Why Whisper large-v3 and not the Persian specialist

A Persian fine-tune of Whisper wins the short-clip leaderboard. It also has a failure mode large-v3 doesn't: on a 63-second passage it fell into a repetition loop — "دادخواهم، دادخواهم، …" — and scored 90.7% word error rate where large-v3 scored 38.5% on the same audio. Voice notes are long. The short-clip winner was the wrong default.

A repetition guard runs on every segment
Whisper's own temperature fallback catches most loops — a looping segment has a terrible compression ratio, which triggers a retry at a higher temperature automatically. If one still gets through, that segment alone is retried once more with explicit repetition penalties, and if it still loops, the repeated run is collapsed algorithmically to a single occurrence rather than shipped as hundreds of repeats of one word. This applies to every request, fusion or not.
Model testedRead speech (Common Voice)Spontaneous speech (voice-note-like)
Whisper large-v3 — data-stt, default53.0% WER34.0% WER
Whisper large-v3-turbo67.1% WER36.9% WER
Whisper large-fa (Persian fine-tune)52.4% WER55.8% WER

Measured on hezarai/common-voice-13-fa (read) and pourmand1376/asr-farsi-youtube-chunked-30-seconds (spontaneous), the same files through every model. Note that "read" and "spontaneous" rank the models differently — a single number from either test alone would be true and misleading.

↑↓ Navigate ↵ Open esc Close