ElevenLabs SDK Patterns
Overview
Production-ready patterns for the ElevenLabs TypeScript and Python SDKs. Covers singleton clients, type-safe TTS wrappers, error classification, retry with a concurrency queue, and multi-tenant client factories. Adopt them incrementally — the singleton client alone fixes the most common mistakes; add error classification and the queue as throughput grows.
The full, copy-ready code for all six patterns lives in references/implementation.md. This file gives the high-level workflow plus the essential skeleton so you can follow it end to end, then drill into the reference for depth.
Prerequisites
@elevenlabs/elevenlabs-jsinstalled (TypeScript) orelevenlabs(Python)ELEVENLABS_API_KEYexported in the environment (never hardcode the key)- Familiarity with async/await patterns and error handling best practices
Instructions
Apply the patterns in order — each builds on the previous one:
-
Singleton client. Create one lazily-initialized
ElevenLabsClientguarded by anELEVENLABS_API_KEYcheck so misconfiguration fails fast at startup. Expose aresetClient()for tests. This is the skeleton every other pattern imports:let instance: ElevenLabsClient | null = null; export function getClient(): ElevenLabsClient { if (!instance) { if (!process.env.ELEVENLABS_API_KEY) { throw new Error("ELEVENLABS_API_KEY environment variable is required"); } instance = new ElevenLabsClient({ apiKey: process.env.ELEVENLABS_API_KEY, maxRetries: 3, timeoutInSeconds: 60, }); } return instance; } -
Type-safe TTS service. Wrap
textToSpeech.convertbehind a typedTTSOptionsinterface and namedVoicePresetrecords (narration / conversational / dramatic / neutral) so voice settings are compile-time checked and consistent across the codebase. -
Error classification. Map raw SDK errors to an
ElevenLabsServiceErrorcarrying a stablecode(auth_failed, quota_exceeded, rate_limited, concurrent_limit, voice_not_found, invalid_request, server_error, network_error) and aretryableflag driven by HTTP status. -
Retry with a concurrency queue. Route calls through a
p-queuesized to your plan's concurrent-request limit, retrying onlyretryableerrors with exponential backoff + jitter. -
Multi-tenant factory. For SaaS platforms, key one client per tenant in a
Mapso each customer's API key stays isolated. -
Python async. Mirror the singleton + streaming-to-file pattern with
AsyncElevenLabsClientfor non-blocking Python backends.
See references/implementation.md for the complete code for every step above.
Output
Applying these patterns produces a small set of focused SDK modules in the target project:
src/elevenlabs/client.ts— singleton client with config +resetClient()src/elevenlabs/tts-service.ts— typedgenerateSpeech()/generateToFile()with voice presetssrc/elevenlabs/errors.ts—ElevenLabsServiceError+classifyError()src/elevenlabs/queue.ts—queuedRequest()with backoff and plan-aware concurrencysrc/elevenlabs/multi-tenant.ts— per-tenant client factory (SaaS only)elevenlabs_service.py— async singleton + streaming generator (Python backends)
TTS calls return an audio stream you pipe to a file or HTTP response; mp3_44100_128 is the
default output format.
Error Handling
| Pattern | Error Type | Benefit |
|---------|-----------|---------|
| classifyError() | All API errors | Maps HTTP status to actionable codes |
| queuedRequest() | 429, 5xx | Auto-retry with exponential backoff + jitter |
| Singleton guard | Missing env var | Fails fast at startup, not at first call |
Only retryable codes (rate_limited, concurrent_limit, server_error, network_error) are
retried; auth_failed, quota_exceeded, voice_not_found, and invalid_request throw
immediately so callers surface a real problem instead of looping.
Examples
Generate speech to a file (TypeScript):
import { generateToFile } from "./elevenlabs/tts-service";
await generateToFile(
{ voiceId: "21m00Tcm4TlvDq8ikWAM", text: "Welcome aboard.", preset: "narration" },
"welcome.mp3"
);
Wrap a call in the retry queue:
import { queuedRequest } from "./elevenlabs/queue";
import { generateSpeech } from "./elevenlabs/tts-service";
const audio = await queuedRequest(() =>
generateSpeech({ voiceId: "21m00Tcm4TlvDq8ikWAM", text: "High-throughput job." })
);
Full runnable examples — including the Python async path and multi-tenant usage — are in references/implementation.md.
Resources
- ElevenLabs JS SDK Source
- ElevenLabs Python SDK
- p-queue (Concurrency)
- Full implementation walkthrough
Next Steps
Apply these patterns in elevenlabs-core-workflow-a for TTS generation, or see
elevenlabs-rate-limits for advanced throttling and plan-aware concurrency tuning.