@inworld/tts
v1.1.1
Published
Inworld TTS SDK – generate, stream, and voice management
Maintainers
Readme
@inworld/tts
Node.js and browser SDK for the Inworld TTS API — generate, stream, and manage voices.
API Reference · Changelog · Platform
Install
npm install @inworld/ttsSupports ESM, CommonJS, and browser (Vite, webpack 5, Rollup, esbuild).
Authentication
Pass your API key directly or set INWORLD_API_KEY in your environment:
import { InworldTTS } from '@inworld/tts'; // TypeScript: types are bundled, no @types/ needed
const tts = InworldTTS(); // reads INWORLD_API_KEY from env
const tts = InworldTTS({ apiKey: 'your_key' }); // or pass directlyGet your key at platform.inworld.ai. For browser usage with JWT, see Browser.
Quickstart
import { InworldTTS } from '@inworld/tts';
const tts = InworldTTS();
await tts.generate({ text: 'Hello, world!', voice: 'Dennis', outputFile: 'hello.mp3' });Models
| Model ID | Quality | Default for |
|----------|---------|-------------|
| inworld-tts-1.5-max | Higher quality | generate() |
| inworld-tts-1.5-mini | Budget option | stream() |
Use max when quality is the priority (e.g. audiobooks, voiceovers). Use mini when cost efficiency matters.
Constructor
const tts = InworldTTS({
apiKey: 'your_key', // or set INWORLD_API_KEY env var
timeout: 120_000, // HTTP timeout in ms (default: per-method)
maxConcurrentRequests: 4, // parallel chunk requests for long text (default: 2)
maxRetries: 2, // retry on network errors / 5xx with exponential backoff (default: 2)
debug: true, // log timing and retry info
});See Constructor in the API Reference for full parameter details and per-method timeout defaults.
generate()
Synthesize speech from text of any length. Blocks until all audio is ready.
// Save to file
await tts.generate({ text: 'Hello!', voice: 'Dennis', outputFile: 'hello.mp3' });
// Get bytes for further processing
const audio = await tts.generate({ text: 'Hello!', voice: 'Dennis' });
// Generate, save, and play
await tts.generate({ text: 'Hello!', voice: 'Dennis', outputFile: 'hello.mp3', play: true });stream()
Streaming — first audio chunk arrives faster than generate(). Max 2000 characters per call.
for await (const chunk of tts.stream({ text: 'Hello, world!', voice: 'Dennis' })) {
// chunk is Uint8Array — pipe to audio player or accumulate
}Timestamps
generateWithTimestamps() and streamWithTimestamps() return word- or character-level timing alongside audio.
const { audio, timestamps } = await tts.generateWithTimestamps({
text: 'Hello, world!',
voice: 'Dennis',
timestampType: 'WORD',
});
const wa = timestamps.wordAlignment;
wa.words.forEach((w, i) =>
console.log(`${w}: ${wa.wordStartTimeSeconds[i].toFixed(2)}s – ${wa.wordEndTimeSeconds[i].toFixed(2)}s`)
);See generateWithTimestamps() and streamWithTimestamps() for full details.
play()
Play audio from a Uint8Array or file path. Encoding is auto-detected from magic bytes or file extension.
const audio = await tts.generate({ text: 'Hello!', voice: 'Dennis' });
await tts.play(audio);
await tts.play('hello.mp3'); // file path also accepted (Node.js only)
await tts.play(pcmBytes, { encoding: 'PCM' }); // encoding hint for raw PCM/ALAW/MULAWSee play() for platform player details and browser constraints.
listVoices()
List voices in your workspace, with optional language filter.
const voices = await tts.listVoices();
const enVoices = await tts.listVoices({ lang: 'EN_US' });
const multi = await tts.listVoices({ lang: ['EN_US', 'ES_ES'] });getVoice()
Get details of a specific voice.
const voice = await tts.getVoice('workspace__my_clone');updateVoice()
Update a voice's display name, description, or tags.
await tts.updateVoice({ voice: 'workspace__my_clone', displayName: 'Narrator', tags: ['calm'] });deleteVoice()
Delete a voice from your workspace.
await tts.deleteVoice('workspace__my_clone');cloneVoice()
Clone a voice from one or more audio recordings (WAV/MP3). Accepts Uint8Array buffers or file path strings (Node.js only).
const result = await tts.cloneVoice({
audioSamples: ['sample.wav'],
displayName: 'My Clone',
});
const voiceId = result.voice.voiceId;Note: Voice cloning is a long-running operation (up to 5 min). If it times out, check
listVoices()— the voice may have been created anyway.
designVoice()
Design a voice from a text description — no recording needed.
const result = await tts.designVoice({
designPrompt: 'A warm, friendly narrator',
previewText: 'Hello, welcome to our audiobook.',
});
const preview = result.previewVoices[0];publishVoice()
Publish a designed or cloned voice preview to your library.
await tts.publishVoice({ voice: preview.voiceId, displayName: 'My Custom Voice' });migrateFromElevenLabs()
Migrate a voice from ElevenLabs to your Inworld workspace. No ElevenLabs SDK required.
Note: Only voices you own in ElevenLabs can be migrated.
const result = await tts.migrateFromElevenLabs({
elevenLabsApiKey: 'el_...',
elevenLabsVoiceId: 'abc123',
});
console.log(`${result.elevenLabsName} → ${result.inworldVoiceId}`);See Voice Management in the API Reference for all parameters.
Errors
| Class | When |
|-------|------|
| MissingApiKeyError | No API key or token found at construction |
| ApiError | API returned 4xx/5xx — has .code and .details |
| NetworkError | Connection or timeout failure |
All inherit from InworldTTSError.
import { InworldTTS, ApiError, MissingApiKeyError, NetworkError } from '@inworld/tts';
try {
const audio = await tts.generate({ text: 'Hello!', voice: 'Dennis' });
} catch (err) {
if (err instanceof MissingApiKeyError) console.error('Missing API key');
else if (err instanceof ApiError) console.error(`HTTP ${err.code}: ${err.message}`);
else if (err instanceof NetworkError) console.error(`Network error: ${err.message}`);
else throw err;
}Browser
For browser usage, use a short-lived JWT token from your backend instead of exposing your API key.
const fetchToken = async () => {
const { token } = await fetch('/api/tts-token').then(r => r.json());
return token;
};
const tts = InworldTTS({
token: await fetchToken(),
onTokenExpiring: fetchToken, // called automatically when token is about to expire
});
// play() must be called inside a user event handler (browser autoplay policy)
button.onclick = async () => {
const audio = await tts.generate({ text: 'Hello!', voice: 'Dennis', encoding: 'MP3' });
await tts.play(audio);
};For development only, you can use an API key directly with dangerouslyAllowBrowser: true — but your key will be visible in DevTools and billed to your account.
A complete working example is in examples/browser/.
Encoding in browser: Prefer
encoding: 'MP3'— supported natively in all browsers.OGG_OPUS/FLACwork in Chrome and Firefox but not Safari.LINEAR16,PCM,ALAW,MULAWcannot be played byplay()in browser.
Examples
Runnable examples are in the examples/ directory:
| File | What it shows |
|------|---------------|
| hello_world.js | Text → MP3 in 3 lines |
| stream_audio.js | Real-time streaming |
| list_voices.js | List all voices with optional language filter |
| clone_voice.js | Clone a voice from a WAV/MP3 recording |
| design_voice.js | Design a voice from a text description, preview, and publish |
| generate_timestamps.js | Word-level timestamps |
| examples/browser/ | Browser usage with JWT auth |
Troubleshooting
MissingApiKeyError / ApiError 401
Set INWORLD_API_KEY or pass apiKey directly. If the key is set but rejected, regenerate it at platform.inworld.ai.
play() blocked by browser (autoplay policy)
Move play() inside a user event handler:
button.onclick = async () => await tts.play(audio);ApiError: text exceeds 2000 character limit
stream() accepts at most 2000 characters per call. Use generate() instead — it handles any text length automatically.
NetworkError: Request timed out
Increase the timeout or add retries:
const tts = InworldTTS({ timeout: 120_000, maxRetries: 3 });require('@inworld/tts') throws ERR_REQUIRE_ESM
Both ESM and CommonJS are supported. Ensure you are on Node.js ≥18 and using a bundler that respects the exports field (webpack 5, Vite, Rollup, esbuild).
// ESM
import { InworldTTS } from '@inworld/tts';
// CommonJS
const { InworldTTS } = require('@inworld/tts');