Batch Transcribe Thousands of Audio Files at Once in Python
Large-scale audio transcription converts thousands of audio files into accurate, searchable text quickly. Process hours of content efficiently with batch tools.



Large-scale audio transcription converts thousands of pre-recorded audio files into accurate, searchable text concurrently rather than one at a time. Because the work runs asynchronously, total completion time is governed by your longest file—not the sum of all of them—so an entire podcast catalog or years of call recordings can finish in roughly the time it takes to process a single file. Jobs run with unlimited concurrency and no rate limits, so throughput is bounded by how fast you can submit rather than by a queue we impose. Unlike real-time transcription, which processes live audio streams, batch transcription prioritizes throughput over latency.
This guide shows you how to architect and implement production-ready batch transcription in Python against the current Speech-to-Text API. You'll learn when batch processing beats real-time, how to optimize audio for accuracy, and how to build resilient systems that handle thousands of concurrent jobs—including diarization, confidence scoring, export, and error handling that scales from hundreds of files to millions.
What is large-scale audio transcription and when do you need it?
Large-scale audio transcription is the practice of transcribing thousands of pre-recorded audio files simultaneously instead of sequentially. This batch approach handles entire audio libraries—from years of customer service recordings to complete podcast catalogs—with total completion time determined by your longest file, the key benefit of an asynchronous architecture.
You need batch processing when sequential processing creates unacceptable delays. The indicators are boring and reliable:
- Volume threshold: you're processing 100+ audio files regularly
- Time constraints: you need results in minutes, not days
- Business applications: media asset management, call center analytics, podcast transcription, compliance review
Batch systems eliminate bottlenecks by processing all jobs concurrently. For a deeper comparison of when each model fits, see real-time speech-to-text and the broader speech-to-text guide.
What architecture handles hours of audio efficiently?
The core idea is asynchronous processing versus synchronous processing. Synchronous processing is like washing dishes one by one—you finish one completely before starting the next. Asynchronous processing is like loading a dishwasher: you put everything in at once and it all gets cleaned in parallel.
Here's what makes async batch transcription work:
- Concurrent job submission: upload and start processing thousands of files at the same time
- Status monitoring: check which jobs are done without stopping the ones still running
- Result collection: gather transcripts as they finish, in any order
- Error handling: retry failed jobs without affecting successful ones
You have two ways to track progress: polling and webhooks. Polling means you periodically check job status yourself. Webhooks mean the service notifies you when jobs complete.
The SDK's transcribe call polls for you. Webhooks are the robust alternative for custom or event-driven architectures—they eliminate periodic checks by notifying your application the moment jobs complete, which is usually more efficient once your pipeline is running unattended.
Audio preprocessing for optimal transcription accuracy
Preprocessing improves accuracy and reduces the number of transcripts you end up reviewing by hand. Two areas matter.
File format. Compressed formats like MP3 and M4A balance quality and file size for most use cases. Lossless formats like FLAC and WAV preserve maximum fidelity when storage isn't a concern. In practice the difference is small compared to what the microphone did.
Audio quality. Apply noise filtering before submission where you can, and process separate channels individually when you have them—dual-channel call recordings give you speaker separation for free, before diarization is even involved. If you don't have channels, speaker diarization handles it on the model side.
How to implement async batch transcription in Python
You'll build this in three steps. Every sample below targets Python SDK 1.0.0, which shipped August 14, 2026 and unified the async, streaming, and sync clients behind one shape—pre-recorded work now imports from assemblyai.prerecorded.v2. If you're on a 0.6x release, upgrade before copying any of this; the import surface changed.
Set up the AssemblyAI Python SDK and authenticate
Install the SDK:
pip install "assemblyai>=1.0.0"Set your API key as an environment variable rather than in code:
export ASSEMBLYAI_API_KEY="your_api_key_here"Then create your script and configure the client:
import os
import threading
from assemblyai.prerecorded.v2 import Transcriber, TranscriptionConfig
transcriber = Transcriber(api_key=os.environ["ASSEMBLYAI_API_KEY"])
config = TranscriptionConfig(
speech_models=["universal-3-5-pro", "universal-2"],
speaker_labels=True,
punctuate=True,
format_text=True,
)Never put your API key directly in your code. Environment variables keep the credential out of your source history, which matters more than it sounds like it does the first time a repo goes public.
Submit and transcribe your batch of audio files
The documented multi-file pattern is a thread per file around a shared Transcriber. Each thread submits one job and blocks until that job finishes, so the whole batch runs concurrently and your wall-clock time tracks the slowest file rather than the sum.
audio_urls = [
"https://example.com/calls/0001.mp3",
"https://example.com/calls/0002.mp3",
# ... thousands more
]
transcripts = [None] * len(audio_urls)
def transcribe_one(url, index):
try:
transcripts[index] = transcriber.transcribe(url, config=config)
except Exception as exc: # network, auth, unreachable URL
transcripts[index] = exc
threads = [
threading.Thread(target=transcribe_one, args=(url, i))
for i, url in enumerate(audio_urls)
]
for t in threads:
t.start()
for t in threads:
t.join()
for url, result in zip(audio_urls, transcripts):
if isinstance(result, Exception):
print(f"submission failed: {url}: {result}")
elif result.status == "error":
print(f"transcription failed: {url}: {result.error}")
else:
print(f"{url}: {len(result.text.split())} words")The try/except inside the worker is the important part. Catch per file and store the exception in the results slot, and one unreachable URL in ten thousand costs you one transcript instead of the whole run. At genuinely large batch sizes, put the work through a bounded thread pool rather than starting one OS thread per file—the API doesn't mind, your machine will.
What the configuration is doing:
- speech_models=["universal-3-5-pro", "universal-2"]: prioritizes Universal-3.5 Pro, which covers 18 languages with native code-switching, and falls back to Universal-2 ($0.15/hr, 99 languages) for anything outside the flagship's 18. Universal-3.5 Pro costs $0.06/hr more, and what that buys is code-switching, better diarization, and contextual prompting.
- speaker_labels=True: identifies who's speaking when.
- punctuate=True and format_text=True: punctuation and sentence casing.
Export transcripts in multiple formats
Write each completed transcript out in whatever your downstream system consumes:
from pathlib import Path
out = Path("transcripts")
out.mkdir(exist_ok=True)
for index, result in enumerate(transcripts):
if isinstance(result, Exception) or result.status == "error":
continue
stem = f"{index:05d}"
(out / f"{stem}.txt").write_text(result.text)
(out / f"{stem}.srt").write_text(result.export_subtitles_srt())
(out / f"{stem}.vtt").write_text(result.export_subtitles_vtt())Skipping the failures rather than crashing on them is the whole difference between a script and a pipeline.
How much does it cost to transcribe one hour of audio?
One hour of audio costs the per-hour list rate for the model you selected, billed per second with no minimums. Universal-3.5 Pro, the flagship async model, is $0.21/hour; Universal-2 is $0.15/hour. Because billing is duration-based, a thousand hours of audio costs the same whether you process it in one batch or a hundred—concurrency affects speed, not price. Current rates for every model and add-on are on the pricing page.
How to plan throughput and cost for large batches
Planning a batch means understanding two things: how long it takes and how much it costs. The good news on the first is that processing time barely increases with more files.
Because jobs run in parallel rather than in sequence, your wall-clock time tracks your longest single file rather than the sum of the batch. A thousand files and a hundred files finish in roughly the same window if the longest file in each is the same length.
Concurrency is unlimited and there are no rate limits, so the shape of your batch matters more than its size. One distinction worth internalizing while you plan: concurrency and traffic shaping are separate mechanisms. They get conflated constantly, and they behave differently.
That combination—accuracy that holds at operational volume, and throughput you don't have to negotiate for—is what teams processing large libraries are actually buying. Sabba Keynejad and Tim Mamedov of Veed put the benefit in terms of what it freed them to do:
"Assembly allowed our team to focus on what they are best at: Building a collaborative, browser-based video editor and distributing that product at speed and at velocity to our user base."
Here's how to estimate cost before you submit anything:
FLAGSHIP_ASYNC_RATE = 0.21 # USD per hour of audio, Universal-3.5 Pro
def estimate_batch_cost(durations_seconds, rate_per_hour=FLAGSHIP_ASYNC_RATE):
"""durations_seconds: iterable of per-file audio durations."""
total_hours = sum(durations_seconds) / 3600
return round(total_hours * rate_per_hour, 2)
print(estimate_batch_cost([3600] * 100)) # 100 one-hour files
print(estimate_batch_cost([1800] * 1000)) # 1,000 half-hour filesWorth knowing where you sit relative to everyone else solving this. In AssemblyAI's 2026 Voice Agent Report, built on 455 responses fielded across Q4 2025 and Q1 2026, 62% of builders named cost-effectiveness as a primary decision driver, and 42.5% named high costs as an active challenge. At batch scale, the difference between those two groups is usually configuration rather than pricing.
Cost optimization, in rough order of how much money it saves:
- Only enable what you need. Diarization, keyterms prompting, and Speech Understanding features are separate add-ons. They're inexpensive individually and they add up across a million files.
- Use the right model. Universal-3.5 Pro is the highest-accuracy option at $0.21/hr and covers 18 languages with native code-switching. Universal-2 is $0.15/hr and remains the fallback for the long tail of 99 languages—worth routing to deliberately if a chunk of your corpus is outside the flagship's languages.
- Group similar content. Processing similar audio types together keeps your configuration consistent and your quality checks meaningful.
- Build in retry logic. Wrap submission in your own retry for network and file-access failures. The SDK handles transient API errors; it can't fix an S3 bucket policy.
If your data can't leave Europe, the same pipeline runs against api.eu.assemblyai.com at the same price, with audio and transcripts staying in the EU.
Advanced configuration options for production transcription
Production systems need more than the defaults.
Accuracy optimization. Keyterms prompting boosts recognition of specific terms, names, and industry jargon—up to 1,000 terms on the Universal Pro line, 200 on Universal-2, for +$0.05/hr. Natural language prompting lets you hand the model context and instructions instead of a term list, which works better for tone, formatting, and domain framing.
You can set both on the same request—they do different jobs. keyterms_prompt tells the model which exact strings to expect; prompt tells it what kind of audio this is. One caveat if you route through Pipecat: that plugin does not accept both in a single request, so scope your config per integration rather than assuming the async behavior carries over.
Diarization tuning. If you know how many speakers are in a recording, speakers_expected improves accuracy. When the count varies across your corpus—which it usually does—speaker_options=SpeakerOptions(min_speakers_expected=2, max_speakers_expected=6) gives the model a range instead of a wrong guess.
Quality control. Flag transcripts below a confidence threshold for human review rather than trusting the whole batch equally, and route anything with an error status into a retry queue automatically.
For summaries, action items, and chapters, don't reach for the legacy transcription parameters. auto_chapters and summarization are Universal-2 only, and on Universal-3.5 Pro they cause silent 500 errors—the request fails, and nothing in the response tells you which parameter did it. Use the speech_understanding request object through the Speech Understanding API for summaries and action items, and LLM Gateway for chapters and anything custom. We walk through the pattern in summarizing audio and video at scale.
Monitoring and debugging large-scale transcription workflows
At a thousand files, failures stop being exceptions and start being a rate. Plan for them.
Common error patterns:
- Invalid URLs: files moved, expired pre-signed links, or bucket permissions that changed under you
- Format issues: unsupported file types or corrupted audio, usually clustered in one part of an archive
- Network timeouts: transient, and the reason exponential backoff exists
What to build: log every job ID alongside its source URL and error text, retry with exponential backoff rather than immediately, and send webhook notifications on failure so you find out from a queue rather than from a customer. And log the confidence score alongside each transcript, because at this volume the useful question stops being "did it work" and becomes "which two percent do I need to look at."
Batch, streaming, or sync: which path fits
Is batch always the right choice? No. If you're processing pre-recorded audio at volume, it's the most efficient method by a wide margin. If you need live captions or a voice agent, you need streaming instead. And if you're transcribing a short clip and don't want a polling loop at all, the Sync API returns a transcript in one request.
Hybrid architectures are common and sensible. An AI meeting assistant might run streaming transcription for live notes during a call, then run a batch job on the final recording to produce a richer transcript with diarization, plus summaries and action items through Speech Understanding. Same API key, same billing, two different latency profiles.
If you just need a tool, not a pipeline
Worth saying plainly, because a lot of people land on this page looking for something else: if you have one folder of recordings and no intention of writing Python, you don't need any of the above. Upload the files in the playground and export the transcripts. That's a legitimate answer, it takes minutes, and building a pipeline for a one-off batch is the engineering equivalent of buying a forklift to move a couch.
The pipeline earns its keep when the batch recurs—when new audio arrives weekly, when transcripts feed something downstream, or when someone other than you depends on the output being there.
What actually governs your batch
The counterintuitive thing about batch transcription at scale is that the model is almost never your bottleneck. Unlimited concurrency, no rate limits, completion governed by your longest file—the transcription itself is the fast part.
What actually governs your wall-clock time is everything around it: how quickly you can hand the API a list of reachable URLs, how you handle the two percent of files that fail for reasons that have nothing to do with speech, and how fast the thing consuming your transcripts can absorb them. Architect for that, and the transcription layer stops being a project and becomes a parameter.
Frequently asked questions
How much does it cost to transcribe one hour of audio?
AssemblyAI bills per second of audio with no minimums, so one hour of audio costs the flagship async list rate of $0.21 for that hour, or $0.15 on Universal-2. Because billing is duration-based, a thousand hours costs the same whether you process it in one batch or many—concurrency affects speed, not price. Add-ons like diarization and keyterms prompting are priced separately.
How can I transcribe 1 hour of audio quickly?
Submit it to the async API and poll for the result, or use the Sync API if the clip is short enough to want a single round trip. If you have many files, submit them concurrently rather than sequentially—total completion time is governed by your longest single file, not the sum of all of them.
Can I process more than 10,000 audio files at once?
Yes. Concurrency is unlimited and there are no rate limits, so a batch of ten thousand is a submission-throughput question on your side rather than a quota question on ours. Note that concurrency and traffic shaping are separate mechanisms, and they are easy to confuse when you are debugging a slow batch.
What happens if some audio files fail to transcribe during batch processing?
Failed jobs return an error status and message you can retry automatically without affecting successful transcriptions. Routing error-status jobs to a retry queue with exponential backoff is the standard production pattern, and catching exceptions inside each worker thread keeps one unreachable file from taking down the whole batch.
What is the easiest way to transcribe a large batch of audio recordings?
For a one-off batch, upload the files in the playground and export the transcripts. For anything recurring, use the Python SDK with a thread per file—a few dozen lines gets you concurrent submission, polling, error handling, and export to TXT, SRT, VTT, or JSON.
Can I combine batch and real-time transcription in the same system?
Yes, and many teams do. Use streaming transcription—Universal-3.5 Pro Realtime is the highest-accuracy real-time option—for live captions or voice agents during a call, then run a batch job on the final recording for richer summaries and analytics. Both paths run on the same platform, so they share API keys, billing, and tooling.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.


