Access the Typecast API with our official Javascript/Typescript SDK.
The official Node.js library for the Typecast API. Convert text to lifelike speech using AI-powered voices.Works with both Javascript and TypeScript. Full TypeScript types included.ESM & CommonJS supported. Works in Node.js 18+ and modern browsers. Node.js 16/17 users need to install isomorphic-fetch polyfill.
Latest registered version: 0.4.7 on npm. Make sure you have version 0.4.7 or higher installed. You can check your version with npm list @neosapience/typecast-js. If you have an older version, run npm update @neosapience/typecast-js to update.
Use recommendVoices when you know the desired style but not the exact voice_id.
const recommendations = await client.recommendVoices( 'warm female voice for a product tutorial', 3);for (const voice of recommendations) { console.log(voice.voice_id, voice.voice_name, voice.score);}
Recommendation results contain only voice_id, voice_name, and score. Use getVoiceV2(voiceId) or getVoicesV2() when you need detailed metadata such as supported models, emotions, gender, age, or use cases.
Set your API key via environment variable or constructor:
// Using environment variable// export TYPECAST_API_KEY="your-api-key-here"const client = new TypecastClient({ apiKey: process.env.TYPECAST_API_KEY!});// Or pass directlyconst client = new TypecastClient({ apiKey: 'your-api-key-here'});
When requests go through your own proxy, set baseHost to the proxy endpoint and omit apiKey. The SDK will not send the X-API-KEY header for empty or missing keys.
Proxy without API key
const client = new TypecastClient({ baseHost: 'https://your-proxy.example.com'});
ssfm-v30 offers two emotion control modes: Preset and Smart.
Smart Mode
Preset Mode
Let the AI infer emotion from context:
import { SmartPrompt } from '@neosapience/typecast-js';const audio = await client.textToSpeech({ text: "Everything is going to be okay.", voice_id: "tc_672c5f5ce59fac2a48faeaee", model: "ssfm-v30", prompt: { emotion_type: "smart", previous_text: "I just got the best news!", // Optional context next_text: "I can't wait to celebrate!" // Optional context } as SmartPrompt});
Explicitly set emotion with preset values:
import { PresetPrompt } from '@neosapience/typecast-js';const audio = await client.textToSpeech({ text: "I am so excited to show you these features!", voice_id: "tc_672c5f5ce59fac2a48faeaee", model: "ssfm-v30", prompt: { emotion_type: "preset", emotion_preset: "happy", // normal, happy, sad, angry, whisper, toneup, tonedown emotion_intensity: 1.5 // Range: 0.0 to 2.0 } as PresetPrompt});
Use generateToFile when you want the SDK to synthesize speech and write the audio bytes directly to a local file. The model defaults to ssfm-v30, and .mp3 / .wav extensions infer the output format when output.audio_format is not set. Browse available voice IDs on the Voices page.
await client.generateToFile('output.mp3', { text: 'Hello from Typecast.', voice_id: 'tc_672c5f5ce59fac2a48faeaee' // Find voice IDs at https://typecast.ai/developers/api/voices});
Use text pause markup when you only need silent gaps inside one composed text segment. Put <|5s|>, <|1s|>, <|0.3s|>, or <|0.34413s|> directly in the text. The value is interpreted as seconds and must end with s. This keeps the pause expression visible in plain text without adding separate pause calls.
const audio = await client .composeSpeech() .defaults({ voice_id: 'tc_672c5f5ce59fac2a48faeaee', model: 'ssfm-v30' }) .say('Hello<|5s|>Nice to meet you<|1s|>Today<|2s|>how does the weather feel?') .generate();
Use the composer chaining API when one output file needs different voices or per-segment options such as pitch, tempo, prompt, or seed. The composer generates each segment as WAV, trims leading/trailing silent PCM samples, and concatenates the result. If you need MP3, generate WAV first and convert it in your app or server pipeline.
import { TypecastClient } from '@neosapience/typecast-js';import fs from 'fs/promises';const client = new TypecastClient({ apiKey: 'YOUR_API_KEY' });const audio = await client .composeSpeech() .defaults({ voice_id: 'tc_672c5f5ce59fac2a48faeaee', model: 'ssfm-v30' }) .say('Hello there') .pause(5) .say('Nice to meet you', { voice_id: 'tc_60e5426de8b95f1d3000d7b5', output: { audio_pitch: 2 } }) .say('Today') .pause(2) .say('How does the weather feel?') .generate();await fs.writeFile('conversation.wav', Buffer.from(audio.audioData));
Stream audio chunks in real-time for low-latency playback:
// Node 18+ (built-in fetch). Pipe stream to ffplay for real-time playback.// Prerequisite: ffmpeg (brew/choco/apt install ffmpeg)import { spawn } from "node:child_process";import { TypecastClient } from '@neosapience/typecast-js';const client = new TypecastClient({ apiKey: 'YOUR_API_KEY' });const ffplay = spawn( "ffplay", ["-autoexit", "-nodisp", "-loglevel", "error", "-i", "pipe:0"], { stdio: ["pipe", "ignore", "ignore"] },);const stream = await client.textToSpeechStream({ text: "Stream this text as audio in real time.", model: "ssfm-v30", voice_id: "tc_672c5f5ce59fac2a48faeaee", output: { audio_format: "wav" }});// ReadableStream — read chunks as they arriveconst reader = stream.getReader();while (true) { const { value, done } = await reader.read(); if (done) break; ffplay.stdin.write(value);}ffplay.stdin.end();await new Promise((resolve) => ffplay.on("close", resolve));
WAV streaming format: 32000 Hz, 16-bit, mono PCM. The first chunk includes a 44-byte WAV header (size = 0xFFFFFFFF); subsequent chunks are raw PCM only. For MP3: 320 kbps, 44100 Hz, each chunk is independently decodable.
textToSpeechWithTimestamps() wraps POST /v1/text-to-speech/with-timestamps and returns the audio together with per-word and per-character alignment data — useful for karaoke highlights, subtitle generation, and lip-sync applications.
The response object includes helpers that convert alignment data to SRT or WebVTT captions. Captions are split on sentence terminators (. ? ! 。 ? !) and capped at 7 seconds / 42 characters per cue (BBC/Netflix subtitle guidelines).
Japanese / Chinese: Word-level segmentation is not meaningful for languages without whitespace delimiters (jpn, zho). Use granularity: "char" for these languages to get character-level alignment.