ElevenLabs — text to speech with character-level timestamps
This is a report, not a tutorial. The vendor's documentation explains the API better than we can and stays fresher; what follows is what happened when we actually used it — what it cost on a named workload on a named date, what broke, and which credential patterns the product can support. Sections 8 and 9 are the point of the page.
Prose from the video vault at commit 7d1916aca5f3, 7 September 2026. Prose from platforms/elevenlabs.md, with the figures behind it from four reels' worth of pipeline findings. When the vault moves ahead, this page is behind — and says so rather than guessing.
How to read this page. Every factual claim carries a chip saying how we know it: verified we ran it and watched it work, on that date · measured our own pipeline produced this number on a named workload · vendor docs we read it in the vendor's documentation on that date and never executed it · specified, not shipped it does not exist yet · written, not run we wrote it and never ran it · projected arithmetic, with the workings shown. Click any chip for the ledger, which lists all of them in one table.
What this site is, and is not
It is narrower than the vendor's own environment, not better. The vendor serves millions of users across every workload; this site serves one — narrating explainer videos from a scripted pipeline — and reports what that cost, where the key had to live, and what broke. A general-purpose interface cannot be narrow, which is why this one does not try to be: the labs here exist because the vendor's own console does not fit this workflow, not because it is inadequate.
It is a report with a workbench attached, not a tutorial and not an index of somebody else's documentation. If a section here could be replaced by a link to the vendor, it should be deleted, and the vendor's page is linked with the date we read it. What this site has that nobody else does is what it cost on a named workload on a named date, what went wrong, and which credential patterns the product can actually support.
"Provider" here means one thing only: a service that serves models over an API — the sense this estate's own code uses, where a constant of that name holds an entry per model service. It does not mean a customer who redistributes capability to the people they serve, which is the other established sense in this estate and is not what any page here is about.
It is not an endorsement, and not the vendor's. No commercial relationship exists, the page saying so shipped before there was anything to disclose, and every claim carries the state that says how we know it.
1 · Disclosure
None. No credits, programme, or commercial relationship with ElevenLabs as of 5 September 2026. The key used for the one session reported in §8 is a Creator-tier key we pay for ourselves at list price. The full list — which currently has no entries in the "relationship" column — is at /disclosures/, and it shipped with this site's first version precisely so that the page's existence is not read as evidence of a relationship.
2 · What it grants
The rows an agent's grant gains when this platform is connected, as capability tuples — verb × object class × reach, with reversibility marked. This is the block a capability index joins on; it is emitted as front-matter in this page's markdown as well as in the table below.
What the platform can grant, per product
| verb | object class | reach | reversibility | product | note |
|---|---|---|---|---|---|
send | text | endpoint | reversible | Text to speech | the words you send leave for the model |
read | audio+timing | self | reversible | Text to speech | character-level alignment — the capability the vendor does not frame as one |
spend | characters | tenant | reversible | any | bounded by the plan's monthly quota and nothing narrower |
create | voice | tenant | reversible | Voices — instant and professional cloning | the row a reader will not expect. A voice that did not exist before, from under two minutes of audio. Deletable, so reversible in the platform's terms — and consent is not a technical control |
read | audio | tenant | reversible | Speech to text (Scribe), forced alignment | transcription of material you supply, priced by the hour |
send | media | endpoint | reversible | Dubbing | media you supply, re-voiced in another language |
send | message | world | irreversible | Agents (conversational) | the only irreversible row this vendor offers. A live agent speaks to a person, and a thing said to somebody cannot be unsaid |
What our key grants — scoped to text to speech and voices-read
| verb | object class | reach | reversibility | bounded by |
|---|---|---|---|---|
spend | characters | tenant | reversible | the plan's monthly quota only — there is no per-key spend limit |
send | text | endpoint | reversible | narration text leaves for the model |
read | audio+timing | self | reversible | character-level alignment — the new capability this API brings |
Not granted by a key scoped this way, and it should stay that way: voice cloning, dubbing, agents.
The same rows are emitted as front-matter in this page’s markdown source, so a capability index can join across providers without parsing English.
Read the two tables as one argument. The first is what an agent gains the moment this provider is connected at all; the second is what the key this evaluation actually used can reach. The gap between them is the work a scope does, and it is the only part of a credential story this vendor lets you control.
The row worth stopping on is create × voice × tenant. Connecting this provider gives an agent the capability to produce a voice that did not exist — instant cloning takes under two minutes of audio. That is a different order of thing from producing audio, no vendor page frames it as a capability, and the platform's controls for it are procedural rather than technical: professional cloning requires verification of the speaker, and instant cloning requires you to have the right to the recording you upload. Consent is not something the API can check. Our key is scoped to exclude it, and the key-scope probe is how you check that yours is too.
One row a reader might expect is deliberately absent. It has been proposed that connecting a voice platform grants send × audio × world, irreversibly, on the reasoning that audio sent is sent. Not for this product. The text-to-speech endpoint returns audio to the caller; nothing leaves for the world, and the estate's own primitive distinguishes endpoint reach from world reach exactly here. The irreversible row this vendor does offer is Agents, in the table above, and it is scoped out of our key. Saying otherwise would tell a reader that generating narration publishes it — which is the error §4 exists to prevent, one product over. vendor docs 5 Sep 2026
Two more things are worth saying in prose. First, read × audio+timing × self is the new capability — this API returns the start and end time of every character it speaks, which the pipeline that drove this evaluation never had from any other provider. Everything in the captions studio is arithmetic on that array. Second, nothing our key grants reaches the world. The pipeline receives audio; publishing a video is a separate, human act by a person who watched it first. That distinction is the difference between a narration tool and a voice agent, and it is why Agents is scoped out of the key rather than merely unused.
3 · Which pattern — and for which product
A vendor with several products has several answers, so this section is indexed by product, not by vendor. The four patterns are defined on /patterns/; the generated matrix, including the sibling provider, is at /comparison/.
Text to speech (the REST API — what we use).
- Pattern 0, key in the page: technically available. CORS permits a browser to call
api.elevenlabs.iodirectly verified 5 Sep 2026 — and the only bound on that key is the account's monthly quota vendor docs 5 Sep 2026. So a key in a published page is pattern 0 with a ceiling. Never publish one. Every lab on this site is the narrower case: the key's owner, testing their own key, in their own browser. - Pattern 1, bounded key in the page: not available. ElevenLabs keys are scoped, not bounded — see §5.
- Pattern 2, short-lived token: available only with a server we run. The vendor offers no short-lived credential for TTS vendor docs 5 Sep 2026. Such a minter would be ours to build, host and pay for; it does not exist.
- Pattern 3, host holds the key: specified, not shipped specified, not shipped.
Agents (conversational). Pattern 2 with the vendor's own signed URL (15 minutes), or a hostname allowlist — and the vendor's documentation says not to configure both on one agent vendor docs 5 Sep 2026. These mechanisms are the reason §4 exists, and they do not apply to text to speech.
4 · Where the key goes
Every quote below carries the product it belongs to, the URL, and the date we read it. This is not pedantry: an earlier draft of our own strategy quoted the Agents rule on a TTS page, which would have sent a reader off to build a signed-URL minter for an endpoint that does not accept one.
Agents product — read 5 September 2026 at https://elevenlabs.io/docs/eleven-agents/customization/authentication: "Never expose your ElevenLabs API key client-side." Signed URLs "are valid for 15 minutes… the conversation must be initiated within the 15 minute window"; allowlists use exact hostname matching; "Do not configure signed URLs and allowlists together on the same agent." vendor docs 5 Sep 2026
Text-to-speech product — there is no equivalent page. The key goes in the xi-api-key request header vendor docs 5 Sep 2026, and the only vendor-side control is scoping a key to endpoints in the dashboard. No signed URL, no allowlist, no origin check. A TTS key is a bearer credential for the account's quota, and the vendor's client-side warning above is written about a different product — which does not make it wrong here, only unenforceable here.
Where the key goes in our own estate, for completeness: owner-sealed at .vault/elevenlabs/config.json (AES-256-GCM under a key derived from the vault's write key, so a read-key holder cannot open it), opened for the life of one shell command. A key pasted into a lab on this site goes to this browser's localStorage and nowhere else verified 7 Sep 2026.
5 · The bounding primitive
The plan's monthly character quota, reported by GET /v1/user/subscription vendor docs 5 Sep 2026. That is the whole bound, and it belongs to the account, not to the key.
Per key, ElevenLabs gives you scope, not spend: a key can be restricted to endpoints — text-to-speech yes, dubbing no — which limits what a leaked key can do, and does nothing at all to limit how much of your quota it can burn vendor docs 5 Sep 2026.
What it does not cap:
- Any single key can consume the whole account's quota.
- Nothing resets faster than the billing cycle — there is no daily or weekly window to fall back to.
- There is no per-key limit, and therefore no equivalent of the thing that saved us on the sibling provider: a refusal at a ceiling you chose in advance verified 3 Sep 2026.
A leaked ElevenLabs key is bounded by the plan. A leaked OpenRouter provisioned key is bounded by the number you typed when you minted it. That difference is the entire reason the comparison table earns its place on this site.
6 · The minimal working example
The smallest thing that runs, as a file rather than a snippet — and every one of these is written from the vendor's reference and never executed written, not run, because the container that wrote them could not reach the API verified 4–5 Sep 2026.
| File | What it does | State |
|---|---|---|
00-smoke.sh | 00-smoke.sh — does the key work, what is the quota, and one sentence of audio. | written, not run |
align.sh | align.sh <audio-or-video> <script.txt> [out.json] — forced alignment of an existing render to its script. | written, not run |
stt.sh | stt.sh <audio-or-video> [out.json] — Scribe transcription with word timestamps (the QA gate, docs/05). | written, not run |
tts-timestamps.mjs | tts-timestamps.mjs "<text>" <voice_id> [basename] — one request to /with-timestamps, writes | written, not run |
tts.sh | tts.sh "<text>" <voice_id> [out.mp3] — plain generation, MP3 to a file. ELEVEN_MODEL and SPEED honoured. | written, not run |
voices.sh | voices.sh — list voices as: voice_id · name · category · accent · gender · use_case | written, not run |
The pipeline invocation they were written for:
export ELEVENLABS_API_KEY=$(node tooling/scripts/vault-secrets.mjs open elevenlabs) node examples/tts-timestamps.mjs "<text>" <voice_id> /tmp/out # → .wav + .words.json + .srt
The interactive version is the bench, which has run: it is the source of every verified chip on this page. The rest of the labs — twelve of them — extend the bench to the features this evaluation did not reach, and they are all written, not run.
7 · What we use it for
Nothing in a shipped video yet, and it is worth saying that plainly before the numbers.
The workload is a video pipeline that renders explainer reels from a reel.json script: one narration string per scene, rendered to audio, composited over stills with the narration text on screen as a caption band. It has run with three speech providers — a local Kokoro model ($0), an OpenRouter-hosted gpt-audio (paid, measured in §8), and now this one, evaluated. Four reels, six cuts, are published.
What this API would change, ranked by how much it improves those videos rather than by how impressive it is:
- Character-level timestamps — SRT sidecars, chapters accurate to the sentence, and a word-synced caption band. None of it is possible with either incumbent provider, and all of it is arithmetic on one response field. This is the reason for the evaluation.
- A speed control back.
voice_settings.speed, 0.7–1.2 vendor docs 5 Sep 2026. The incumbent has none, and the cost of that was a portrait cut losing a third of its script rather than 15% of its pace measured 2 Sep 2026. - A pronunciation dictionary, so the narration text can say
sgit.aiinstead ofsgit dot ai— because in this pipeline the narration text is the caption text, so every pronunciation hack is on screen where viewers read it as a typo vendor docs 5 Sep 2026. - Forced alignment of the six existing cuts — subtitles for videos already published, including the two Kokoro cuts that will never have API timestamps, for about two cents vendor docs 5 Sep 2026.
- Scribe as a QA gate — transcribe the finished file, diff it against the script, and catch automatically the two failures we have shipped and then caught by eye.
Items 1–3 have a lab each on this site. None of the five has been run.
8 · What it cost
Figures as of 7 September 2026. List prices move; every number below carries the date, the workload size, the model and the request count, because "$0.30 for a 3:46 video" without them is not a fact, it is a vibe.
eleven_v3, list price measured 5 Sep 2026pNInz6obpgDQGcFmaJgB, eleven_v3, 5 Sep 2026 verified 5 Sep 2026openai/gpt-audio, 3 Sep 2026 measured 3 Sep 2026What we actually paid ElevenLabs, in full: one bench session on 5 September 2026 — four generations of the same 187-character sample on eleven_v3 with timestamps, about 750 characters, $0.075 at the list rate of $0.10 per 1,000 characters vendor docs 5 Sep 2026. That is the entire measured spend. Everything else in this section is arithmetic, and is labelled as such.
Projected, with the arithmetic shown. Our scripts run about 6 characters per word; the word counts are measured from the four reels we have rendered measured 3 Sep 2026. Characters × the model's list rate:
| Reel | Words (measured) | ≈ chars | Flash @ $0.05/1k | v3 @ $0.10/1k | Actually paid (incumbent) |
|---|---|---|---|---|---|
| AIUC-1 landscape 3:46 | 599 | 3,600 | $0.18 | $0.36 | $0.2997 |
| AIUC-1 portrait 2:09 | 318 | 1,900 | $0.10 | $0.19 | $0.1708 |
| VoiceDebrief deck landscape 4:17 | 699 | 4,200 | $0.21 | $0.42 | — (local model, $0) |
| VoiceDebrief deck portrait 2:51 | 480 | 2,900 | $0.14 | $0.29 | — (local model, $0) |
| VoiceDebrief pitch landscape 1:55 | 304 | 1,800 | $0.09 | $0.18 | $0.1554 |
| VoiceDebrief pitch portrait 1:24 | 190 | 1,150 | $0.06 | $0.12 | $0.1112 |
| All six, one pass | 2,590 | 15,550 | $0.78 | $1.56 |
projected for the two ElevenLabs columns — no reel has been rendered with this API. measured for the words and for the right-hand column. The conclusion the arithmetic supports: v3 costs about what the incumbent costs; flash costs half — and both are a rounding error against the value of the timestamps, which is the only sound reason to switch.
And one more measured figure, added 8 September 2026: the first video made with this API — nine scenes, 1,821 characters, 2:08 of speech, two cuts — cost $0.18 at list price and was paid in a free tier's quota rather than in money verified 8 Sep 2026. Zero re-renders, because the script was written first. The full account is on /video/.
Plan arithmetic. At about 4,000 characters per video, a Creator plan's 220,000 monthly characters is roughly 50 videos a month projected. Rates and quotas: vendor docs 5 Sep 2026, vendor docs 5 Sep 2026.
Run the arithmetic on your own script in the cost model — it needs no key and makes no network call.
9 · What went wrong
The section nobody else writes. Four things, in the order they cost us time.
The host, not the vendor
The app frame's CSP ate every request
The first Connect from inside the vault app failed with connect-src blob: data: — the SG/App host locks every app frame to no network at all, by design, and the bench reported the generic Failed to fetch. Nothing to do with the key, the CORS headers, or the vendor. verified 5 Sep 2026
Fixed by "permissions": {"network": true} in app.json, which omits the meta tag and makes the host's HUD show a standing "direct network access" chip, so the exception is never silent. Right for this vault — nothing in it is confidential except the sealed keys, which sit below the permission floor and are unreachable from any frame regardless. Wrong for a vault whose app holds private content: that grant is per-app and it is a real widening.
Us, not the vendor
The container that wrote all of this could not reach the API
api.elevenlabs.io:443 was refused to the authoring container by the egress proxy — connect_rejected, organization policy — from curl and from Node, on the regional hosts too. verified 4–5 Sep 2026
So the render shim written, not run, every example script written, not run, the pronunciation-dictionary upload written, not run and every lab on this site written, not run were written from the vendor's reference and have never been executed. This is why the site is badged the way it is. The one exception is the bench, run from a human's own browser, which is the reason there are any verified chips at all.
Caught in review, before it shipped
The mechanism we nearly documented was the wrong product's
An earlier draft carried the Agents rules — "never expose your key client-side", the 15-minute signed URL, the hostname allowlist — as if they governed text to speech. They do not: the TTS REST API has neither mechanism. vendor docs 5 Sep 2026
Had it shipped, a reader could have spent a week building a signed-URL minter for an endpoint that does not accept one. The conclusion survived the correction — TTS still needs a server for pattern 2 — but the mechanism named did not. Hence the rule this page obeys: every quote carries its product.
Still open
Concurrency is unknown, and our render fires 16 requests at once
Concurrent request limits scale with the plan. Our render fires every scene in parallel — 10 to 16 requests — so a 429 mid-render is the most likely first failure on a small plan, and we have never provoked one to find out where the wall is. written, not run
The concurrency probe exists to answer exactly this, at a cost of a few cents. Nobody has run it. Until somebody does, the honest planning number for a Starter or Creator plan is unknown, and the mitigation — a semaphore in the shim — is written but untested.
And what went wrong the first time somebody ran it
On 8 September 2026 the render path ran for the first time verified 8 Sep 2026. Three of the four things that broke are the vendor's, and one is ours:
Their tier gate
PCM output is Pro-and-above
403, naming the format. verified 8 Sep 2026 The shim asks for PCM to skip a decoder, so on any lower tier that is a dead path with a six-line fallback.
Their model
Stitching is not available on v3
400 unsupported_model for previous_text/next_text. verified 8 Sep 2026 The first request failed before any audio existed, and the lab that offered the same combination now refuses it.
Their key scoping, working
The 401 that names its own permission
“missing the permission pronunciation_dictionaries_write”. verified 8 Sep 2026 Precise about what the key may do, silent about what it may spend — which is §5 arriving as an error message.
Ours
The encoder added 14 seconds of silence
ffmpeg's -shortest with a looped still ran past the end of the audio, up to 1.8 s per scene. verified 8 Sep 2026 The provider's timing was exact to the millisecond verified 8 Sep 2026 — the drift was entirely ours, which is the first time this estate has been able to say which.
What has not gone wrong, because nobody has tried it
The most useful thing in this section is the list of failures we have not yet had the opportunity to have. written, not run written, not run written, not run Every one of them has a lab on this site, and each lab prints a result you can paste back into the vault as evidence.
The full list of what remains unverified — and who could verify it — is the open items, which is the last section of the ledger.