Translation API: Translate Transcripts into 86 Languages | WhisperAI
The WhisperAI Developer API now translates any completed transcript into up to 86 languages with a single call. Endpoint, response shape, per-speaker output, and pricing from $0.0005 per minute.
One endpoint, as many languages as you need
Translation runs on a transcript you already have. Pass the transcript ID and the languages you want:
POST /v1/transcript/{transcript_id}/translateThe body takes the target languages you want, plus two optional flags. Ask for two languages or all 86 in the same call.
curl -X POST https://api.whisperai.com/v1/transcript/tr_8f1c92/translate \
-H "Authorization: wai_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"target_languages": ["es", "de"],
"formal": true,
"force": false
}'target_languages— an array of language codes, any number of them.formal— chooses formal or informal register. It is the difference between usted and tú, or Sie and du: the right choice depends on whether you are localizing a compliance recording or a creator's podcast.force— off by default. A requested target language that matches the transcript's detected source language is skipped rather than translated, so you can send a fixed language list across a whole library without paying to translate English into English. Setforce: truewhen a recording opens in one language and continues in another, or is genuinely multilingual throughout, and you want the pass to run anyway.
What comes back
The response is a separate translation resource with a tl_… ID, the source transcript_id, billing quantity, and a translated_texts map keyed by language code:
{
"id": "tl_8f1c92",
"transcript_id": "tr_source92",
"status": "completed",
"target_languages": ["es", "de"],
"translated_texts": {
"es": "El humo de cientos de incendios forestales está activando alertas de calidad del aire.",
"de": "Rauch von Hunderten von Waldbränden löst Luftqualitätswarnungen aus."
},
"billed_seconds": 7200
}Per-speaker translations
If the original transcript was created with speaker_labels, every entry in utterances comes back with its own translated_texts, keeping the speaker attribution and the millisecond timestamps intact:
{
"id": "tl_8f1c92",
"transcript_id": "tr_source92",
"utterances": [
{
"speaker": "A",
"start": 240,
"end": 26560,
"text": "Smoke from hundreds of wildfires is triggering air quality alerts.",
"translated_texts": {
"es": "El humo de cientos de incendios forestales está activando alertas de calidad del aire."
}
}
]
}This is the part worth designing around. Because each utterance keeps its own start and end times, building a speaker-attributed subtitle track in another language is a loop over an array rather than an alignment project — you already have the cue boundaries, you are only swapping the text. If you are producing caption files, pair it with our SRT export guide.
Transcribe first, then translate
Translation runs on a completed transcript. Keeping the two steps separate means you can review or correct a transcript before spending anything on translation, translate an archive you captured months ago without re-processing the audio, and add a language later without touching the original job.
const API = "https://api.whisperai.com/v1";
const headers = {
Authorization: "wai_your_api_key_here",
"Content-Type": "application/json",
};
// 1. Transcribe. speaker_labels is what unlocks per-speaker translations later.
const job = await fetch(`${API}/transcript`, {
method: "POST",
headers,
body: JSON.stringify({
audio_url: "https://acme.com/all-hands.mp3",
speech_model: "whisperai-universal",
speaker_labels: true,
}),
}).then((r) => r.json());
// 2. Wait for it to finish (or skip the polling with a webhook_url).
let transcript;
do {
await new Promise((resolve) => setTimeout(resolve, 3000));
transcript = await fetch(`${API}/transcript/${job.id}`, { headers }).then((r) => r.json());
} while (transcript.status === "queued" || transcript.status === "processing");
// 3. Translate the finished transcript.
const translated = await fetch(`${API}/transcript/${job.id}/translate`, {
method: "POST",
headers,
body: JSON.stringify({ target_languages: ["es", "fr", "ja"], formal: true }),
}).then((r) => r.json());
console.log(translated.translated_texts.ja);
for (const utterance of translated.utterances ?? []) {
console.log(utterance.speaker, utterance.start, utterance.translated_texts.fr);
}Pricing
Translation is billed on the duration of the audio multiplied by the number of target languages, pro-rated to the second.
| Plan | Per minute of audio, per target language |
|---|---|
| Pay as you go | $0.001 |
| Developer plan | $0.0005 |
Worked example
A 30-minute file translated into 3 languages is 30 minutes × 3 = 90 translation-minutes. On pay as you go that is $0.09; on the Developer plan, $0.045.
Two things that keep the bill honest:
- Skipped and failed translations are not billed. Billing counts the languages actually returned in
translated_texts, not the number you requested. Ask for["en", "es"]on an English transcript and you are billed for one language, not two. - Translation does not consume the Developer plan's 10,000 included minutes. Those minutes are for transcription; translation is metered separately at the rate above.
Language coverage
86 languages are supported as translation targets. The API actually accepts 89 codes, because English is available in four regional variants — en, en_au, en_uk, and en_us — which is why the code count and the language count differ. The full list lives in the API reference.
What developers are building with it
Multilingual subtitles
Transcribe once with speaker labels, translate into every release language, and emit one subtitle track per language from the returned utterances.
Podcast localization
Publish translated show notes and transcripts alongside each episode without a second pass over the audio.
Support and sales calls
Give international teams a readable, speaker-attributed version of a call recorded in a language they do not speak.
Two more that come up constantly: multilingual meeting notes, where a single recording becomes a readable record for every office; and educational content, where a lecture recorded once reaches students who do not share the instructor's language. For the live, in-the-moment side of the same problem, see Breaking Language Barriers: Real-Time Translation for Global Teams.
Notes before you ship
- Turn on
speaker_labelswhen you create the transcript if you want per-speaker translations — the translate call mirrors whatever the original transcript contains. - Batch every language you need into one request against the transcript ID, keeping in mind that each extra language adds its own translation-minutes to the bill.
- Decide
formalper content type, not per account — legal and support material usually wantstrue, creator content usually does not. - Leave
forceoff unless you are knowingly handling mixed-language audio; the default skip is what stops you paying for no-op translations.
Start translating
Request and response schemas, the full language list, and error codes are in the API reference.