Article
An audio transcription API takes a finished recording and returns text, usually through a background job. The part most guides skip is that your encoding decides how long a visit can be: with Sully's 100 MiB and 24-hour caps, uncompressed 16 kHz mono audio stops at about 54.6 minutes, and 9.7 kbps or lower is the only way to fit a full 24 hours.
So the limit you'll hit is a choice you make before writing any API code. This guide follows one recorded visit from encoding to deletion. For live audio, see the real-time transcription API guide.
Key Takeaways
The size cap usually binds first. Sully's transcription docs cap uploads at 100 MiB and 24 hours. Below about 9.7 kbps the duration cap binds. Above it, the size cap does.
No published bitrate is proven for clinical audio. The evidence comes from other engines and non-clinical audio, so test on your own recordings and count errors that change meaning.
Don't auto-retry the POST. A lost response can leave a billable job running whose ID you never received, which also means you can't delete it.
Use webhooks and polling together. A webhook wakes your worker, and a polling sweep catches what it missed.
Your parser has to tolerate gaps. Results come back per channel, and
wordsandspeakerare optional.
Why encoding is the first audio transcription API decision
The 100 MiB cap (104,857,600 bytes) turns every encoding into a maximum recording length: 104,857,600 × 8 ÷ bitrate, ignoring container overhead.
Encoding | Bitrate | Longest recording in 100 MiB |
|---|---|---|
48 kHz 16-bit stereo PCM | 1,536 kbps | 9.1 minutes |
16 kHz 16-bit stereo PCM | 512 kbps | 27.3 minutes |
16 kHz 16-bit mono PCM | 256 kbps | 54.6 minutes |
Compressed, 128 kbps | 128 kbps | 1.8 hours |
Compressed or 8 kHz mu-law, 64 kbps | 64 kbps | 3.6 hours |
Compressed, 16 kbps | 16 kbps | 14.6 hours |
Compressed, 9.7 kbps | 9.7 kbps | About 24 hours |
Look at the first row: a recorder left on 48 kHz stereo WAV hits the cap in nine minutes.
The bottom row looks generous and isn't. RFC 7587 lists Opus "sweet spots" of 16-20 kbit/s for wideband speech and 8-12 kbit/s for narrowband, so a full 24-hour file lands in the narrowband range. You can only reach that cap by giving up audio quality.
What the evidence says about bitrate
The search for this article found no published study measuring a bitrate on clinical audio. What exists, none of it clinical:
Google's encoding guide says to use lossless FLAC or LINEAR16 if you control the source, and that 16 kHz suits speech. That's guidance for Google's engine.
A 2020 multichannel Opus study reports word error rate degrading by 12.6% relative at 16 kbps compared with uncompressed audio.
AmiVoice's vendor test found accuracy between 97.9% and 98.2% from 256 kbps down to 16 kbps, then 95.5% at 6 kbps, on its own engine and sample audio.
Those last two disagree at 16 kbps, and RFC 7587's numbers describe audio quality rather than recognition accuracy. They tell you where to start testing, not what to ship. So our recommendation isn't a number:
If the recording fits, send lossless. FLAC's size depends on the audio, so measure it on your own files.
If it doesn't fit, test before you compress. Encode the same reference clips at each candidate bitrate and compare meaning-changing errors. Pick the lowest bitrate that doesn't add any.
Fix capture settings, not files. Converting an MP3 to FLAC makes it bigger and restores nothing.
Don't downmix multichannel audio to save space.
-ac 1below is for single-microphone sources only.
Splitting is the last resort. OpenAI's guide warns that cutting mid-sentence can reduce accuracy. Each part also becomes its own job, timestamps need offsetting, and you shouldn't assume speaker values carry across uploads.
Upload once: calling the transcription API without duplicate work
The request is multipart, and the file field must be named audio. These examples follow Sully's transcription docs:
A 201 only means processing was accepted. Write data.id to durable storage before doing anything else.
Three details are easy to miss:
Boolean fields must be exactly
trueorfalse. Python'sstr(True)producesTrue, which doesn't meet that rule.languagedefaults toen. Store each visit's language with the recording. Language handling has its own guide.Timeouts have arithmetic behind them. The SDK reference uses a five-minute timeout because creation can exceed the one-minute default. Sending 100 MiB takes about 14 Mbps of sustained upload to finish in one minute, and about 2.8 Mbps in five. A slower clinic uplink, with a client that treats the timeout as a total deadline, will fail on the largest files.
Why not to auto-retry the POST
Sully's SDK examples set maxRetries: 0 on creation because repeating the POST can create duplicate billable work. The less obvious problem is what a lost response leaves behind.
Say the job is created and the connection drops before you read the 201. You may now have a job running with no ID. Without an ID you can't look it up, and you can't DELETE it, because deletion needs the ID. That's a possible billing duplicate and a possible orphaned transcript of a patient visit.
A lost response isn't a failure. It's an unknown, and your state machine should have a state for it.
The body never finished sending: the server never had a complete file, so resending is low risk.
The body was sent and no response came back: mark the attempt
unknownand resubmit deliberately, at most once. A missing visit transcript usually costs more than a possible duplicate, but make that decision on purpose and log it.You have webhooks: the orphan's terminal event carries its ID. An event for an ID you don't recognize, after a grace period long enough for the original POST to have been recorded, is a candidate for deletion. First confirm nothing else on the account creates transcriptions.
Handling each failure
Signal | What it means | What to do | Resend the same file? |
|---|---|---|---|
| The account is out of credits | Pause the whole queue and alert someone. Retrying per file only fills your dead-letter queue. | After adding credits |
| File over 100 MiB | Re-encode, and check size before upload next time. | No |
| Duration unreadable, or outside 0-24 hours | Inspect with | Not unchanged |
| Service unavailable | Wait at least | Yes |
| The job ended without a transcript | Log | Once, at most |
Watch the 422 if you record in the browser. Addpipe's write-up of Chrome's WebM output says non-chunked recordings have carried Duration since Chrome 140, but chunked recordings, the usual choice for long sessions, still don't. Sully's docs ask for readable duration metadata, so run your real capture path through the API before launch. A remux can write the duration into a new file:
Log x-request-id too. The docs ask for it if a 409 billing_conflict persists.
Polling vs webhooks: wait for the result properly
Poll the ID until data.status moves from pending or processing to completed or failed.
If webhooks are configured for your account, terminal events arrive as audio_transcription.succeeded or audio_transcription.failed. Run both: the webhook triggers the work, and a periodic sweep polls anything still unfinished.
Polling alone needs no public endpoint, but it does need job state in a database so it survives deploys. Choose an interval: start at a few seconds, back off toward a minute, and alert on jobs that stay unfinished longer than your measured normal.
Webhooks alone can miss an event and leave a visit silently stuck. Two things to design for:
Expect duplicates. The docs mention webhook retries, so your handler must be safe to run twice for one ID.
Your receiver now holds PHI. Events include "the same public transcription object", which on success means the transcript. Request logs, APM traces and dead-letter queues can all capture it.

From raw response to usable transcript
One result per channel
Set multichannel=true only when the source really has separate channels. You then get one entry in result.channels for each, with transcript, confidence and, optionally, words.
Record who was on which channel at capture, so you can map the results back. A channel also isn't a speaker: a relative sitting by the patient's microphone shares that channel. And a recorder that writes one microphone to both stereo channels would give you two copies of one conversation. The diarization guide covers labels versus channels.
To get one conversation order, merge by word timing and handle the case where it's missing:
Treat speaker the same way, since it's optional too.
Checks that meetings and podcasts don't need
A meeting transcript with a few wrong words is still useful. A visit transcript with one wrong word may not be. Check these directly on reference clips:
Drug names. Hydralazine and hydroxyzine have been mixed up for decades, according to reports to the Institute for Safe Medication Practices. One is a blood pressure drug and the other an antihistamine and anxiolytic, and either can fit a clinical sentence.
Negation. "Denies chest pain" and "chest pain" differ by one word and mean opposite things. Sully's word error rate article explains how to count these separately.
Long visits. A two-minute clip doesn't test a long file. Sample the last third of your longest recordings, at the encoding you'll ship.
Confidence scores. Before routing review on word-level
confidence, check on your own clips whether wrong drug names actually score low.
One detail to plan for: word_boost is a streaming option, so check how you'd pass a formulary with a recorded file. For one clinician dictating, see the dictation guide and the dictation option.
Delete the resource last
Delete only once you're sure you have everything:
Save the transcript to your own system and run your parsing and checks.
Call
DELETE, which returns204 No Content. The docs say retrying doesn't start another transcription, and a404on a retry most likely means it's already gone.Remove your other copies, such as webhook payloads, queue messages, logs and temporary audio, according to your retention policy.
Keep the source audio until step 1 passes, since it's your only way to resubmit. Sully's API page lists Zero Data Retention: it deletes the audio, transcript, and input and output tokens after processing, so save the result as soon as the job completes. The API also has a DELETE endpoint. The HIPAA guide has the questions to ask any vendor.
How other transcription APIs handle recorded files
Sully wrote this comparison, so check it against the linked docs, all read on 3 November 2026. The PCM column is arithmetic at 32,000 bytes per second, treating MB and GB as decimal. "None stated" refers to the linked page.
API | File limit | Duration limit | 16 kHz mono PCM stops at | How you get the result | After delivery |
|---|---|---|---|---|---|
100 MiB | 24 hours | 54.6 minutes (size) | Poll by ID; webhooks if configured | Fetch by ID, | |
25 MB | None stated | About 13 minutes (size) | In the response, optionally streamed as events | No job to poll | |
2 GB | None stated; processing over 10 minutes (Nova, Base, Enhanced) returns | About 17.4 hours (size) | In the response, or via | Doesn't store transcripts | |
5 GB per request; 2.2 GB through | 160 ms to 10 hours | 10 hours (duration) | Poll by ID; webhooks | Fetch by ID, delete documented |
Deepgram's callback has a clock on it. It retries up to 10 times with a 30-second delay, about five minutes in total, and Deepgram says it doesn't store transcripts. Read together, a receiver down for longer could lose the result. Not storing transcripts is also a real advantage for some teams.
AssemblyAI's webhook is the tidiest for PHI. The payload holds only
transcript_idandstatus, so no transcript text lands in your webhook logs. A4xxfrom your endpoint stops its retries, though, so a broken auth check needs a sweep behind it. Sully's event carries the transcription object, which saves a GET and widens what you have to protect.OpenAI is the simplest for short files. There's no job to track, but anything over 25 MB needs compressing or splitting. Several older OpenAI transcription models shut down on February 26, 2027, which the Whisper article covers.
Where another vendor fits better. If your recordings run past 100 MiB and you won't re-encode them, AssemblyAI and Deepgram accept far larger files. If you need vocabulary hints on uploaded files today, OpenAI documents a keywords field on gpt-transcribe. The Deepgram article lists what to check before switching.
Where Sully fits. Sully makes sense when the transcript is a step toward a clinical note, since a transcript isn't a note. Its API also offers Note Generation and Text to JSON as separate calls. The API page lists 92.2% clinical note accuracy, which scores finished notes. Billing is credit-based, and the pricing guide covers the wider market.
Frequently Asked Questions
What is an audio transcription API?
It's a service that accepts a recorded file and returns text, usually through a background job. The browser's Web Speech API, which also ranks for this search, is a different tool for speech recognition inside a web page, and MDN notes it sends audio to a server-based engine by default.
What is the best audio format for a transcription API?
If your files fit the size limit, use a lossless format at 16 kHz, such as FLAC. That follows Google's guidance, which is written for Google's engine. If you need lossy audio to fit, test candidate bitrates on your own clinical clips.
How do I transcribe an audio file that's too large for the API?
Re-encode first and split only as a last resort, because splitting multiplies job IDs, requires timestamp offsets and can reduce accuracy at the cut points.
Should I use polling or webhooks for transcription?
Use both if you can. If you can only build one, build polling, because a missed poll runs again on the next cycle.
Can Sully transcribe a 24-hour audio file?
Only if it's also under 100 MiB, which means about 9.7 kbps or lower. Test transcription quality at that bitrate before relying on it.
To try this, get an API key from the Sully API page and follow the transcription docs. Start with your longest visit at the encoding you plan to ship.
Sources
Sully, OpenAI Whisper API alternatives for medical transcription
Google Cloud, Introduction to audio encoding for Speech-to-Text
IETF, RFC 7587: RTP Payload Format for the Opus Speech and Audio Codec
Khare et al., Multi-channel Acoustic Modeling using Mixed Bitrate OPUS Compression
AmiVoice, How does speech recognition accuracy change with sampling rate and compression rate?
Pharmacy Times, Lookalike drug names lead to decades of confusion
Article
Keep reading
Article
/
Medical Dictation API, When to Use Dictation Instead of Ambient Transcription
Choose a medical dictation API when one clinician is composing a document. When it beats ambient, what it needs, and what error studies show.
Article
/
Is a Medical Transcription API HIPAA Compliant? What a BAA Does and Doesn't Cover
A medical transcription API isn't HIPAA compliant by itself. What a BAA covers, what it misses, and where PHI leaks outside the audio.
Article
/
Best Speech-to-Text APIs in 2026, Compared for Developers
Eight speech-to-text APIs compared on price, streaming, diarization, languages, self-hosting and BAAs, with picks by use case. Written by a vendor.




