Disciplines · Integrations

ElevenLabs Troubleshooting Guide

1.

13sections3 minread

On this page

This guide covers common issues when working with the ElevenLabs integration and their solutions.

Table of Contents#

Authentication Issues#

Error: "Invalid API Key" (401)#

Symptoms:

  • ElevenLabsProviderError with code INVALID_API_KEY
  • HTTP 401 Unauthorized response

Causes:

  1. Missing API key
  2. Invalid or expired API key
  3. API key with insufficient permissions

Solutions:

typescript
// 1. Verify API key is set
if (!process.env.ELEVENLABS_API_KEY) {
  throw new Error('ELEVENLABS_API_KEY environment variable not set');
}

// 2. Test API key validity
try {
  const provider = new ElevenLabsProvider({
    apiKey: process.env.ELEVENLABS_API_KEY,
  });
  await provider.getUsage(); // Simple test call
  console.log('API key is valid');
} catch (error) {
  if (error.code === 'INVALID_API_KEY') {
    console.error('API key is invalid or expired');
    // Regenerate at: https://elevenlabs.io/api
  }
}

Verification steps:

  1. Log into ElevenLabs dashboard
  2. Navigate to Profile > API Key
  3. Regenerate key if necessary
  4. Update ELEVENLABS_API_KEY in your environment

Error: "Unauthorized" for Voice Cloning#

Symptoms:

  • Can synthesize but cannot clone voices
  • 403 Forbidden on clone endpoints

Cause: Voice cloning requires paid subscription tier

Solution:

typescript
// Check subscription level
const subscription = await provider.getSubscription();
if (subscription.tier === 'free') {
  console.error('Voice cloning requires paid subscription');
  console.log('Upgrade at: https://elevenlabs.io/subscription');
}

Rate Limiting#

Error: "Rate Limited" (429)#

Symptoms:

  • ElevenLabsProviderError with code RATE_LIMITED
  • HTTP 429 Too Many Requests
  • Requests fail in bursts

Rate Limits by Tier:

Tier Requests/min Concurrent
Free 20 2
Starter 100 5
Creator 500 10
Pro 1000 20
Enterprise Custom Custom

Solutions:

typescript
// 1. Built-in retry with exponential backoff
const provider = new ElevenLabsProvider({
  apiKey: process.env.ELEVENLABS_API_KEY,
  maxRetries: 5,
  retryDelayMs: 1000, // Base delay
});

// 2. Manual rate limiting with queue
import pLimit from 'p-limit';

const limit = pLimit(5); // 5 concurrent requests
const texts = ['text1', 'text2', 'text3' /* ... */];

const results = await Promise.all(
  texts.map((text) =>
    limit(() =>
      provider.synthesize({
        text,
        voice_id: 'voice_id',
      })
    )
  )
);

// 3. Add delays between requests
async function synthesizeWithDelay(
  texts: string[],
  delayMs: number = 200
): Promise<Buffer[]> {
  const results: Buffer[] = [];
  for (const text of texts) {
    const response = await provider.synthesize({
      text,
      voice_id: 'voice_id',
    });
    results.push(response.audio);
    await new Promise((resolve) => setTimeout(resolve, delayMs));
  }
  return results;
}

Handling Rate Limit Headers#

typescript
// The provider automatically handles Retry-After headers
// For custom handling:
try {
  await provider.synthesize({ text, voice_id });
} catch (error) {
  if (error.code === 'RATE_LIMITED') {
    const retryAfter = error.retryAfter || 60; // seconds
    console.log(`Rate limited. Retry after ${retryAfter}s`);
    await new Promise((r) => setTimeout(r, retryAfter * 1000));
    // Retry the request
  }
}

Quota and Usage Issues#

Error: "Quota Exceeded"#

Symptoms:

  • ElevenLabsProviderError with code QUOTA_EXCEEDED
  • Cannot synthesize despite valid API key

Diagnosis:

typescript
// Check current usage
const usage = await provider.getUsage();
console.log(`Characters used: ${usage.character_count}`);
console.log(`Character limit: ${usage.character_limit}`);
console.log(`Remaining: ${usage.character_limit - usage.character_count}`);
console.log(`Reset date: ${usage.next_reset_date}`);

if (usage.character_count >= usage.character_limit) {
  console.error('Quota exceeded');
  if (usage.can_extend_character_limit) {
    console.log('You can extend your limit in the dashboard');
  }
}

Solutions:

  1. Wait for monthly reset
  2. Upgrade subscription tier
  3. Purchase character add-on
  4. Optimize text to reduce character count

Optimizing Character Usage#

typescript
// 1. Remove unnecessary whitespace
const optimizedText = text.replace(/\s+/g, ' ').trim();

// 2. Use abbreviations for common words
const abbreviations: Record<string, string> = {
  'for example': 'e.g.',
  'that is': 'i.e.',
  approximately: 'approx.',
};

// 3. Estimate cost before synthesis
function estimateCharacterCost(text: string): number {
  // ElevenLabs counts all characters including spaces
  return text.length;
}

const cost = estimateCharacterCost(text);
const remaining = usage.character_limit - usage.character_count;
if (cost > remaining) {
  throw new Error(`Text too long: ${cost} chars, only ${remaining} remaining`);
}

Voice Issues#

Error: "Voice Not Found"#

Symptoms:

  • ElevenLabsProviderError with code VOICE_NOT_FOUND
  • HTTP 404 on synthesis request

Causes:

  1. Invalid voice ID
  2. Voice was deleted
  3. Voice belongs to different account
  4. Cloned voice not yet processed

Solutions:

typescript
// 1. Validate voice ID before use
async function validateVoiceId(voiceId: string): Promise<boolean> {
  try {
    await provider.getVoice(voiceId);
    return true;
  } catch (error) {
    if (error.code === 'VOICE_NOT_FOUND') {
      console.error(`Voice ${voiceId} not found`);
      return false;
    }
    throw error;
  }
}

// 2. List available voices
const voices = await provider.listVoices();
const voiceIds = voices.map((v) => v.voice_id);
if (!voiceIds.includes(targetVoiceId)) {
  console.error('Voice not in available list');
  console.log(
    'Available voices:',
    voices.map((v) => `${v.name}: ${v.voice_id}`)
  );
}

// 3. Use fallback voice
const FALLBACK_VOICE_ID = 'EXAVITQu4vr4xnSDxMaL'; // Sarah

async function synthesizeWithFallback(text: string, voiceId: string) {
  try {
    return await provider.synthesize({ text, voice_id: voiceId });
  } catch (error) {
    if (error.code === 'VOICE_NOT_FOUND') {
      console.warn(`Voice ${voiceId} not found, using fallback`);
      return await provider.synthesize({ text, voice_id: FALLBACK_VOICE_ID });
    }
    throw error;
  }
}

Voice Sounds Different Than Expected#

Symptoms:

  • Voice output doesn't match preview
  • Voice quality inconsistent

Causes:

  1. Different model being used
  2. Voice settings not applied
  3. Text context affecting delivery

Solutions:

typescript
// 1. Ensure consistent model
const response = await provider.synthesize({
  text,
  voice_id: 'voice_id',
  model_id: 'eleven_v3', // Explicitly set model
});

// 2. Apply optimal voice settings
const response = await provider.synthesize({
  text,
  voice_id: 'voice_id',
  voice_settings: {
    stability: 0.75, // Consistent delivery
    similarity_boost: 0.85, // Match original voice
    style: 0.0, // Neutral style
    use_speaker_boost: true,
  },
});

// 3. Provide context for better intonation
const response = await provider.synthesize({
  text: 'Current sentence to speak.',
  voice_id: 'voice_id',
  previous_text: 'Context from before.', // Helps with intonation
  next_text: 'What comes after.', // Helps with pacing
});

Audio Quality Issues#

Audio Sounds Robotic or Unnatural#

Causes:

  1. Using legacy model
  2. Inappropriate voice settings
  3. Text formatting issues

Solutions:

typescript
// 1. Use latest model for best quality
const response = await provider.synthesize({
  text,
  voice_id: 'voice_id',
  model_id: 'eleven_v3', // Most natural sounding
});

// 2. Adjust stability for more natural variation
voice_settings: {
  stability: 0.5,        // Lower = more variation
  similarity_boost: 0.7,
  style: 0.3            // Some expressiveness
}

// 3. Add natural pauses with audio tags
const naturalText = text
  .replace(/\. /g, '. <break time="0.3s" /> ')
  .replace(/\? /g, '? <break time="0.4s" /> ')
  .replace(/, /g, ', <break time="0.1s" /> ');

Audio Has Artifacts or Glitches#

Causes:

  1. Text contains problematic characters
  2. Very long text without breaks
  3. Special characters not handled

Solutions:

typescript
// 1. Clean text before synthesis
function cleanTextForSynthesis(text: string): string {
  return (
    text
      // Remove control characters
      .replace(/[\x00-\x1F\x7F]/g, '')
      // Normalize quotes
      .replace(/[""]/g, '"')
      .replace(/['']/g, "'")
      // Remove emojis (unless supported)
      .replace(
        /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}]/gu,
        ''
      )
      // Normalize whitespace
      .replace(/\s+/g, ' ')
      .trim()
  );
}

// 2. Split long text at natural boundaries
function splitAtSentences(text: string, maxLength: number = 500): string[] {
  const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
  const chunks: string[] = [];
  let current = '';

  for (const sentence of sentences) {
    if ((current + sentence).length > maxLength) {
      if (current) chunks.push(current.trim());
      current = sentence;
    } else {
      current += sentence;
    }
  }
  if (current) chunks.push(current.trim());

  return chunks;
}

Wrong Output Format#

Symptoms:

  • File won't play
  • Wrong sample rate
  • Unexpected file size

Solutions:

typescript
// Explicitly specify format
const response = await provider.synthesize({
  text,
  voice_id: 'voice_id',
  output_format: 'mp3_44100_128', // Clear format specification
});

// Verify output
const audioBuffer = response.audio;
console.log(`Output size: ${audioBuffer.length} bytes`);
console.log(`Expected MP3 header: ${audioBuffer.slice(0, 3).toString()}`);
// MP3 should start with 'ID3' or 0xFF 0xFB

Streaming Issues#

WebSocket Connection Fails#

Symptoms:

  • Cannot establish streaming connection
  • Connection drops immediately
  • Timeout errors

Causes:

  1. Network/firewall blocking WebSocket
  2. Invalid configuration
  3. Server temporarily unavailable

Solutions:

typescript
// 1. Verify WebSocket URL
const provider = new ElevenLabsProvider({
  apiKey: process.env.ELEVENLABS_API_KEY,
  websocketUrl: 'wss://api.elevenlabs.io/v1/text-to-speech',
  websocketTimeoutSeconds: 30, // Increase timeout
});

// 2. Add connection error handling
provider.on('streaming-error', (event) => {
  console.error(`Stream error: ${event.error}`);

  if (event.error.includes('connection')) {
    // Network issue - check proxy settings
    console.log('Check firewall/proxy for WebSocket support');
  }
});

// 3. Implement reconnection logic
async function streamWithReconnect(
  text: string,
  maxRetries: number = 3
): Promise<void> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const session = await provider.startStreaming({
        voice_id: 'voice_id',
        model_id: 'eleven_flash_v2_5',
      });
      await provider.sendStreamingText(session.sessionId, text);
      return;
    } catch (error) {
      console.error(`Stream attempt ${attempt + 1} failed:`, error);
      if (attempt < maxRetries - 1) {
        await new Promise((r) => setTimeout(r, 1000 * (attempt + 1)));
      }
    }
  }
  throw new Error('Failed to establish stream after retries');
}

Stream Audio Choppy or Delayed#

Causes:

  1. Network latency
  2. Buffer underrun
  3. Wrong model for real-time use

Solutions:

typescript
// 1. Use Flash model for lowest latency
const session = await provider.startStreaming({
  voice_id: 'voice_id',
  model_id: 'eleven_flash_v2_5', // ~75ms latency
});

// 2. Optimize latency settings
const session = await provider.startStreaming({
  voice_id: 'voice_id',
  model_id: 'eleven_flash_v2_5',
  optimize_streaming_latency: 4, // Maximum latency optimization
});

// 3. Pre-buffer audio chunks
const audioBuffer: Buffer[] = [];
const MIN_BUFFER_SIZE = 3; // Wait for 3 chunks before playing

provider.on('streaming-audio', (event) => {
  audioBuffer.push(event.audio);

  if (audioBuffer.length >= MIN_BUFFER_SIZE) {
    // Start playing
    playNextChunk();
  }
});

Voice Cloning Issues#

Clone Request Fails#

Symptoms:

  • 400 Bad Request on clone
  • "Invalid audio file" error

Causes:

  1. Audio file too short/long
  2. Wrong audio format
  3. Poor audio quality

Requirements:

  • Minimum duration: 30 seconds per file
  • Maximum files: 25 for instant clone
  • Formats: MP3, WAV, M4A
  • Quality: Clear speech, minimal background noise

Solutions:

typescript
// 1. Validate audio files before upload
import { parseBuffer } from 'music-metadata';

async function validateCloneAudio(buffer: Buffer): Promise<void> {
  const metadata = await parseBuffer(buffer);

  // Check duration
  if (metadata.format.duration && metadata.format.duration < 30) {
    throw new Error('Audio must be at least 30 seconds');
  }

  // Check format
  const validFormats = ['mp3', 'wav', 'm4a', 'mpeg'];
  if (!validFormats.includes(metadata.format.container?.toLowerCase() || '')) {
    throw new Error(`Invalid format. Use: ${validFormats.join(', ')}`);
  }

  // Check for mono/stereo
  if (
    metadata.format.numberOfChannels &&
    metadata.format.numberOfChannels > 2
  ) {
    throw new Error('Audio must be mono or stereo');
  }
}

// 2. Normalize audio before upload
// Use ffmpeg to standardize format
// ffmpeg -i input.wav -ar 44100 -ac 1 -b:a 128k output.mp3

Cloned Voice Sounds Different#

Causes:

  1. Insufficient training data
  2. Background noise in samples
  3. Multiple speakers in samples

Solutions:

typescript
// 1. Use more/better samples for professional clone
const clonedVoice = await provider.cloneVoiceProfessional({
  name: 'Custom Voice',
  files: highQualitySamples, // 30+ minutes recommended
  description: 'Clear speech with consistent tone',
  remove_background_noise: true,
});

// 2. Use optimal settings for cloned voices
const response = await provider.synthesize({
  text,
  voice_id: clonedVoice.voice_id,
  voice_settings: {
    stability: 0.85, // Higher for cloned voices
    similarity_boost: 0.9, // High to match original
    style: 0.0, // Minimal style deviation
  },
});

Model-Specific Issues#

Audio Tags Not Working#

Cause: Audio tags only work with eleven_v3 model

Solution:

typescript
// Verify model supports audio tags
const text = 'Hello <break time="1s" /> world';

const response = await provider.synthesize({
  text,
  voice_id: 'voice_id',
  model_id: 'eleven_v3', // Required for audio tags
});

// Check model capabilities
const models = await provider.getModels();
const v3Model = models.find((m) => m.model_id === 'eleven_v3');
console.log('Audio tags supported:', v3Model?.can_use_audio_tags);

Model Not Available#

Symptoms:

  • "Model not found" error
  • 400 Bad Request with model ID

Solutions:

typescript
// 1. List available models
const models = await provider.getModels();
console.log(
  'Available models:',
  models.map((m) => m.model_id)
);

// 2. Use model availability check
async function getAvailableModel(
  preferred: string,
  fallback: string
): Promise<string> {
  const models = await provider.getModels();
  const modelIds = models.map((m) => m.model_id);

  if (modelIds.includes(preferred)) return preferred;
  if (modelIds.includes(fallback)) return fallback;

  throw new Error(`Neither ${preferred} nor ${fallback} available`);
}

const modelId = await getAvailableModel('eleven_v3', 'eleven_multilingual_v2');

Network and Connectivity#

Timeout Errors#

Symptoms:

  • Request timeout after 30 seconds
  • Connection reset errors

Solutions:

typescript
// 1. Increase timeout for long text
const provider = new ElevenLabsProvider({
  apiKey: process.env.ELEVENLABS_API_KEY,
  timeout: 60000, // 60 seconds
});

// 2. Split long text into chunks
const MAX_CHUNK_LENGTH = 2500;

async function synthesizeLongText(text: string): Promise<Buffer> {
  const chunks = splitAtSentences(text, MAX_CHUNK_LENGTH);
  const audioChunks: Buffer[] = [];

  for (const chunk of chunks) {
    const response = await provider.synthesize({
      text: chunk,
      voice_id: 'voice_id',
    });
    audioChunks.push(response.audio);
  }

  return Buffer.concat(audioChunks);
}

SSL/TLS Errors#

Symptoms:

  • Certificate errors
  • Handshake failures

Solutions:

typescript
// For development/debugging only
// NOT recommended for production
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

// Better: Update Node.js CA certificates
// npm install -g ca-certificates

Performance Issues#

Slow Response Times#

Diagnosis:

typescript
// Enable request logging
const provider = new ElevenLabsProvider({
  apiKey: process.env.ELEVENLABS_API_KEY,
  logRequests: true,
});

// Check statistics
const stats = provider.getStats();
console.log(`Average latency: ${stats.averageLatencyMs}ms`);
console.log(`Total requests: ${stats.totalRequests}`);
console.log(`By model:`, stats.requestsByModel);

Solutions:

typescript
// 1. Use faster models for real-time
model_id: 'eleven_flash_v2_5';

// 2. Optimize latency
optimize_streaming_latency: 4;

// 3. Pre-warm voice cache
await provider.listVoices(); // Cache voices
await provider.getVoice('frequently_used_voice'); // Pre-fetch

// 4. Parallel processing for multiple texts
const results = await Promise.all(
  texts.map((text) =>
    provider.synthesize({
      text,
      voice_id: 'voice_id',
      model_id: 'eleven_flash_v2_5',
    })
  )
);

Debugging Tools#

Enable Verbose Logging#

typescript
// Configure provider with logging
const provider = new ElevenLabsProvider({
  apiKey: process.env.ELEVENLABS_API_KEY,
  logRequests: true,
});

// Listen to all events
provider.on('synthesis-start', (e) => console.log('Start:', e));
provider.on('synthesis-complete', (e) => console.log('Complete:', e));
provider.on('synthesis-error', (e) => console.error('Error:', e));
provider.on('rate-limited', (e) => console.warn('Rate limited:', e));

Health Check Endpoint#

typescript
async function elevenLabsHealthCheck(): Promise<{
  status: 'healthy' | 'degraded' | 'down';
  details: Record<string, unknown>;
}> {
  try {
    const start = Date.now();

    // Test basic connectivity
    const usage = await provider.getUsage();
    const apiLatency = Date.now() - start;

    // Test voice list
    const voicesStart = Date.now();
    const voices = await provider.listVoices();
    const voiceLatency = Date.now() - voicesStart;

    return {
      status: 'healthy',
      details: {
        apiLatencyMs: apiLatency,
        voiceLatencyMs: voiceLatency,
        voicesAvailable: voices.length,
        charactersRemaining: usage.character_limit - usage.character_count,
        quotaResetDate: usage.next_reset_date,
      },
    };
  } catch (error) {
    return {
      status: error.code === 'RATE_LIMITED' ? 'degraded' : 'down',
      details: {
        error: error.message,
        code: error.code,
      },
    };
  }
}

Debug Request/Response#

typescript
// Log raw HTTP for debugging
import { createLogger } from '@oshun/logging';

const logger = createLogger({ service: 'elevenlabs-debug' });

// Wrap provider methods for debugging
const originalSynthesize = provider.synthesize.bind(provider);
provider.synthesize = async (request) => {
  logger.debug('Synthesis request', {
    text_length: request.text.length,
    voice_id: request.voice_id,
    model_id: request.model_id,
  });

  try {
    const response = await originalSynthesize(request);
    logger.debug('Synthesis response', {
      audio_size: response.audio.length,
      latency_ms: response.latencyMs,
    });
    return response;
  } catch (error) {
    logger.error('Synthesis failed', {
      error: error.message,
      code: error.code,
    });
    throw error;
  }
};

Getting Help#

Support Resources#

  • ElevenLabs Documentation: https://docs.elevenlabs.io
  • API Status Page: https://status.elevenlabs.io
  • Discord Community: https://discord.gg/elevenlabs
  • Support Email: support@elevenlabs.io

Information to Include in Support Requests#

  1. API key prefix (first 8 characters)
  2. Error code and message
  3. Request parameters (without sensitive data)
  4. Timestamp and timezone
  5. Subscription tier
  6. Node.js version
  7. Provider version
typescript
// Generate debug info for support
async function getDebugInfo(): Promise<string> {
  const usage = await provider.getUsage();
  const stats = provider.getStats();

  return JSON.stringify(
    {
      nodeVersion: process.version,
      timestamp: new Date().toISOString(),
      usage: {
        charactersUsed: usage.character_count,
        limit: usage.character_limit,
      },
      stats: {
        totalRequests: stats.totalRequests,
        successRate:
          (stats.successfulRequests / stats.totalRequests) * 100 + '%',
        avgLatency: stats.averageLatencyMs + 'ms',
      },
    },
    null,
    2
  );
}