Skip to content
Batchwork
Esc
navigateopen⌘Jpreview
On this page

Transcriptions

Transcribe thousands of audio files in one batch with batch.transcriptions — hosted audio URLs in, normalized transcripts and timestamped segments out.

Submit a transcription batch

batch.transcriptions() mirrors batch(): pass a transcription model and a list of hosted audio URLs, and it returns the same BatchJob handle immediately. Each request produces one transcript, correlated by customId.

import { batch } from "batchwork";
import { groq } from "@ai-sdk/groq";

const job = await batch.transcriptions({
  model: groq.transcription("whisper-large-v3"),
  requests: [
    { customId: "a", audioUrl: "https://example.com/interview.wav" },
    { customId: "b", audioUrl: "https://example.com/standup.mp3" },
  ],
});

const results = await job.wait().then(() => job.collect());
for (const r of results) {
  console.log(r.customId, r.text);
}

Everything on the job handle works unchanged — wait(), poll(), results(), collect(), and cancel() — as does rehydration with getBatch / getBatchResults / cancelBatch.

You can also pass a "provider/model" string — the only form for Mistral ("mistral/voxtral-mini-latest") and Together ("together/openai/whisper-large-v3"), whose AI SDK packages have no transcription model yet.

Supported providers

Batch transcription is available for the providers whose batch API accepts an audio-transcription endpoint:

Provider Example model Notes
Groq whisper-large-v3 /v1/audio/transcriptions, audio by url.
Mistral voxtral-mini-latest /v1/audio/transcriptions, audio by file_url, model set on the job.
Together AI "together/openai/whisper-large-v3" /v1/audio/transcriptions, audio by file, method: "FILE" lines.

OpenAI’s Batch API does not accept its audio endpoints, and the remaining providers have no standalone transcription API. Calling batch.transcriptions() with any of them throws UnsupportedProviderError before any network request. (Gemini models can still transcribe audio through batch() by passing audio file parts in messages.)

Request shape

Each request is a hosted audioUrl plus an optional customId and a few knobs:

Field Notes
audioUrl Publicly reachable URL of the audio file to transcribe.
customId Correlates the result. Auto-generated as request-<index> if omitted; must be unique within a batch.
language Language of the audio as an ISO-639-1 code (e.g. "en"). Improves accuracy and latency.
timestampGranularities ["segment"] and/or ["word"]. Populates result.segments and switches Groq to verbose_json.
providerOptions Provider-specific fields merged into the request body (e.g. { groq: { prompt, temperature } }).

limits and metadata work exactly as in batch(), and defaults are merged into every request (request-level values win) — handy for applying one language across the whole batch.

Translations

batch.translations() runs Whisper’s translate task instead: audio in any language, English text out. It mirrors batch.transcriptions() exactly, minus the language field (English is the only target):

const job = await batch.translations({
  model: groq.transcription("whisper-large-v3"),
  requests: [
    { customId: "a", audioUrl: "https://example.com/french-interview.wav" },
  ],
});

Translations are supported for Groq (whisper-large-v3 only — the turbo variant can’t translate) and Together AI ("together/openai/whisper-large-v3"). Mistral batches transcriptions but has no translations endpoint, so it throws UnsupportedProviderError here. The English text lands on result.text, with result.segments available via timestampGranularities as usual.

Results

Transcriptions reuse the normalized BatchResult. The transcript lands on result.text; when timestamps were requested, result.segments holds { text, startSecond?, endSecond? } spans:

const job = await batch.transcriptions({
  model: groq.transcription("whisper-large-v3"),
  requests: [
    {
      customId: "a",
      audioUrl: "https://example.com/interview.wav",
      timestampGranularities: ["segment"],
    },
  ],
});

for await (const result of job.results()) {
  if (result.status === "succeeded") {
    for (const segment of result.segments ?? []) {
      console.log(`[${segment.startSecond}s] ${segment.text}`);
    }
  } else if (result.status === "errored") {
    console.error(result.customId, result.error?.message);
  }
}

The raw provider payload — detected language, duration, word-level timestamps — is on result.response. embedding and images are undefined for transcription batches, and usage is normalized where the provider reports it, billed at the batch rate (~50% off).

How it’s built

Unlike the other modalities, transcription bodies are not derived through the AI SDK’s capturing fetch — the synchronous transcription APIs (and the AI SDK’s transcribe) upload audio as multipart form data, while the batch endpoints take JSON with a hosted URL instead. Batchwork builds each provider body directly: { model, url, … } for Groq, { file_url, … } for Mistral (model on the job). Everything downstream — JSONL upload, job lifecycle, result parsing — reuses each provider’s standard batch flow.

Was this page helpful?