Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Implement an ElevenLabs reference architecture for production TTS/voice applications. Use when designing new ElevenLabs integrations, reviewing project structure, or building a scalable audio generation service. Trigger with "elevenlabs architecture", "elevenlabs project structure", "how to organize elevenlabs", "TTS service architecture", "elevenlabs design patterns", "voice API architecture".
.claude/skills/jeremylongshore-elevenlabs-reference-architecture/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 0% | 0% |
| case-02 | ✗→✓ | ▲ Improved | -14% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 16% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 14% | 0% |
| case-10 | ✗→✓ | ▲ Improved | -3% | 0% |
Production-ready architecture for ElevenLabs TTS/voice applications. Covers project layout, service layers, caching, streaming, and multi-model orchestration. The full code for each layer lives in references/ so this file stays a navigable map; drill into a reference file when you need the exact implementation.
elevenlabs-sdk-patterns)ELEVENLABS_API_KEY (read by theconfig layer). This is the only ElevenLabs credential — your app's own request auth (middleware/auth.ts) is separate and unrelated.
Build the service in six layers. Each step below is the high-level move; the verbatim code and diagrams are in the linked reference files.
Split the codebase into elevenlabs/ (client, config, models, errors, types), services/ (tts, voice, audio, cache), api/ (routes + middleware), queue/, and monitoring/. See the full project tree.
Define an environment-aware ElevenLabsConfig — dev uses the cheap/fast eleven_flash_v2_5 and small output format; production uses eleven_multilingual_v2 at higher quality, more concurrency, and a larger cache. loadConfig() merges the per-environment defaults with ELEVENLABS_API_KEY. Full interface and ENV_CONFIGS: implementation walkthrough.
Wrap the SDK client in a TTSService that owns a singleton client and a p-queue sized to maxConcurrency (this is what prevents 429s). generate() supports both streaming and buffered convert, logs latency, and routes errors through classifyError. generateLongText() splits on sentence boundaries under the 5000-char limit to preserve prosody. Full class: implementation walkthrough.
A VoiceService over the client for list/clone/get-settings/update-settings/delete, with category filtering (premade / cloned / generated). Full class: implementation walkthrough.
Requests flow Client → API layer → Cache/TTS/Voice services → queue → singleton SDK client → ElevenLabs REST/WS endpoints. See the data flow diagram.
Compose a /health route that runs connectivity, quota, and cache checks with Promise.allSettled, returning healthy / degraded / unhealthy (degraded once quota exceeds 90%). Full function: implementation walkthrough.
Every architectural choice (singleton client, p-queue, LRU-vs-Redis, sentence splitting, environment-based model selection, HTTP-vs-WS streaming) and its rationale is tabulated in the architecture decisions table.
Applying this skill produces a layered service scaffold, not a single file:
TTSService (queued, retry-aware, streaming-capable) and a VoiceService./health route returning { status, services, timestamp } where status ishealthy, degraded, or unhealthy.
generate() returns a Buffer (or a ReadableStream whenstreaming: true); generateLongText() returns Buffer[], one per chunk.
| Issue | Cause | Solution | |-------|-------|----------| | Circular dependencies | Wrong layering | Services depend on client, never reverse | | Cold start latency | Client initialization | Pre-warm in server startup | | Memory pressure | Unbounded audio cache | Set maxSizeMB on cache | | Type errors | SDK version mismatch | Pin SDK version in package.json | | Frequent 429s | Concurrency above plan limit | Lower maxConcurrency in config | | Missing API key | ELEVENLABS_API_KEY unset | Export it before loadConfig() runs |
Generate speech through the service layer:
typescriptconst tts = new TTSService(); const audio = await tts.generate("Hello from production.", { voiceId: "21m00Tcm4TlvDq8ikWAM", });
Stream a long article with prosody-preserving chunking:
typescriptconst chunks = await tts.generateLongText(longArticleText); // chunks: Buffer[] — concatenate or pipe in order
For the complete, runnable layers behind these snippets — config, full TTSService, VoiceService, and the /health composition — see the implementation walkthrough. For the project tree, data flow, and decision rationale, see architecture.md.
Start with elevenlabs-install-auth for setup, then apply this architecture. Use elevenlabs-core-workflow-a and elevenlabs-core-workflow-b for feature implementation.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 39,747 | 25,144 | -37% | 1 | 1 | 0% | 6,625 | 6,592 | -0% | 0 | 0 | — |
case-02 | fail→pass | 36,523 | 23,755 | -35% | 1 | 1 | 0% | 8,254 | 7,105 | -14% | 0 | 0 | — |
case-03 | fail→pass | 38,798 | 38,739 | -0% | 1 | 1 | 0% | 6,979 | 8,068 | +16% | 0 | 0 | — |
case-04 | pass→pass | 9,909 | 5,988 | -40% | 1 | 1 | 0% | 1,916 | 2,452 | +28% | 0 | 0 | — |
case-05 | pass→pass | 22,788 | 38,711 | +70% | 1 | 1 | 0% | 5,184 | 7,916 | +53% | 0 | 0 | — |
case-06 | pass→pass | 21,582 | 20,588 | -5% | 1 | 1 | 0% | 3,208 | 5,309 | +65% | 0 | 0 | — |
case-07 | fail→fail | 16,851 | 20,364 | +21% | 1 | 1 | 0% | 2,833 | 4,657 | +64% | 0 | 0 | — |
case-08 | fail→pass | 21,136 | 10,788 | -49% | 1 | 1 | 0% | 3,140 | 3,584 | +14% | 0 | 0 | — |
case-09 | pass→pass | 18,813 | 21,220 | +13% | 1 | 1 | 0% | 2,788 | 4,657 | +67% | 0 | 0 | — |
case-10 | fail→pass | 18,793 | 10,327 | -45% | 1 | 1 | 0% | 3,169 | 3,087 | -3% | 0 | 0 | — |
case-11 | pass→pass | 15,438 | 10,667 | -31% | 1 | 1 | 0% | 2,129 | 2,950 | +39% | 0 | 0 | — |
case-12 | fail→pass | 22,584 | 22,451 | -1% | 1 | 1 | 0% | 4,166 | 5,059 | +21% | 0 | 0 | — |
case-13 | fail→fail | 4,034 | 7,438 | +84% | 1 | 1 | 0% | 657 | 2,772 | +322% | 0 | 0 | — |
case-14 | fail→pass | 23,582 | 15,081 | -36% | 1 | 1 | 0% | 3,455 | 4,366 | +26% | 0 | 0 | — |
case-15 | pass→pass | 13,917 | 10,091 | -27% | 1 | 1 | 0% | 2,332 | 3,134 | +34% | 0 | 0 | — |
case-16 | pass→pass | 13,314 | 10,490 | -21% | 1 | 1 | 0% | 2,325 | 3,223 | +39% | 0 | 0 | — |
case-17 | fail→pass | 15,340 | 22,160 | +44% | 1 | 1 | 0% | 2,622 | 4,561 | +74% | 0 | 0 | — |
case-18 | pass→pass | 13,495 | 8,064 | -40% | 1 | 1 | 0% | 2,230 | 2,607 | +17% | 0 | 0 | — |
case-19 | fail→pass | 14,207 | 6,945 | -51% | 1 | 1 | 0% | 1,942 | 2,488 | +28% | 0 | 0 | — |
case-20 | fail→pass | 14,709 | 8,879 | -40% | 1 | 1 | 0% | 2,557 | 3,148 | +23% | 0 | 0 | — |
case-21 | fail→pass | 20,940 | 15,722 | -25% | 1 | 1 | 0% | 3,027 | 3,686 | +22% | 0 | 0 | — |
case-22 | pass→pass | 15,031 | 10,693 | -29% | 1 | 1 | 0% | 2,743 | 3,664 | +34% | 0 | 0 | — |
case-23 | fail→pass | 11,012 | 2,413 | -78% | 1 | 1 | 0% | 1,505 | 1,777 | +18% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +52 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.