JavaScript SDK
Official JavaScript/TypeScript SDK for the TTS SaaS Platform
Introduction
The TTS SaaS JavaScript SDK provides a simple, type-safe way to integrate text-to-speech functionality into your applications. Built with developer experience in mind, the SDK handles authentication, caching, error handling, and provider-specific differences automatically.
Automatic Caching
Identical text is cached, reducing costs by 50-75%
Multi-Provider
OpenAI, Google TTS, Azure, Amazon Polly
Type-Safe
Full TypeScript support with exported types
Installation
Install the SDK using npm or yarn:
npm install @your-org/tts-sdkyarn add @your-org/tts-sdkQuick Start
Get started with just a few lines of code:
import { TtsClient } from '@your-org/tts-sdk';
// Initialize the client
const client = new TtsClient({
apiKey: 'your-api-key-here'
});
// Convert text to speech
const result = await client.convert('Hello, world!', {
voice: 'alloy'
});
// Play audio in browser
const audio = new Audio(result.chunks[0].audioUrl);
audio.play();
// Check caching statistics
console.log(`Cached: ${result.stats.cachedChunks} / ${result.stats.totalChunks} chunks`);Authentication
Getting Your API Key
- Log in to your TTS SaaS dashboard
- Navigate to Settings โ API Keys
- Click Create New API Key
- Copy the key (it won't be shown again)
Using Your API Key
const client = new TtsClient({
apiKey: 'tts_sk_1234567890abcdef' // Your API key
});Security Best Practices
- Never expose API keys in client-side code committed to version control
- Use environment variables:
process.env.TTS_API_KEY - For browser apps: Proxy requests through your backend
- For Node.js apps: Load from
.envfile
Basic Usage
Converting Text to Speech
The convert() method is the main entry point:
const result = await client.convert('Welcome to our website!', {
voice: 'alloy'
});Playing Audio in the Browser
// Single chunk (short text)
if (result.chunks.length === 1) {
const audio = new Audio(result.chunks[0].audioUrl);
await audio.play();
}
// Multiple chunks (long text) - play sequentially
for (const chunk of result.chunks) {
if (chunk.audioUrl) {
const audio = new Audio(chunk.audioUrl);
await audio.play();
await new Promise(resolve =>{
audio.onended = resolve;
});
}}Playing Audio in Node.js
import https from 'https';
import fs from 'fs';
const result = await client.convert('Text to convert');
// Download audio chunk
for (const chunk of result.chunks) {
if (chunk.audioUrl) {
const file = fs.createWriteStream(`chunk_${chunk.sequence}.mp3`);
https.get(chunk.audioUrl, response =>{
response.pipe(file);
});
}}API Reference
TtsClientConfig
| Property | Type | Required | Description |
|---|---|---|---|
| apiKey | string | โ | Your API key from the dashboard |
| baseUrl | string | โ | Custom API endpoint (for testing) |
| timeout | number | โ | Request timeout in ms (default: 30000) |
ConvertOptions
| Property | Type | Default | Description |
|---|---|---|---|
| voice | string | 'alloy' | Voice ID to use |
| region | string | 'NA_WEST' | Storage region |
| languageCode | string | auto | Language code for Google voices |
| audioFormat | 'mp3' | 'wav' | 'ogg' | provider | Audio output format |
ConvertResult
interface ConvertResult {
chunks: AudioChunkDto[];
stats: GenerationStats;
}
interface AudioChunkDto {
sequence: number; // Chunk order (0-based)
audioUrl: string | null;
duration: number | null;
sizeBytes: number | null;
cacheKey: string | null;
isCached: boolean;
}
interface GenerationStats {
totalChunks: number;
cachedChunks: number;
generatedChunks: number;
totalDuration: number;
processingTimeMs: number;
}Example Response
{
"chunks": [
{
"sequence": 0,
"audioUrl": "https://cdn.example.com/audio/abc123.mp3",
"duration": 3.5,
"sizeBytes": 56000,
"cacheKey": "tts_hash_abc123_chunk_0",
"isCached": false
}
],
"stats": {
"totalChunks": 1,
"cachedChunks": 0,
"generatedChunks": 1,
"totalDuration": 3.5,
"processingTimeMs": 1240
}}Utility Methods
getTextHash()
Get the SHA-256 hash of text (matches backend hashing for debugging):
const hash = await client.getTextHash('Hello world');
console.log(hash); // "64ec88ca00b268e5ba1a35678a1b5316d212f4f366b2477232534a8aeca37f3c"Use Cases: Debug cache misses, verify text normalization, generate stable IDs for analytics
normalizeText()
See how the backend will normalize text before hashing:
const normalized = client.normalizeText(' HELLO World ');
console.log(normalized); // "hello world"Normalization Rules: 1. Trim leading/trailing whitespace, 2. Convert to lowercase, 3. Collapse multiple spaces to single space
Available Voices
OpenAI Voices
All OpenAI voices support multi-language text (auto-detected):
alloy Neutral, balanced โข General purpose
echo Male, clear โข Professional narration
fable Warm, expressive โข Storytelling
onyx Deep, authoritative โข News
nova Female, energetic โข Marketing
shimmer Soft, gentle โข Meditation
await client.convert('Hello world', { voice: 'nova' });Audio Format: MP3 only
Google Voices
Google voices include language codes in the voice ID:
| Voice ID | Language | Type |
|---|---|---|
| en-US-Chirp3-HD | English (US) | Neural HD |
| en-GB-Chirp3-HD | English (UK) | Neural HD |
| fr-FR-Chirp3-HD | French | Neural HD |
| es-ES-Chirp3-HD | Spanish | Neural HD |
| de-DE-Chirp3-HD | German | Neural HD |
// Basic usage (language auto-extracted from voice ID)
await client.convert('Bonjour!', {
voice: 'fr-FR-Chirp3-HD'
});
// Advanced: Override language code
await client.convert('Hello', {
voice: 'en-US-Chirp3-HD',
languageCode: 'en-US' // optional override
});Audio Formats: MP3, WAV, OGG
Advanced Usage
Custom Audio Formats
Different providers support different audio formats:
// OpenAI (MP3 only)
await client.convert('Test', {
voice: 'alloy',
audioFormat: 'mp3'
});
// Google (MP3, WAV, OGG)
await client.convert('Test', {
voice: 'en-US-Chirp3-HD',
audioFormat: 'wav' // or 'ogg'
});Format Comparison
| Format | Size | Quality | Browser Support | Use Case |
|---|---|---|---|---|
| MP3 | Small | Good | Excellent | Web streaming, mobile |
| WAV | Large | Excellent | Excellent | High-fidelity, editing |
| OGG | Medium | Very Good | Good | Balance of size/quality |
Regional Endpoints
Choose storage regions based on your users' location:
// European users
await client.convert('Hello', { region: 'EU_WEST' });
// US users
await client.convert('Hello', { region: 'NA_WEST' });
// Asian users
await client.convert('Hello', { region: 'ASIA_EAST' });Benefits
- Lower latency for users
- Data residency compliance (GDPR, etc.)
- Reduced bandwidth costs
Custom Timeout
Adjust timeout for slow networks or long texts:
const client = new TtsClient({
apiKey: 'your-key',
timeout: 60000 // 60 seconds (default: 30s)
});Multi-Language Support
OpenAI voices auto-detect language:
// Automatically detects French
await client.convert('Bonjour le monde', { voice: 'alloy' });
// Automatically detects Spanish
await client.convert('Hola mundo', { voice: 'nova' });
// Automatically detects Japanese
await client.convert('ใใใซใกใฏไธ็', { voice: 'shimmer' });Google voices require language-specific voice IDs:
// French
await client.convert('Bonjour', { voice: 'fr-FR-Chirp3-HD' });
// Spanish
await client.convert('Hola', { voice: 'es-ES-Chirp3-HD' });Error Handling
The SDK throws specific error types for different failure scenarios:
TtsAuthenticationError (401)
Thrown when the API key is invalid or missing.
if (error instanceof TtsAuthenticationError) {
console.error('Invalid API key:', error.message);
}TtsQuotaExceededError (402)
Thrown when character or bandwidth quota is exceeded. Includes quotaInfo with usage details.
if (error instanceof TtsQuotaExceededError) {
console.log('Used:', error.quotaInfo.charactersUsed);
console.log('Limit:', error.quotaInfo.charactersLimit);
}TtsValidationError (400)
Thrown when request parameters are invalid (empty text, exceeds 5000 chars, invalid voice).
TtsNetworkError
Thrown when network request fails (timeout, no connection, CORS).
TtsServerError (5xx)
Thrown when the server returns 500, 502, 503, or 504.
Comprehensive Error Handling
import {
TtsClient,
TtsAuthenticationError,
TtsQuotaExceededError,
TtsValidationError,
TtsNetworkError,
TtsServerError
} from '@your-org/tts-sdk';
try {
const result = await client.convert('Hello world', { voice: 'alloy' });
// Success - play audio
} catch (error) {
if (error instanceof TtsAuthenticationError) {
// Invalid API key
console.error('Authentication failed. Please check your API key.');
window.location.href = '/login';
} else if (error instanceof TtsQuotaExceededError) {
// Quota exceeded
console.warn('Quota exceeded:', error.quotaInfo);
showUpgradePrompt(error.quotaInfo.upgradeUrl);
} else if (error instanceof TtsValidationError) {
// Invalid input
console.error('Invalid input:', error.message);
showErrorMessage(error.message);
} else if (error instanceof TtsNetworkError) {
// Network failure
console.error('Network error:', error.message);
showRetryButton();
} else if (error instanceof TtsServerError) {
// Server error
console.error('Server error:', error.statusCode);
showGenericError();
} else {
// Unexpected error
console.error('Unexpected error:', error);
throw error;
}}Quota Exceeded Handling
Show users their quota status and upgrade options:
try {
await client.convert(text);
} catch (error) {
if (error instanceof TtsQuotaExceededError) {
const { quotaInfo } = error;
// Show friendly message
alert(`
You've used ${quotaInfo.charactersUsed} of ${quotaInfo.charactersLimit} characters.
${quotaInfo.charactersRemaining} characters remaining.
Upgrade your plan to continue: ${quotaInfo.upgradeUrl}
`);
}}Retry Logic
Implement retry for transient network errors:
async function convertWithRetry(text: string, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await client.convert(text);
} catch (error) {
lastError = error;
// Only retry network errors
if (error instanceof TtsNetworkError) {
console.log(`Attempt ${attempt} failed, retrying...`);
await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); // Exponential backoff
continue;
}
// Don't retry other errors
throw error;
}}
throw lastError;
}How Caching Works
The SDK automatically optimizes costs through intelligent caching.
Automatic Text Hashing
When you call convert(), the SDK:
- Normalizes text (trim, lowercase, collapse spaces)
- Generates SHA-256 hash
- Backend checks if hash exists in cache
- Returns cached audio instantly (no TTS API call)
- Generates new audio only for uncached text
Cache Hit Optimization Tips
- Use consistent text:
"Hello World"and"hello world"are the same after normalization - Same voice ID: Cache is per voice, so
alloyandnovawon't share cache - Remove dynamic content: Don't include timestamps or unique IDs in TTS text
// First request - generates audio
const result1 = await client.convert('Hello world', { voice: 'alloy' });
console.log(result1.stats.generatedChunks); // 1
// Second request (identical text) - cached!
const result2 = await client.convert('Hello world', { voice: 'alloy' });
console.log(result2.stats.cachedChunks); // 1
console.log(result2.stats.generatedChunks); // 0Chunk-Level Caching
Long texts are split into chunks (300-400 chars). Each chunk is cached independently:
const result = await client.convert('Very long article text...', { voice: 'alloy' });
// Mixed cache result
console.log(result.stats);
// {
// totalChunks: 5,
// cachedChunks: 3, // 3 chunks found in cache
// generatedChunks: 2 // 2 chunks newly generated
// }Benefits
- Shared sentences across articles get cached
- Only unique content generates new audio
- Typical cache hit rate: 50-75%
TypeScript
The SDK is written in TypeScript with types auto-generated from the OpenAPI specification.
Importing Types
import {
TtsClient,
type TtsClientConfig,
type ConvertOptions,
type ConvertResult,
type AudioChunkDto,
type GenerationStats,
type QuotaInfo,
TtsAuthenticationError,
TtsQuotaExceededError,
TtsValidationError,
TtsNetworkError,
TtsServerError
} from '@your-org/tts-sdk';Type-Safe Configuration
const config: TtsClientConfig = {
apiKey: process.env.TTS_API_KEY!,
baseUrl: 'https://api.example.com',
timeout: 30000
};
const client = new TtsClient(config);Type-Safe Options
const options: ConvertOptions = {
voice: 'alloy',
region: 'NA_WEST',
audioFormat: 'mp3'
};
const result: ConvertResult = await client.convert('Hello', options);Working with Responses
const result = await client.convert('Test');
// TypeScript knows the structure
result.chunks.forEach((chunk: AudioChunkDto) =>{
console.log(`Chunk ${chunk.sequence}: ${chunk.audioUrl}`);
});
const stats: GenerationStats = result.stats;
console.log(`Cache hit rate: ${(stats.cachedChunks / stats.totalChunks * 100).toFixed(1)}%`);Complete Examples
Blog Article Reader
async function readArticle(articleText: string) {
const client = new TtsClient({
apiKey: process.env.TTS_API_KEY!
});
const result = await client.convert(articleText, {
voice: 'nova'
});
// Play audio chunks sequentially
for (const chunk of result.chunks) {
if (chunk.audioUrl) {
const audio = new Audio(chunk.audioUrl);
await audio.play();
await new Promise(resolve =>{
audio.onended = resolve;
});
}}
console.log(`Cache hit rate: ${
(result.stats.cachedChunks / result.stats.totalChunks * 100).toFixed(1)
}%`);
}Multi-Language Support
async function translateAndSpeak(text: string, targetLang: string) {
const client = new TtsClient({
apiKey: process.env.TTS_API_KEY!
});
// Voice mapping
const voiceMap: Record<string, string> = {
'en': 'en-US-Chirp3-HD',
'fr': 'fr-FR-Chirp3-HD',
'es': 'es-ES-Chirp3-HD',
'de': 'de-DE-Chirp3-HD'
};
const result = await client.convert(text, {
voice: voiceMap[targetLang] || 'alloy',
audioFormat: 'mp3'
});
return result.chunks[0].audioUrl;
}Quota Monitoring Dashboard
async function checkQuotaUsage(text: string) {
const client = new TtsClient({
apiKey: process.env.TTS_API_KEY!
});
try {
const result = await client.convert(text);
return {
success: true,
cacheHitRate: (result.stats.cachedChunks / result.stats.totalChunks),
processingTime: result.stats.processingTimeMs
};
} catch (error) {
if (error instanceof TtsQuotaExceededError) {
return {
success: false,
quotaExceeded: true,
usage: error.quotaInfo.charactersUsed,
limit: error.quotaInfo.charactersLimit,
remaining: error.quotaInfo.charactersRemaining
};
}
throw error;
}}Production-Ready Implementation
import { TtsClient, TtsQuotaExceededError, TtsNetworkError } from '@your-org/tts-sdk';
class TtsService {
private client: TtsClient;
private cache = new Map<string, ConvertResult>();
constructor(apiKey: string) {
this.client = new TtsClient({
apiKey,
timeout: 60000
});
}
async convert(text: string, voice = 'alloy', maxRetries = 3) {
// Local cache check
const cacheKey = `${text}:${voice}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)!;
}
// Retry logic
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await this.client.convert(text, { voice });
// Store in local cache
this.cache.set(cacheKey, result);
// Log metrics
this.logMetrics(result);
return result;
} catch (error) {
lastError = error;
// Don't retry quota errors
if (error instanceof TtsQuotaExceededError) {
this.handleQuotaError(error);
throw error;
}
// Retry network errors with backoff
if (error instanceof TtsNetworkError && attempt < maxRetries) {
await this.delay(1000 * attempt);
continue;
}
throw error;
}}
throw lastError;
}
private logMetrics(result: ConvertResult) {
console.log('TTS Metrics:', {
chunks: result.stats.totalChunks,
cacheHitRate: `${(result.stats.cachedChunks / result.stats.totalChunks * 100).toFixed(1)}%`,
processingTime: `${result.stats.processingTimeMs}ms`
});
}
private handleQuotaError(error: TtsQuotaExceededError) {
console.warn('Quota exceeded:', {
used: error.quotaInfo.charactersUsed,
limit: error.quotaInfo.charactersLimit,
remaining: error.quotaInfo.charactersRemaining
});
}
private delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}}
// Usage
const tts = new TtsService(process.env.TTS_API_KEY!);
const result = await tts.convert('Hello world', 'nova');Troubleshooting
"Invalid API key" Error
Cause: API key is incorrect, expired, or missing.
Solution: Check the API key in your dashboard. Ensure no extra spaces. Verify environment variable loads: console.log(process.env.TTS_API_KEY)
CORS Errors in Browser
Cause: Browser blocks requests to API from different origin.
Solution: Proxy requests through your backend (recommended) or configure allowed domains in dashboard for widget keys.
Request Timeout
Cause: Long text or slow network exceeds default 30s timeout.
Solution: Increase timeout: timeout: 60000
"Text exceeds maximum length" Error
Cause: Text is longer than 5000 characters.
Solution: Split text into smaller chunks:
function splitText(text: string, maxLength = 4500): string[] {
const chunks: string[] = [];
let current = '';
for (const sentence of text.split(/[.!?]+/)) {
if ((current + sentence).length > maxLength) {
chunks.push(current.trim());
current = sentence;
} else {
current += sentence + '. ';
}}
if (current.trim()) {
chunks.push(current.trim());
}
return chunks;
}
// Convert each chunk
for (const chunk of splitText(longText)) {
const result = await client.convert(chunk);
// Process result...
}Cache Misses (Low Cache Hit Rate)
Cause: Text is too dynamic or contains unique content.
Solution:
- Remove timestamps from TTS text
- Extract dynamic content (user names, dates) before conversion
- Use consistent text formatting
// Bad: dynamic content
await client.convert(`Welcome back, ${userName}! Today is ${date}`);
// Good: static content
await client.convert('Welcome back! Check your dashboard for updates.');TypeScript Type Errors
Cause: Incorrect type imports or usage.
Solution: Import types correctly:
import {
TtsClient,
type ConvertOptions, // Use 'type' keyword for type-only imports
type ConvertResult
} from '@your-org/tts-sdk';Support
Documentation
docs.example.com
API Status
status.example.com
Email Support
Coming Soon
Community
community.example.com
Reporting Issues
Please include:
- SDK version:
npm list @your-org/tts-sdk - Node.js/browser version
- Minimal code reproduction
- Error messages and stack traces
- Expected vs actual behavior
Changelog
v0.1.0 Initial Release
- โ Core TTS conversion functionality
- โ Automatic text hashing and caching
- โ OpenAI and Google TTS support
- โ Full TypeScript support
- โ Comprehensive error handling
- โ Browser and Node.js compatibility
- โ Quota management and tracking