JavaScript SDK

Official JavaScript/TypeScript SDK for the TTS SaaS Platform

TypeScript Ready Auto Caching Multi-Provider

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:

bash
npm install @your-org/tts-sdk
bash
yarn add @your-org/tts-sdk

Quick Start

Get started with just a few lines of code:

typescript
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

  1. Log in to your TTS SaaS dashboard
  2. Navigate to Settings โ†’ API Keys
  3. Click Create New API Key
  4. Copy the key (it won't be shown again)

Using Your API Key

typescript
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 .env file

Basic Usage

Converting Text to Speech

The convert() method is the main entry point:

typescript
const result = await client.convert('Welcome to our website!', {
voice: 'alloy'
});

Playing Audio in the Browser

typescript
// 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

typescript
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

typescript
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

json
{
"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):

typescript
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:

typescript
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

typescript
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
typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
const client = new TtsClient({
apiKey: 'your-key',
  timeout: 60000 // 60 seconds (default: 30s)
});

Multi-Language Support

OpenAI voices auto-detect language:

typescript
// 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:

typescript
// 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.

typescript
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.

typescript
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

typescript
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:

typescript
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:

typescript
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:

  1. Normalizes text (trim, lowercase, collapse spaces)
  2. Generates SHA-256 hash
  3. Backend checks if hash exists in cache
  4. Returns cached audio instantly (no TTS API call)
  5. 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 alloy and nova won't share cache
  • Remove dynamic content: Don't include timestamps or unique IDs in TTS text
typescript
// 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); // 0

Chunk-Level Caching

Long texts are split into chunks (300-400 chars). Each chunk is cached independently:

typescript
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

typescript
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

typescript
const config: TtsClientConfig = {
apiKey: process.env.TTS_API_KEY!,
  baseUrl: 'https://api.example.com',
  timeout: 30000
};

const client = new TtsClient(config);

Type-Safe Options

typescript
const options: ConvertOptions = {
voice: 'alloy',
  region: 'NA_WEST',
  audioFormat: 'mp3'
};

const result: ConvertResult = await client.convert('Hello', options);

Working with Responses

typescript
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

typescript
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

typescript
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

typescript
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

typescript
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:

typescript
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
typescript
// 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:

typescript
import {
TtsClient,
  type ConvertOptions,  // Use 'type' keyword for type-only imports
  type ConvertResult
} from '@your-org/tts-sdk';

Support

Reporting Issues

Please include:

  1. SDK version: npm list @your-org/tts-sdk
  2. Node.js/browser version
  3. Minimal code reproduction
  4. Error messages and stack traces
  5. 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