Do you need ElevenLabs, or is bundled TTS good enough for voiceover?
A decision rule for buying speech: what a bundled TTS catalogue really contains, the cost of a finished minute of audio, and the four cases where a specialist still wins.
Ask one question first: is the voice the product? If a listener is going to judge the performance — an audiobook, a recurring character, a brand spot that runs on television — pay the specialist and don’t argue about it. If speech is a feature inside software, the read-aloud button, the notification, the summary someone listens to on a commute, then the TTS bundled with a platform like Infrai is very probably good enough.
Most teams get this wrong in the same direction. They buy studio-grade synthesis for a two-sentence alert, then discover the hard part was never the voice — it was storing 40,000 audio files, retrying the ones that failed, and working out which tenant to bill. So here’s what the bundled option actually contains, what a finished minute costs, and where the argument flips.
Start by counting what’s in the box
“Bundled TTS” is not one thing. It’s whatever vendors that platform currently has keyed, which changes. One free call settles it:
export INFRAI_API_KEY="your_infrai_api_key"
curl -sS "https://api.infrai.cc/v1/ai/tts/voices" \
-H "Authorization: Bearer ${INFRAI_API_KEY}" \
| jq '{total: .data.count, by_vendor: ([.data.items[].vendor] | group_by(.) | map({vendor: .[0], voices: length}))}'
Reading that on 2026-07-26 gave us ninety-seven voices across three vendors:
{
"total": 97,
"by_vendor": [
{ "vendor": "cosyvoice", "voices": 48 },
{ "vendor": "elevenlabs", "voices": 1 },
{ "vendor": "tencent_tts", "voices": 48 }
]
}
That single ElevenLabs entry is the honest shape of the trade. A pass-through gives you a taste of the specialist, not their library — and certainly not their cloning, their director controls or their emotion tags. If your product needs a voice nobody else has, that row is your answer already.
Ninety-six of the ninety-seven are Chinese-vendor voices, most of them strongest in Mandarin with solid English. For an accessibility feature or an in-app narrator that’s plenty. For a US consumer brand’s flagship ad, it isn’t.
The cost of a finished minute
Speech is billed by the character here, not by the call, which makes the arithmetic unusually easy to do in advance.
curl -sS "https://api.infrai.cc/v1/ai/models?capability=tts&available=true" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
{
"object": "list",
"capability": "tts",
"available_only": true,
"count": 2,
"data": [
{ "id": "qwen-tts", "owned_by": "cosyvoice", "price_usd": 0.009, "unit": "per_1k_char" },
{ "id": "qwen3-tts-flash", "owned_by": "cosyvoice", "price_usd": 0.006, "unit": "per_1k_char" }
]
}
Verified 2026-07-26: $0.009 per 1,000 characters on qwen-tts, $0.006 on qwen3-tts-flash. English narration runs about 150 words a minute and roughly six characters a word, so a minute of finished audio is around 900 characters — call it $0.008 a minute, or 49 cents an hour. Synthesis rates have been drifting downward and vendors discount hard, so rerun that catalogue call rather than trusting this paragraph; what you find will quite possibly be lower.
Now compare structures rather than numbers, because that’s the part that survives a price change. Specialist vendors sell monthly character or credit tiers. That’s excellent when your volume is steady and you use the allowance — and it’s a bad deal when a product ships 12 minutes of audio in January and 900 in March, because you either overbuy or you hit a ceiling mid-campaign. Per-character metering has no such cliff.
Here’s a script that turns the live catalogue into a per-minute number for your own script length:
const KEY = process.env.INFRAI_API_KEY;
if (!KEY) throw new Error("INFRAI_API_KEY is not set");
const res = await fetch("https://api.infrai.cc/v1/ai/models?capability=tts&available=true", {
headers: { Authorization: `Bearer ${KEY}` },
});
if (!res.ok) throw new Error(`catalogue lookup failed: ${res.status} ${await res.text()}`);
const { data } = await res.json();
const CHARS_PER_MINUTE = 900;
for (const m of data) {
if (m.unit !== "per_1k_char" || typeof m.price_usd !== "number") continue;
const perMinute = (m.price_usd / 1000) * CHARS_PER_MINUTE;
const perHour = perMinute * 60;
console.log(`${m.owned_by}/${m.id}: $${perMinute.toFixed(4)}/min, $${perHour.toFixed(2)}/hour`);
}
Feed it your real average — a legal disclaimer is denser than a chatty summary — and you get a budget you can defend.
Generating the audio, and the container caveat
Pick a voice from the catalogue, pass that voice’s own model through, and write the bytes to disk. Point INFRAI_BASE_URL at https://api.infrai.cc before you run this:
import { writeFile } from "node:fs/promises";
const KEY = process.env.INFRAI_API_KEY;
const BASE = process.env.INFRAI_BASE_URL;
if (!KEY || !BASE) throw new Error("set INFRAI_API_KEY and INFRAI_BASE_URL");
async function narrate(script, wanted = "Rachel") {
const catalogue = await fetch(`${BASE}/v1/ai/tts/voices`, {
headers: { Authorization: `Bearer ${KEY}` },
});
if (!catalogue.ok) throw new Error(`voices failed: ${catalogue.status}`);
const { data } = await catalogue.json();
const pick = data.items.find((v) => v.name === wanted) ?? data.items.find((v) => v.is_default);
if (!pick) throw new Error("no servable voice");
const audio = await fetch(`${BASE}/v1/audio/speech`, {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
vendor: pick.vendor,
model: pick.model,
voice: pick.voice_id,
input: script,
response_format: "mp3",
}),
});
if (!audio.ok) throw new Error(`speech failed: ${audio.status} ${await audio.text()}`);
const type = audio.headers.get("content-type") ?? "";
const bytes = Buffer.from(await audio.arrayBuffer());
const ext = type.includes("mpeg") ? "mp3" : type.includes("wav") ? "wav" : "bin";
await writeFile(`narration.${ext}`, bytes);
console.log(`${pick.vendor}/${pick.voice_id} -> narration.${ext} (${bytes.length} bytes, ${type})`);
}
await narrate("Your monthly usage report is ready. Total spend was twelve dollars.");
Note the last four lines. Asking for mp3 is a request, not a promise: cosyvoice answered a mp3 request with audio/wav and a RIFF header in our testing, while the ElevenLabs route returned real audio/mpeg. Derive the extension from what arrived, not from what you asked for, or you’ll ship files your player refuses.
Confirm the spend afterwards rather than trusting an estimate:
curl -sS "https://api.infrai.cc/v1/account/usage" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
The response breaks the 30-day total down per capability, so ai.tts sits beside storage.object.put and email.send in one list. That’s the shape of the second argument, which we’ll get to.
Where the specialist genuinely wins
| Requirement | Bundled TTS | Specialist (ElevenLabs and similar) |
|---|---|---|
| Notification, alert, read-aloud | fine | overkill |
| 20 languages, low effort | fine — Mandarin and English strongest | fine, wider European coverage |
| Cloned or licensed brand voice | doesn’t support it | the reason to buy |
| Line-level direction, emotion tags | no | yes |
| Deterministic audio container | vendor-dependent | as requested |
| Billing shape | per character, no tier | monthly credit tiers |
| Same key covers storage, queue, email | yes | no |
Four rows favour the specialist. That’s not a hedge; it’s the real answer to the question, and any page that tells you otherwise is selling something. If you need a cloned voice, fine-grained SSML or a specific named performer, you’d be better off buying it directly and treating speech as a first-class vendor relationship.
Two more limitations worth flagging on the bundled side. TTS availability rides on vendor keys, so when a key isn’t hydrated the call returns VENDOR_NOT_CONFIGURED rather than quietly falling back to another vendor — check the catalogue count before a launch, not after. And a voice id is pinned to one model, so model: "auto" and a named voice don’t mix.
OpenAI’s own speech API is the other obvious middle option: a small, well-tuned voice set inside an SDK many teams already have, and Azure OpenAI hosts the same voices when you need a contracted region. Neither gives you cloning either.
The argument the comparison tables never make
Generating audio is maybe 20% of shipping an audio feature. The rest is the boring 80%: putting the file somewhere durable, retrying the vendor timeout, emailing the finished asset, telling finance which customer caused the bill.
On a single-purpose speech vendor, all four of those are somebody else’s product — four accounts, four keys, four invoices, four rotation schedules. On a platform where the same credential also reaches object storage, queues, email and error tracking, the follow-on work is a different path on a client you already built. Per-tenant attribution becomes a query against one usage endpoint instead of a reconciliation project.
That’s the durable claim, and it holds whichever way speech pricing moves.
So: buy the specialist when the voice carries brand or narrative weight, and use the bundled option for everything else — which, in most products, is everything else. Start with the free catalogue call, generate 30 seconds, and listen. Thirty seconds of honest listening beats any comparison table, including this one.