Captioned speech API: words + timings with your MP3 — Audra
POST /v2/speech/captioned is a separate endpoint that returns what /v2/speech returns — MP3 or WAV — plus word-level timestamps, delivered in the X-Captions-Json response header. One request, audio and subtitles together, which is exactly what video editors, highlight tools, and read-along UIs need.
This post is the endpoint reference: the header format, the accuracy contract, the 50k limit, and what to build on top of it.
The request
Same body as speech — model, text, voice, format, speed — sent to /v2/speech/captioned. Same 50,000-character cap. The audio comes back as the response body; the timestamps ride along in X-Captions-Json.
curl
curl https://audratalks.com/v2/speech/captioned \
-H "Authorization: Bearer sk-audra-..." \
-H "Content-Type: application/json" \
-d '{"model":"audra-essential","voice":"june","text":"Chapter one: the arrival...","format":"mp3"}' \
-D headers.txt \
--output chapter-01.mp3
Python
import json, requests
resp = requests.post(
"https://audratalks.com/v2/speech/captioned",
headers={"Authorization": "Bearer sk-audra-..."},
json={"model": "audra-essential", "voice": "june", "text": text, "format": "mp3"},
timeout=3600,
)
resp.raise_for_status()
open("chapter-01.mp3", "wb").write(resp.content)
words = json.loads(resp.headers["X-Captions-Json"])["words"]
# [{"word": "Chapter", "start": 0.0, "end": 0.6}, ...]
JavaScript
const res = await fetch("https://audratalks.com/v2/speech/captioned", {
method: "POST",
headers: { Authorization: "Bearer sk-audra-...", "Content-Type": "application/json" },
body: JSON.stringify({ model: "audra-essential", voice: "june", text, format: "mp3" }),
});
if (!res.ok) throw new Error(await res.text());
await writeFile("chapter-01.mp3", Buffer.from(await res.arrayBuffer()));
const { words } = JSON.parse(res.headers.get("X-Captions-Json"));
// [{ word: "Chapter", start: 0.0, end: 0.6 }, ...]
The timestamps and their contract
Each entry is a word with start and end in seconds. The honest caveat: these are duration-weighted estimates, not forced alignment — accurate enough for subtitles, karaoke highlighting, and chapter scrubbing, not frame-accurate lip-sync. If you need broadcast-grade sync, plan a light alignment pass on top.
What people build with it
- SRT/VTT subtitles for video voiceovers — group the word timings into caption lines, format, done.
- Read-along highlighting in listen-mode players (the a11y post pairs well).
- Highlight reels: find a phrase's timestamps and cut the clip automatically.
- Chapter markers at section boundaries for long-form audio.
Failure modes
- 413 above 50,000 characters — the captioned endpoint has the same sync cap; split long scripts at section boundaries and merge SRTs yourself.
- Header size: a 50k-character script yields a very large X-Captions-Json — some proxies cap header size; use a smaller per-request chunk size if your infrastructure truncates headers.
- 402 when out of credits — captioned renders bill like speech, with the same QA-gated behavior; X-QA-Passed still applies.
Audio-only rendering: the docs quickstart. The listen-mode player pattern: the accessibility post. Full API reference: /docs.