bland-tts
v0.3.0
Published
Focused TTS + voice toolkit for Bland AI — CLI, Node library, and MCP server in one package.
Maintainers
Readme
bland-tts
Focused TTS + voice toolkit for Bland AI. Three modes from one package:
- CLI —
npx bland-tts speak "hello world" --voice maya -o hello.mp3 - Library —
import { BlandTtsClient } from "bland-tts"for Node scripting - MCP server —
npx bland-tts mcp— drop-in for Claude Code, Cursor, Claude Desktop
If you want the full Bland API surface (calls, pathways, scenarios, knowledge bases…), use bland-cli. This package is the speech subset.
Install
npm i -g bland-tts # global CLI
# or
npm i bland-tts # library / per-projectAuth
Set your Bland API key in the environment:
export BLAND_API_KEY=org_your_key_here(Or pass apiKey to the BlandTtsClient constructor.)
Default voices
When you call speak() without specifying a voice, one is picked at random from:
- Karen
- Valentine Experimental
- Willow
To lock a single default for a process, pass defaultVoice to the client constructor; to opt out and let the server choose, pass defaultVoice: null. The CLI surfaces which voice was actually used (voice_used in --json, dimmed line otherwise).
CLI
# Speak some text — voice picked from the rotation
bland-tts speak "Hey, thanks for calling Maple Vet"
# → "voice: Karen" (or Valentine Experimental, or Willow)
# Pin a specific voice
bland-tts speak "Hello world" --voice maya -o hello.mp3
bland-tts speak "Hola" --language es --json
# Voices
bland-tts voices # list
bland-tts voices --custom # only voices you've cloned
bland-tts voice maya # full details for one voice
# Voice cloning (BTTS v2)
bland-tts clone "MyVoice" sample1.wav sample2.wav
bland-tts rename <voice_id> "NewName"
bland-tts delete <voice_id>
# Browse stored TTS generations (auto-stored from `speak`)
bland-tts samples list
bland-tts samples list --voice <voice_id> --limit 10
bland-tts samples get <sample_id> -o sample.mp3Every command supports --json for scripting.
Library
import { BlandTtsClient } from "bland-tts";
const tts = new BlandTtsClient(); // reads BLAND_API_KEY from env
// One-shot synthesis to a file
const { audio } = await tts.speakToBuffer({
text: "Hello world",
voice: "maya",
});
await fs.writeFile("out.mp3", audio);
// Just get the URL
const res = await tts.speak({ text: "Hi", voice: "nat", language: "en" });
console.log(res.url);
// Streaming (lowest time-to-first-byte)
const { stream } = await tts.speakStream({ text: "streaming hello" });
for await (const chunk of stream) { /* write chunk to a sink */ }
// Voices + settings
const voices = await tts.listVoices();
const maya = await tts.getVoice("maya");
const settings = await tts.getVoiceSettings("maya"); // defaults + valid ranges
await tts.updateVoiceSettings(voiceId, { consistency: 0.8, expressiveness: 0.3 });
await tts.rateVoice(voiceId, 5);
// Models
const models = await tts.listModels(); // BTTS_V2 / BTTS_V3 / …
// Shared library
const shared = await tts.listSharedVoices();
await tts.addLibraryVoice(shared[0].id);
// Clone (BTTS_V3 default: one ~10s sample; BTTS_V2: exactly 1 WAV)
await tts.checkNameAvailability("MyVoice");
await tts.cloneVoice({
name: "MyVoice",
engine: "BTTS_V3",
samples: [{ filename: "a.wav", content: await fs.readFile("a.wav") }],
});
await tts.renameVoice(voiceId, "NewName");
await tts.deleteVoice(voiceId);
// Stored samples (every speak() auto-stores)
const recent = await tts.listSamples({ limit: 10 });
const sample = await tts.getSample(recent[0].id);Note:
POST /v1/speakreturns raw audio bytes (WAV by default), not a JSON URL.speak()returns{ audio: Uint8Array, contentType, voice_used }— writeaudiostraight to a file.speakToBuffer()is a deprecated alias.
Library API
| Method | Endpoint | Notes |
|---|---|---|
| speak({text, voice?, language?, output_format?, consistency?, expressiveness?}) | POST /v1/speak | Returns raw audio {audio, contentType, voice_used}. Auto-stores. |
| speakStream({...}) | POST /v1/speak (stream) | Chunked; returns async-iterable of audio chunks. |
| mintStreamToken({voice?}) | POST /v1/speak/stream-input-token | Short-lived JWT for the browser streaming WS. |
| listModels() | GET /v1/models | TTS models + capabilities. |
| listVoices() | GET /v1/voices | All voices on your account. |
| listSharedVoices() | GET /v1/voices/shared | Public voice library. |
| addLibraryVoice(id) | POST /v1/voices/library/add/:id | Add a shared voice to your org. |
| getVoice(idOrName) | GET /v1/voices/:id | Accepts UUID or name. |
| getVoiceSettings(idOrName) | GET /v1/voices/:id/settings | Synthesis defaults + ranges. |
| updateVoiceSettings(id, {...}) | POST /v1/voices/:id/settings | consistency / expressiveness / boost. |
| rateVoice(id, 1..5) | POST /v1/voices/:id/rate | Star rating. |
| checkNameAvailability(name) | POST /v1/voices/check-name-availability | Before cloning. |
| cloneVoice({name, samples, engine?}) | POST /v1/voices/clone | Multipart. name ≤ 30 chars. BTTS_V3 default. |
| renameVoice(id, name) | PATCH /v1/voices/:id/rename | |
| deleteVoice(id) | DELETE /v1/voices/:id | Permanent. |
| listSamples({voiceId?, limit?, offset?}) | GET /v1/speak/samples | Past speak() outputs. |
| getSample(id) | GET /v1/speak/samples/:id | One sample by ID. |
All methods throw BlandApiError (with .status and .body) on API failures.
MCP server
Hook into Claude Code, Cursor, Claude Desktop, Windsurf, etc.
~/.config/claude-code/mcp.json (or your client's equivalent):
{
"mcpServers": {
"bland-tts": {
"command": "npx",
"args": ["-y", "bland-tts", "mcp"],
"env": { "BLAND_API_KEY": "org_your_key_here" }
}
}
}Tools exposed (14):
| Tool | Does what |
|---|---|
| tts_speak | Generate speech (auto-stored; returns metadata — save files via the CLI) |
| tts_models_list | List TTS models + capabilities |
| tts_voices_list | List voices on your account |
| tts_voice_get | Details for one voice (by ID or name) |
| tts_voice_settings_get / _update | Get / update synthesis defaults |
| tts_voice_rate | Rate a voice 1–5 |
| tts_voice_name_check | Check a name's availability before cloning |
| tts_shared_voices_list / tts_shared_voice_add | Browse & add public library voices |
| tts_voice_rename / tts_voice_delete | Manage cloned voices |
| tts_samples_list / tts_sample_get | Browse / fetch stored generations |
Voice cloning is intentionally CLI-only since multipart file uploads aren't well-suited to stdio JSON-RPC. Use bland-tts clone <name> <files...> for that. tts_speak returns metadata (the audio is raw bytes over HTTP, not JSON) — save files with bland-tts speak -o <file>.
Why a separate package?
bland-cli is the full kitchen sink — calls, pathways, scenarios, knowledge bases, personas, batch campaigns. Big install, broad surface.
bland-tts is for projects that just need speech: a podcast generator, a voice notification system, an experimentation harness for prompt phrasing. Tiny dependency footprint (chalk, commander, ora). Library-first design means you can drop the MCP server and import BlandTtsClient directly.
License
MIT
