Error Handling

Understand error responses and how to handle them gracefully.

Error Response Format

All API errors follow a consistent JSON structure for easy parsing:

json
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "category": "error_category",
    "resolution": "Suggested steps to fix the issue"
  },
  "request_id": "req_abc123xyz",
  "timestamp": 1699820400.123
}
FieldDescription
successAlways false for error responses
error.codeMachine-readable error code for programmatic handling
error.messageHuman-readable description of the error
error.categoryError category (authentication, validation, rate_limit, etc.)
error.resolutionSuggested steps to resolve the issue
request_idUnique ID for tracking this request (useful for support)
timestampUnix timestamp when error occurred

HTTP Status Codes

The API uses standard HTTP status codes to indicate request outcome:

CodeStatusDescription
200OKRequest successful
400Bad RequestInvalid parameters or malformed request
401UnauthorizedMissing or invalid API key
403ForbiddenValid key but insufficient permissions
404Not FoundResource or endpoint not found
422UnprocessableValid request but failed validation
429Too Many RequestsRate limit exceeded
500Server ErrorInternal server error
503Service UnavailableService temporarily unavailable

Error Categories

Errors are grouped into categories for easier handling:

Authentication Errors

CodeMessageResolution
INVALID_API_KEY The API key provided is invalid Check your API key is correct and active
MISSING_API_KEY No API key provided Include X-API-Key header in your request
API_KEY_EXPIRED Your API key has expired Generate a new API key from your dashboard
API_KEY_REVOKED This API key has been revoked Contact support or generate a new key

Validation Errors

CodeMessageResolution
INVALID_TEXT Text parameter is invalid or empty Provide non-empty text (1-10,000 characters)
TEXT_TOO_LONG Text exceeds maximum length Split text into smaller chunks (max 10,000 chars)
INVALID_VOICE The specified voice does not exist Use /voices endpoint to get valid voice IDs
INVALID_MODEL The specified model is invalid Use /models endpoint to get valid model names
INVALID_FORMAT Unsupported output format Use mp3 or wav
INVALID_SPEED Speed value out of range Use a value between 0.5 and 2.0

Rate Limit Errors

CodeMessageResolution
RATE_LIMIT_EXCEEDED Too many requests Wait and retry with exponential backoff
QUOTA_EXCEEDED Monthly character quota exhausted Upgrade your plan or wait for quota reset
DAILY_LIMIT_REACHED Daily request limit reached Wait until tomorrow or upgrade your plan

Permission Errors

CodeMessageResolution
MODEL_ACCESS_DENIED Your plan doesn't include this model Upgrade to a plan with access to this model
VOICE_ACCESS_DENIED Premium voice not available on your plan Upgrade to access premium voices
FEATURE_DISABLED This feature is not enabled for your account Contact support to enable this feature

Handling Errors

Best practices for robust error handling:

python
import requests
import time

def generate_tts_with_retry(text, voice_id, max_retries=3):
    """Generate TTS with automatic retry on transient errors."""
    url = "https://yourvoic.com/api/v1/tts/generate"
    headers = {"X-API-Key": "your_api_key"}
    data = {"text": text, "voice_id": voice_id}
    
    for attempt in range(max_retries):
        response = requests.post(url, json=data, headers=headers)
        
        if response.status_code == 200:
            return response.content  # Success
        
        error = response.json().get("error", {})
        error_code = error.get("code")
        
        # Don't retry client errors (except rate limits)
        if response.status_code == 400:
            raise ValueError(f"Invalid request: {error.get('message')}")
        
        if response.status_code == 401:
            raise AuthError(f"Authentication failed: {error.get('message')}")
        
        if response.status_code == 403:
            raise PermissionError(f"Access denied: {error.get('message')}")
        
        if response.status_code == 429:
            # Rate limited - wait and retry
            retry_after = int(response.headers.get("Retry-After", 60))
            print(f"Rate limited. Waiting {retry_after}s...")
            time.sleep(retry_after)
            continue
        
        if response.status_code >= 500:
            # Server error - exponential backoff
            wait_time = (2 ** attempt) + random.random()
            print(f"Server error. Retrying in {wait_time:.1f}s...")
            time.sleep(wait_time)
            continue
    
    raise Exception(f"Max retries exceeded. Last error: {error_code}")
javascript
async function generateTTSWithRetry(text, voiceId, maxRetries = 3) {
    const url = 'https://yourvoic.com/api/v1/tts/generate';
    
    for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
            const response = await fetch(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-API-Key': 'your_api_key'
                },
                body: JSON.stringify({ text, voice_id: voiceId })
            });
            
            if (response.ok) {
                return await response.blob();
            }
            
            const error = await response.json();
            const errorCode = error.error?.code;
            
            // Handle specific errors
            switch (response.status) {
                case 400:
                    throw new Error(`Invalid request: ${error.error?.message}`);
                case 401:
                    throw new Error(`Auth failed: ${error.error?.message}`);
                case 403:
                    throw new Error(`Access denied: ${error.error?.message}`);
                case 429:
                    const retryAfter = response.headers.get('Retry-After') || 60;
                    console.log(`Rate limited. Waiting ${retryAfter}s...`);
                    await sleep(retryAfter * 1000);
                    continue;
                default:
                    if (response.status >= 500) {
                        const waitTime = Math.pow(2, attempt) * 1000;
                        console.log(`Server error. Retrying in ${waitTime}ms...`);
                        await sleep(waitTime);
                        continue;
                    }
            }
        } catch (e) {
            if (attempt === maxRetries - 1) throw e;
        }
    }
    
    throw new Error('Max retries exceeded');
}

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

Retry Strategy

Different errors require different retry approaches:

Error TypeShould Retry?Strategy
400 Validation ❌ No Fix the request parameters
401 Auth ❌ No Check/refresh API key
403 Permission ❌ No Upgrade plan or contact support
429 Rate Limit ✅ Yes Wait for Retry-After header duration
500 Server ✅ Yes Exponential backoff (1s, 2s, 4s...)
503 Unavailable ✅ Yes Wait and retry with backoff
💡 Exponential Backoff

For server errors, use exponential backoff: wait 1s, then 2s, then 4s, etc. Add random jitter (0-1s) to prevent thundering herd issues when many clients retry simultaneously.

Debugging Tips

  • Save the request_id - Include it when contacting support for faster resolution
  • Check response headers - Rate limit headers show remaining quota
  • Validate locally - Check text length and parameters before making API calls
  • Use the health endpoint - Check /health to verify API availability
  • Log full responses - The resolution field often contains the fix
bash
# Check API health status
curl -s https://yourvoic.com/api/v1/health | jq

# Test your API key
curl -s -H "X-API-Key: your_key" \
  https://yourvoic.com/api/v1/usage | jq