Skip to main content

API Best Practices

This guide outlines best practices for using the Uberduck API effectively in your applications. Following these recommendations will help you build robust, efficient, and maintainable integrations.

API Key Management

Secure Storage

  • Never store API keys in client-side code or public repositories
  • Use environment variables or secure vaults (AWS Secrets Manager, HashiCorp Vault, etc.)
  • Implement key rotation schedules for enhanced security
// Environment variable in Node.js
const apiKey = process.env.UBERDUCK_API_KEY;

// For client-side applications, proxy through your backend
app.post('/api/text-to-speech', async (req, res) => {
const response = await fetch('https://api.uberduck.ai/v1/text-to-speech', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.UBERDUCK_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(req.body)
});

const data = await response.json();
res.json(data);
});

Error Handling

Implement Proper Error Handling

  • Handle different error types appropriately
  • Provide meaningful error messages to users
  • Log errors for debugging and monitoring
async function generateSpeech(text, voice) {
try {
const response = await fetch('https://api.uberduck.ai/v1/text-to-speech', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
text,
voice,
model: voice.startsWith('polly') ? 'polly_neural' : 'google_wavenet'
})
});

if (!response.ok) {
const errorData = await response.json();
const errorCode = errorData.error?.code || 'unknown_error';

switch (errorCode) {
case 'voice_not_found':
throw new Error(`The voice "${voice}" could not be found. Please select a different voice.`);
case 'rate_limit_exceeded':
throw new Error('Rate limit exceeded. Please try again later.');
case 'text_too_long':
throw new Error('Text exceeds maximum length. Please use shorter text or split into multiple requests.');
default:
throw new Error(`Error generating speech: ${errorData.error?.message || 'Unknown error'}`);
}
}

return await response.json();
} catch (error) {
console.error('Speech generation failed:', error);
throw error;
}
}

Rate Limit Handling

  • Implement exponential backoff for retries
  • Monitor your usage to avoid hitting limits
  • Consider using the X-RateLimit-* headers to adapt your requests
async function fetchWithRetry(url, options, maxRetries = 3) {
let retries = 0;

while (retries < maxRetries) {
try {
const response = await fetch(url, options);

if (response.status === 429) {
// Rate limit exceeded
const retryAfter = parseInt(response.headers.get('X-RateLimit-Reset')) || 1;
console.log(`Rate limit exceeded. Retrying in ${retryAfter} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
retries++;
continue;
}

return response;
} catch (error) {
if (retries === maxRetries - 1) throw error;
retries++;
// Exponential backoff: 1s, 2s, 4s, etc.
const delay = 1000 * Math.pow(2, retries - 1);
console.log(`Request failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}

Performance Optimization

Implement Caching

  • Cache API responses to reduce redundant requests
  • Consider time-based or content-based caching strategies
  • Use CDNs for audio file distribution
// Simple in-memory cache for text-to-speech results
const ttsCache = new Map();

async function getCachedTTS(text, voice) {
// Create a unique key for this text + voice combination
const cacheKey = `${voice}:${text}`;

// Check if we have a cached result
if (ttsCache.has(cacheKey)) {
console.log('Cache hit!');
return ttsCache.get(cacheKey);
}

// If not cached, generate new audio
console.log('Cache miss, generating new audio...');
const result = await generateSpeech(text, voice);

// Cache the result (but don't cache errors)
ttsCache.set(cacheKey, result);

return result;
}

Batch Processing

  • Group related requests together to minimize API calls
  • Use parallel processing for independent requests
  • Balance between batching and real-time needs
// Process multiple texts in parallel with a concurrency limit
async function processMultipleTexts(texts, voice, concurrencyLimit = 3) {
const results = [];
const batches = [];

// Split texts into batches based on concurrency limit
for (let i = 0; i < texts.length; i += concurrencyLimit) {
batches.push(texts.slice(i, i + concurrencyLimit));
}

// Process each batch in parallel
for (const batch of batches) {
const batchPromises = batch.map(text => generateSpeech(text, voice));
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}

return results;
}

Resource Management

Clean Up Unused Resources

  • Download and store important audio files
  • Remember that API-generated audio URLs have expiration times
  • Implement proper resource lifecycle management
async function downloadAndStoreAudio(audioUrl, localPath) {
const response = await fetch(audioUrl);
if (!response.ok) throw new Error(`Failed to download audio: ${response.statusText}`);

const audioData = await response.arrayBuffer();

// Node.js environment
const fs = require('fs');
fs.writeFileSync(localPath, Buffer.from(audioData));

console.log(`Audio saved to ${localPath}`);
return localPath;
}

Content Guidelines

Text Preprocessing

  • Break down long text into manageable chunks (5-10 sentences)
  • Preprocess text to handle abbreviations, numbers, and special characters
  • Use punctuation to control pacing and intonation
function preprocessText(text) {
// Replace numbers with words for better pronunciation
text = text.replace(/\b(\d+)\b/g, (match, number) => {
return new Intl.NumberFormat('en-US', { style: 'spell-out' }).format(number);
});

// Replace common abbreviations
const abbreviations = {
'Dr.': 'Doctor',
'Mr.': 'Mister',
'Mrs.': 'Misses',
'Ms.': 'Miss',
'Prof.': 'Professor',
'etc.': 'etcetera',
'e.g.': 'for example',
'i.e.': 'that is'
};

for (const [abbr, expanded] of Object.entries(abbreviations)) {
text = text.replace(new RegExp(`\\b${abbr}\\b`, 'g'), expanded);
}

return text;
}

function splitIntoChunks(text, maxChunkLength = 5000) {
// Find natural break points: paragraphs, sentences, etc.
const paragraphs = text.split(/\n\n+/);
const chunks = [];
let currentChunk = '';

for (const paragraph of paragraphs) {
// If adding this paragraph would exceed the chunk limit
if (currentChunk.length + paragraph.length > maxChunkLength) {
// If the current chunk is not empty, add it to chunks
if (currentChunk.length > 0) {
chunks.push(currentChunk);
currentChunk = '';
}

// If the paragraph itself exceeds the limit, split it into sentences
if (paragraph.length > maxChunkLength) {
const sentences = paragraph.split(/(?<=[.!?])\s+/);
for (const sentence of sentences) {
if (currentChunk.length + sentence.length > maxChunkLength) {
if (currentChunk.length > 0) {
chunks.push(currentChunk);
currentChunk = '';
}
chunks.push(sentence);
} else {
currentChunk += (currentChunk ? ' ' : '') + sentence;
}
}
} else {
currentChunk = paragraph;
}
} else {
currentChunk += (currentChunk ? '\n\n' : '') + paragraph;
}
}

if (currentChunk.length > 0) {
chunks.push(currentChunk);
}

return chunks;
}

Voice Selection Strategy

Testing and Selection

  • Test multiple voices with your actual content
  • Consider the context and target audience
  • Maintain consistency across your application
async function findBestVoice(sampleText, voiceCandidates) {
const results = [];

// Generate speech with each candidate voice
for (const voice of voiceCandidates) {
try {
const result = await generateSpeech(sampleText, voice);
results.push({
voice,
audioUrl: result.audio_url,
durationSeconds: result.duration_seconds
});
} catch (error) {
console.error(`Error testing voice ${voice}:`, error);
}
}

console.log('Voice test results:');
console.table(results);

return results;
}

Monitoring and Analytics

Track Usage and Performance

  • Monitor API calls, success rates, and latency
  • Set up alerts for anomalies or errors
  • Analyze patterns to optimize usage
function trackAPICall(endpoint, parameters, startTime, success, errorMessage = null) {
const endTime = Date.now();
const duration = endTime - startTime;

// Simple console logging
console.log({
timestamp: new Date().toISOString(),
endpoint,
parameters,
duration_ms: duration,
success,
error: errorMessage
});

// In a real application, you would send this to your analytics system
// analytics.track('api_call', { ... });
}

async function generateSpeechWithTracking(text, voice) {
const startTime = Date.now();
try {
const result = await generateSpeech(text, voice);
trackAPICall('/v1/text-to-speech', { text, voice }, startTime, true);
return result;
} catch (error) {
trackAPICall('/v1/text-to-speech', { text, voice }, startTime, false, error.message);
throw error;
}
}

Testing

Implement Comprehensive Testing

  • Test with different types of input
  • Verify error handling and edge cases
  • Include integration tests for end-to-end verification
// Example Jest test
test('generateSpeech handles valid input correctly', async () => {
const result = await generateSpeech('Hello world', 'polly_joanna');

expect(result).toHaveProperty('audio_url');
expect(result.audio_url).toMatch(/^https:\/\/static\.uberduck\.ai\//);
expect(result).toHaveProperty('duration_seconds');
expect(typeof result.duration_seconds).toBe('number');
});

test('generateSpeech handles nonexistent voice', async () => {
await expect(
generateSpeech('Hello world', 'nonexistent_voice')
).rejects.toThrow('The voice "nonexistent_voice" could not be found');
});

Documentation

Maintain Internal Documentation

  • Document your integration approach
  • Keep track of voice selections and their uses
  • Document common issues and solutions

Example documentation template:

# Uberduck API Integration

## Overview
This document describes how we use the Uberduck API in our application.

## Voice Selection
- Product announcements: `polly_joanna` (professional, clear)
- Error messages: `polly_matthew` (authoritative but friendly)
- Tutorial content: `google_en-US-Wavenet-F` (engaging, natural)

## Common Issues
1. **Rate limiting**: If you see 429 errors, check our usage dashboard and consider increasing our plan.
2. **Audio playback issues**: Ensure the audio format matches what the client can play.

## Code References
- Main API client: `/src/services/tts-service.js`
- Voice configuration: `/src/config/voices.js`
- Error handling: `/src/utils/api-error-handler.js`

Security

Implement Security Best Practices

  • Use HTTPS for all API communications
  • Validate and sanitize user input
  • Implement proper access controls for API keys
function sanitizeUserInput(text) {
// Remove potentially harmful characters or patterns
const sanitized = text
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/<[^>]*>/g, ''); // Remove HTML tags

return sanitized;
}

async function generateSpeechFromUserInput(userText, voice) {
const sanitizedText = sanitizeUserInput(userText);
return await generateSpeech(sanitizedText, voice);
}

Cost Management

Optimize for Cost Efficiency

  • Cache frequently used audio
  • Monitor usage to stay within plan limits
  • Use appropriate voice/model combinations for your needs
function isHighValueContent(text, context) {
// Determine if this content warrants using a premium voice
if (context === 'marketing' || context === 'customer-facing') {
return true;
}

if (text.length > 500) {
return true; // Longer content benefits more from premium voices
}

return false;
}

async function costOptimizedTTS(text, context) {
// Choose voice based on content value
let voice, model;

if (isHighValueContent(text, context)) {
// Use premium voice for high-value content
voice = 'premium_voice_id';
model = 'premium_model';
} else {
// Use standard voice for routine content
voice = 'standard_voice_id';
model = 'standard_model';
}

return await generateSpeech(text, voice, model);
}

Conclusion

Following these best practices will help you build robust, efficient, and cost-effective applications with the Uberduck API. As you gain experience with the API, you'll develop additional practices specific to your use case.

For more information, refer to the Getting Started and API Reference documentation.