Accessibility audio versions of web content via TTS API — Audra
Listen mode is the accessibility feature teams promise and then quietly drop: the effort of maintaining human-narrated versions of a growing content library never survives contact with the roadmap. Batch TTS fixes the maintenance problem — audio versions regenerate automatically whenever the text changes, for cents.
This post is the a11y listen-mode pipeline: what WCAG expects from audio alternatives, how to scope the back catalog, how to handle screen-reader users versus listen mode, and the cost of narrating a content library on Essential at $25/1M prepaid.
The persona: a publisher with 500 articles
A content site with 500 articles averaging 3,000 characters is 1.5M characters total — about 27 hours of finished audio. The full back catalog renders in one batch run; new articles render on publish via the same webhook that builds the page.
Same text, different users
Be clear about who listen mode serves. Screen-reader users already have speech — they use their own voice at their own speed, and an auto-playing audio file is an anti-pattern for them. Listen mode serves sighted users in eyes-busy contexts and users with reading difficulties who prefer natural narration. Ship it as an optional player, never autoplay, with a visible play control and transcript pairing.
Render the article, keep the structure
Strip interactive elements (forms, embeds, nav) from the article body, keep headings and paragraph breaks as natural pause points, and render per article. Each is one sync POST well under the 50k limit.
curl
curl https://audratalks.com/v2/speech \
-H "Authorization: Bearer sk-audra-..." \
-H "Content-Type: application/json" \
-d '{"model":"audra-essential","voice":"june","text":"How to read a water bill...","format":"mp3"}' \
--output article-2841.mp3
Python
import requests
resp = requests.post(
"https://audratalks.com/v2/speech",
headers={"Authorization": "Bearer sk-audra-..."},
json={"model": "audra-essential", "voice": "june", "text": article_text, "format": "mp3"},
timeout=3600,
)
resp.raise_for_status()
open("article-2841.mp3", "wb").write(resp.content)
JavaScript
const res = await fetch("https://audratalks.com/v2/speech", {
method: "POST",
headers: { Authorization: "Bearer sk-audra-...", "Content-Type": "application/json" },
body: JSON.stringify({ model: "audra-essential", voice: "june", text: articleText, format: "mp3" }),
});
if (!res.ok) throw new Error(await res.text());
await writeFile("article-2841.mp3", Buffer.from(await res.arrayBuffer()));
MP3 at 128 kbps is the right choice for a web player: a 3,000-character article is about 3.5 MB, streams instantly, and works in every browser. Pair it with the article text as the transcript — the text already exists, so WCAG's transcript requirement is free here.
Voice and pacing for long listening
People listen to a11y versions for 20+ minutes at a stretch. june is a common pick — warm without being performative — but audition candidates on your actual content, not generic samples. Keep the default 1.0 speed: faster audio is harder to follow for exactly the users who use listen mode.
Cost of the whole program
Back catalog: 1.5M characters × $25/1M = $37.50, once. Ongoing: at 20 new/edited articles a week (60,000 chars), about $1.50 a week — call it $75/year to keep audio current across the entire library. Nonprofit and education budgets survive those numbers. Model yours on /calc.
Watch-outs specific to a11y
- Images need alt text spoken in context — insert the alt text inline where the image appears, or note 'image' and leave it to the transcript.
- Tables and data visualizations cannot be narrated meaningfully; link to the transcript and skip the block with a spoken note.
- normalize (default true) expands URLs into words — usually what you want for listening.
- Shipped audio for users requires a paid pack (commercial rights) — trial output is evaluation-only.
Word-timed captions for karaoke-style highlighting: the captioned speech post. Docs-site listen mode specifics: the documentation TTS post.