Claude-code-plugins-plus elevenlabs-sdk-patterns
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/elevenlabs-pack/skills/elevenlabs-sdk-patterns" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-elevenlabs-sdk-patterns && rm -rf "$T"
manifest:
plugins/saas-packs/elevenlabs-pack/skills/elevenlabs-sdk-patterns/SKILL.mdsource content
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
installed (TypeScript) or@elevenlabs/elevenlabs-js
(Python)elevenlabs- Familiarity with async/await patterns
- Understanding of error handling best practices
Instructions
Pattern 1: Singleton Client with Config
// 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
// 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<VoicePreset, { stability: number; similarity_boost: number; style: number; }> = { 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<ReadableStream> { 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<void> { const audio = await generateSpeech(options); await pipeline( Readable.fromWeb(audio as any), createWriteStream(outputPath) ); }
Pattern 3: Error Classification
// 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
// 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<T>( operation: () => Promise<T>, maxRetries = 3 ): Promise<T> { 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<T>; }
Pattern 5: Multi-Tenant Client Factory
// src/elevenlabs/multi-tenant.ts import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js"; const clients = new Map<string, ElevenLabsClient>(); 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
# 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 |
|---|---|---|
| All API errors | Maps HTTP to actionable codes |
| 429, 5xx | Auto-retry with exponential backoff |
| Singleton guard | Missing env var | Fails fast at startup, not at first call |
Resources
Next Steps
Apply patterns in
elevenlabs-core-workflow-a for TTS, or see elevenlabs-rate-limits for advanced throttling.