How it works
Read the header. Hold the credits. Do the work. Give the rest back.
Per-minute pricing has an awkward requirement: you have to know the minutes before you can name a price. Everything about the way this runs follows from solving that honestly.
The pipeline
Five steps, and you are only charged for the fourth.
Everything before the transcription exists so the price is right, and everything after it exists so you get the difference back.
Read the header
Two ranged requests pull the container's metadata to find the real length. That is the number you are quoted.
Hold the credits
Reserved up front on that length, never on a cap. Nothing is spent yet.
Split if needed
Over 22 MB the audio is cut on frame boundaries with a three-second overlap, so no word falls in a gap.
Transcribe
Each piece is transcribed with word-level timings, then stitched back onto one timeline.
Settle the difference
Billed on the length actually transcribed. The rest goes straight back to your wallet.
The two hard parts
Knowing the length, and surviving the split.
Duration, from the file itself
There is no media toolchain running on a serverless function, so the length has to come out of the container's own metadata. Two ranged requests — the first half-megabyte and the last quarter — are enough for every format we accept.
WAV states its byte rate. MP3 carries a frame count in its Xing header. MP4 keeps a timescale in mvhd, which sits at the end of the file unless it was written for streaming — which is why the last quarter-megabyte is fetched too.
When a file genuinely will not say, you are told: the quote comes back marked as an estimate, and the charge still lands on the real length.
Splitting without losing a word
The transcription rail refuses uploads over 25 MB, which at podcast bitrates is about twenty-six minutes. Longer files are cut on MPEG frame boundaries, so every piece is a valid file on its own and every piece states its own exact duration.
Each cut overlaps the one before it by three seconds, because a hard cut lands mid-word and both sides then mis-hear it. The duplicate is removed afterwards by asking which chunk holds most of each word — so a word straddling the join survives exactly once instead of being dropped by both.
Timings are reassembled from word-level timestamps, not from segment guesses, which is what keeps a subtitle file in sync an hour in.
Exports
One transcript. Whatever file you actually needed.
Subtitles, plain text, or structured JSON — all four are the same words with the same timings, serialized differently. Exporting is free, and it stays free however many times you come back for a different one.
- .srt
- Premiere, Resolve, YouTube
- Numbered cues, comma milliseconds.
- .vtt
- The web, <track> elements
- WEBVTT header, dot milliseconds.
- .txt
- Reading and pasting
- One timestamped line per cue.
- .json
- Anything programmatic
- Segments with start, end, text.
End to end
Four calls, three of them free.
# 1 — ask what it will cost. Free, and it reads the real file.
curl -X POST https://transcribe.ounie.com/api/quote \
-H "Authorization: Bearer trx_live_…" \
-d '{"url":"https://example.com/ep-104.mp3"}'
# → { "credits": 186, "minutes": 62, "duration_estimated": false }
# 2 — submit it.
curl -X POST https://transcribe.ounie.com/api/jobs \
-H "Authorization: Bearer trx_live_…" \
-d '{"url":"https://example.com/ep-104.mp3"}'
# → 202 { "job": { "id": "8f2c…", "status": "queued" } }
# 3 — poll. Free, and polling drives the job forward.
curl https://transcribe.ounie.com/api/jobs/8f2c… \
-H "Authorization: Bearer trx_live_…"
# → { "status": "succeeded", "text": "…", "segments": [ … ] }
# 4 — take the file you needed. Also free.
curl "https://transcribe.ounie.com/api/jobs/8f2c…/export?format=srt" \
-H "Authorization: Bearer trx_live_…" -o ep-104.srt