--- name: elevenlabs-sdk-patterns description: 'Apply production-ready ElevenLabs SDK patterns for TypeScript and Python. Use when implementing ElevenLabs integrations, refactoring SDK usage, or establishing team coding standards for audio AI applications. Trigger: "elevenlabs SDK patterns", "elevenlabs best practices", "elevenlabs code patterns", "idiomatic elevenlabs", "elevenlabs typescript". ' allowed-tools: Read, Write, Edit version: 1.0.0 license: MIT author: Jeremy Longshore tags: - saas - voice - ai - elevenlabs - sdk - patterns compatibility: Designed for Claude Code --- # ElevenLabs SDK Patterns ## Overview Production-ready patterns for the ElevenLabs TypeScript and Python SDKs. Covers singleton clients, type-safe wrappers, error handling, retry logic, and multi-tenant patterns. ## Prerequisites - `@elevenlabs/elevenlabs-js` installed (TypeScript) or `elevenlabs` (Python) - Familiarity with async/await patterns - Understanding of error handling best practices ## Instructions ### Pattern 1: Singleton Client with Config ```typescript // src/elevenlabs/client.ts import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js"; 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, // Auto-retry on 429/5xx (default: 2) timeoutInSeconds: 60, // Per-request timeout }); } return instance; } // Reset for testing export function resetClient(): void { instance = null; } ``` ### Pattern 2: Type-Safe TTS Service ```typescript // src/elevenlabs/tts-service.ts import { getClient } from "./client"; import { Readable } from "stream"; import { pipeline } from "stream/promises"; import { createWriteStream } from "fs"; export type VoicePreset = "narration" | "conversational" | "dramatic" | "neutral"; const VOICE_PRESETS: Record = { narration: { stability: 0.6, similarity_boost: 0.75, style: 0.0 }, conversational: { stability: 0.4, similarity_boost: 0.6, style: 0.3 }, dramatic: { stability: 0.3, similarity_boost: 0.8, style: 0.7 }, neutral: { stability: 0.8, similarity_boost: 0.5, style: 0.0 }, }; export interface TTSOptions { voiceId: string; text: string; modelId?: "eleven_v3" | "eleven_multilingual_v2" | "eleven_flash_v2_5" | "eleven_turbo_v2_5"; preset?: VoicePreset; outputFormat?: string; languageCode?: string; } export async function generateSpeech(options: TTSOptions): Promise { const client = getClient(); const settings = VOICE_PRESETS[options.preset || "narration"]; return client.textToSpeech.convert(options.voiceId, { text: options.text, model_id: options.modelId || "eleven_multilingual_v2", voice_settings: settings, output_format: options.outputFormat || "mp3_44100_128", language_code: options.languageCode, }); } export async function generateToFile(options: TTSOptions, outputPath: string): Promise { const audio = await generateSpeech(options); await pipeline( Readable.fromWeb(audio as any), createWriteStream(outputPath) ); } ``` ### Pattern 3: Error Classification ```typescript // src/elevenlabs/errors.ts export type ElevenLabsErrorCode = | "auth_failed" | "quota_exceeded" | "rate_limited" | "concurrent_limit" | "voice_not_found" | "invalid_request" | "server_error" | "network_error"; export class ElevenLabsServiceError extends Error { constructor( message: string, public readonly code: ElevenLabsErrorCode, public readonly httpStatus: number | null, public readonly retryable: boolean, public readonly originalError?: Error ) { super(message); this.name = "ElevenLabsServiceError"; } } export function classifyError(error: unknown): ElevenLabsServiceError { if (error instanceof Error) { const status = (error as any).statusCode || (error as any).status; if (status === 401) { const msg = (error as any).body?.detail?.message || error.message; if (msg?.includes("quota")) { return new ElevenLabsServiceError(msg, "quota_exceeded", 401, false, error); } return new ElevenLabsServiceError(msg, "auth_failed", 401, false, error); } if (status === 429) { const msg = (error as any).body?.detail?.message || error.message; const code = msg?.includes("concurrent") ? "concurrent_limit" : "rate_limited"; return new ElevenLabsServiceError(msg, code, 429, true, error); } if (status === 404) { return new ElevenLabsServiceError(error.message, "voice_not_found", 404, false, error); } if (status === 400) { return new ElevenLabsServiceError(error.message, "invalid_request", 400, false, error); } if (status >= 500) { return new ElevenLabsServiceError(error.message, "server_error", status, true, error); } return new ElevenLabsServiceError(error.message, "network_error", null, true, error); } return new ElevenLabsServiceError(String(error), "network_error", null, true); } ``` ### Pattern 4: Retry with Concurrency Queue ```typescript // src/elevenlabs/queue.ts import PQueue from "p-queue"; import { classifyError, ElevenLabsServiceError } from "./errors"; // Concurrency limits by plan: // Free: 2, Starter: 3, Creator: 5, Pro: 10, Scale: 15 const queue = new PQueue({ concurrency: 5, // Match your plan's concurrent request limit interval: 1000, // Window in ms intervalCap: 10, // Max requests per window }); export async function queuedRequest( operation: () => Promise, maxRetries = 3 ): Promise { return queue.add(async () => { let lastError: ElevenLabsServiceError | undefined; for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await operation(); } catch (err) { lastError = classifyError(err); if (!lastError.retryable) throw lastError; const delay = Math.min(1000 * Math.pow(2, attempt), 30000); const jitter = Math.random() * 500; await new Promise(r => setTimeout(r, delay + jitter)); } } throw lastError; }) as Promise; } ``` ### Pattern 5: Multi-Tenant Client Factory ```typescript // src/elevenlabs/multi-tenant.ts import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js"; const clients = new Map(); export function getClientForTenant(tenantId: string, apiKey: string): ElevenLabsClient { if (!clients.has(tenantId)) { clients.set(tenantId, new ElevenLabsClient({ apiKey, maxRetries: 3, })); } return clients.get(tenantId)!; } export function removeTenantClient(tenantId: string): void { clients.delete(tenantId); } ``` ### Pattern 6: Python Async Pattern ```python # elevenlabs_service.py import os import asyncio from elevenlabs.client import AsyncElevenLabsClient _client: AsyncElevenLabsClient | None = None def get_async_client() -> AsyncElevenLabsClient: global _client if _client is None: _client = AsyncElevenLabsClient( api_key=os.environ["ELEVENLABS_API_KEY"] ) return _client async def generate_speech(text: str, voice_id: str, output_path: str): client = get_async_client() audio = await client.text_to_speech.convert( voice_id=voice_id, text=text, model_id="eleven_multilingual_v2", voice_settings={ "stability": 0.5, "similarity_boost": 0.75, }, ) with open(output_path, "wb") as f: async for chunk in audio: f.write(chunk) # Usage asyncio.run(generate_speech("Hello!", "21m00Tcm4TlvDq8ikWAM", "out.mp3")) ``` ## Pattern Summary | Pattern | Use Case | Key Benefit | |---------|----------|-------------| | Singleton client | Single-tenant apps | Memory efficiency, shared retries | | Type-safe TTS | All TTS work | Voice presets, compile-time checks | | Error classification | Production error handling | Actionable error codes | | Concurrency queue | High-throughput apps | Respects plan limits automatically | | Multi-tenant factory | SaaS platforms | Per-customer isolation | | Python async | Python backends | Non-blocking I/O | ## Error Handling | Pattern | Error Type | Benefit | |---------|-----------|---------| | `classifyError()` | All API errors | Maps HTTP to actionable codes | | `queuedRequest()` | 429, 5xx | Auto-retry with exponential backoff | | Singleton guard | Missing env var | Fails fast at startup, not at first call | ## Resources - [ElevenLabs JS SDK Source](https://github.com/elevenlabs/elevenlabs-js) - [ElevenLabs Python SDK](https://pypi.org/project/elevenlabs/) - [p-queue (Concurrency)](https://github.com/sindresorhus/p-queue) ## Next Steps Apply patterns in `elevenlabs-core-workflow-a` for TTS, or see `elevenlabs-rate-limits` for advanced throttling.