YouTube narration TTS API: scripts to mastered voiceover — Audra

A faceless YouTube channel is a content machine: script on Sunday, render Monday, edit Tuesday, publish Thursday — every week. What breaks channels is not writing, it is narration cost and voice drift. Batch TTS turns the voice track into a fixed line item.

This post is the voiceover pipeline for faceless-channel operators: weekly cadence, script sizing at real channel numbers, the loudness you need for YouTube, and per-video cost on Essential's $25/1M prepaid rate.

The persona: a faceless channel, one 10-minute video per week

A 10-minute video is about 1,400 words of script — call it 7,500 characters, a single sync POST. Channels running explainers, listicles, or documentary-style content at 2–3 videos a week sit around 25,000 characters weekly, still trivially inside the 50k sync limit. At this scale you never write chunking code; you write a render script.

One request per video section

Send the full script to POST /v2/speech and you get back a single mastered MP3: 128 kbps, −16 LUFS. That lands where YouTube normalizes anyway, so what you hear in review is what viewers hear. Editors drop the file straight onto the timeline.

curl
curl https://audratalks.com/v2/speech \
  -H "Authorization: Bearer sk-audra-..." \
  -H "Content-Type: application/json" \
  -d '{"model":"audra-essential","voice":"june","text":"In 1961, a cartographer made a mistake...","format":"mp3"}' \
  --output narration.mp3
Python
import requests

resp = requests.post(
    "https://audratalks.com/v2/speech",
    headers={"Authorization": "Bearer sk-audra-..."},
    json={"model": "audra-essential", "voice": "june", "text": script, "format": "mp3"},
    timeout=3600,
)
resp.raise_for_status()
open("narration.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: script, format: "mp3" }),
});
if (!res.ok) throw new Error(await res.text());
await writeFile("narration.mp3", Buffer.from(await res.arrayBuffer()));

If your videos run sections with b-roll between them, render per-section instead — intro, segment 1, segment 2, outro — so the editor can move sections without re-cutting audio. Per-section requests also localize failures: one bad render is one redo, not a whole video.

The weekly cadence that works

Batch all sections the same day. A 3-video week is 3–9 requests; render them sequentially, tag each file with video ID and section, and hand the folder to the editor. A QA-listen of the first 30 seconds per file catches what matters — the API already gates silence and truncation before billing you.

Voice: the channel is the voice

Audience retention is identity. Pick one Essential slug and freeze it in the render script as a constant — changing it mid-catalog makes episode 41 sound like a different channel. Because voices are catalog presets rather than clones, the slug renders identically for the life of the channel. Blend if you want a unique timbre: june(2)+wren(1) mixes two presets at fixed weights.

Per-video cost, at faceless-channel numbers

A 7,500-character script at $25/1M costs $0.19 per video on Essential. A 3-video week is about $0.56; a 52-week, one-video-per-week channel narrates for roughly $10 a year. Doubling to long-form 20-minute scripts still keeps you under $0.40 per video. Budget it once on /calc and stop thinking about it.

Watch-outs for YouTube specifically

Show-pipeline loudness detail lives in the podcast post; multi-speaker formats in the Conversations post. Voice previews: /voices.