# MusaTTS API — Developer Integration Guide
MusaTTS runs a local HTTP API inside the desktop app. Other applications on the
same computer can call it to:
- **Generate speech** with any of the 46 built-in voices.
- **Use saved voice blends.** Blends created in the app's Voice Blending panel are addressed by name.
- **Blend voices on the fly** without saving them first.
- **Clone voices with Voice Clone Pro** in 8 language models: from 26 preset voices per language, saved custom voices, or a reference clip you upload.
- **Act as a drop-in OpenAI TTS replacement.** Any app or SDK that supports OpenAI's `/v1/audio/speech` works by changing the base URL.
| | |
|---|---|
| Base URL | `http://127.0.0.1:7860` (port configurable in the app) |
| Auth | `X-API-Key: <key>` or `Authorization: Bearer <key>` |
| Request bodies | JSON (`Content-Type: application/json`) |
| Audio out | WAV (16-bit mono, 24 kHz), raw PCM, or base64 JSON |
| Errors | Always JSON: `{"error": "message"}` |
| Machine-readable spec | [`openapi.yaml`](openapi.yaml), also served live at `GET /api/v1/openapi.yaml` |
---
## Contents
1. [Setup](#1-setup)
2. [Quick start](#2-quick-start)
3. [Concepts](#3-concepts)
4. [TTS endpoints](#4-tts-endpoints)
5. [Voice Clone Pro endpoints](#5-voice-clone-pro-endpoints)
6. [OpenAI-compatible endpoint](#6-openai-compatible-endpoint)
7. [Output formats and streaming](#7-output-formats-and-streaming)
8. [Errors](#8-errors)
9. [Integration examples](#9-integration-examples)
10. [Behaviour and limits](#10-behaviour-and-limits)
---
## 1. Setup
The user of the MusaTTS app does this once:
1. Open **MusaTTS → Settings → API**.
2. Turn the API server **on** (requires an active MusaTTS license).
3. Copy the **API key** (starts with `musa_`) and note the **port** (default `7860`).
Give those two values to your application through its settings or an environment
variable. Never hard-code the key: each installation generates its own key, and
the user can regenerate it at any time.
Check the server is reachable (no key needed):
```bash
curl http://127.0.0.1:7860/api/v1/health
# {"status":"ok","version":"4.0.0"}
```
> The server only listens on `127.0.0.1`, so your app must run on the same
> machine as MusaTTS, and the MusaTTS app must be open.
---
## 2. Quick start
```bash
KEY="musa_your_key_here"
# Built-in voice
curl -X POST http://127.0.0.1:7860/api/v1/synthesize \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"text": "Hello from MusaTTS!", "voice": "Isabella"}' \
--output hello.wav
# A blend saved in the app — just use its name
curl -X POST http://127.0.0.1:7860/api/v1/synthesize \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"text": "Hello from my blend!", "voice": "My Narrator Blend"}' \
--output blend.wav
# Voice Clone Pro with a preset voice
curl -X POST http://127.0.0.1:7860/api/v1/voice-clone \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"text": "Hello from Voice Clone Pro!", "preset": "alba"}' \
--output cloned.wav
```
The TTS engine and Voice Clone Pro both start automatically on the first request,
so there is no "initialize" call to make. The first request after the app opens
takes a few extra seconds.
---
## 3. Concepts
### Two engines
| | **TTS** | **Voice Clone Pro** |
|---|---|---|
| Endpoints | `/api/v1/synthesize`, `/api/v1/blend` | `/api/v1/voice-clone` |
| Voices | 46 built-in voices + saved blends | Preset/custom embeddings or reference clips |
| Languages | English (US/UK), Spanish, French, Hindi, Italian, Japanese, Portuguese | One language model loaded at a time (`english`, `spanish`, `german`, …) |
| Speed | Fast | Slower; quality depends on the reference voice |
| Streaming | Yes | No |
| Setup | None | Download a language model once |
### Voice types
| Where it comes from | Use it with | Field |
|---|---|---|
| Built-in voice (e.g. `Isabella` / `af_bella`) | `/synthesize` | `voice` |
| **Saved blend**, created in the app | `/synthesize` | `voice` (the blend's name) |
| Ad-hoc blend of built-in voices | `/blend` | `voices` |
| Voice Clone Pro **preset** (26 per language, e.g. `alba`) | `/voice-clone` | `preset` |
| Voice Clone Pro **custom voice**, saved via the API or app | `/voice-clone` | `preset` |
| Voice Store clip, uploaded in the app | `/voice-clone` | `voice` |
| Your own audio clip | `/voice-clone` | `reference_audio` (base64) |
Voice names are case-insensitive.
---
## 4. TTS endpoints
### `GET /api/v1/voices`
All voices usable with `/synthesize`: built-ins plus saved blends.
```json
[
{ "name": "Isabella", "id": "af_bella", "language": "English (United States)", "gender": "Female", "type": "builtin" },
{ "name": "Harry", "id": "bm_daniel", "language": "English (United Kingdom)", "gender": "Male", "type": "builtin" },
{ "name": "My Narrator Blend", "language": "English (United States)", "gender": "Blend", "type": "blend" }
]
```
`type` is `builtin`, `blend` or `blend-slerp`. Only built-ins have an `id`.
### `GET /api/v1/blends`
Saved blends with their recipe, for showing users what a blend is made of.
```json
[
{
"name": "My Narrator Blend",
"language": "English (United States)",
"use_slerp": false,
"created": "2026-06-01T10:22:00Z",
"voices": [
{ "voice": "Isabella", "voice_id": "af_bella", "percent": 60 },
{ "voice": "Benjamin", "voice_id": "am_michael", "percent": 40 }
]
}
]
```
### `POST /api/v1/synthesize`
```json
{ "text": "Hello world!", "voice": "Isabella", "speed": 1.0 }
```
| Field | Type | Default | Notes |
|---|---|---|---|
| `text` | string | required | Up to 10,000 characters (100,000 with `stream: true`) |
| `voice` | string | required | Built-in name or id, or a saved blend name |
| `speed` | number | `1.0` | 0.5 – 2.0 |
| `preprocess` | bool | `true` | Applies the app's pronunciation lexicon and number/date normalization, matching in-app output |
| `auto_pause` | bool | `false` | Adds natural pauses at sentence and clause boundaries |
| `response_format` | string | `"wav"` | `wav`, `pcm` or `json`. See [§7](#7-output-formats-and-streaming) |
| `stream` | bool | `false` | Sends audio chunk by chunk as it is rendered |
The response is audio. See [§7](#7-output-formats-and-streaming).
**Inline pause markers** work in `text`: `...500ms`, `...2s`.
### `POST /api/v1/blend`
Mix built-in voices without saving a blend. `voices` accepts either form:
```json
{ "text": "Hello!", "voices": "Isabella:60,Benjamin:40" }
```
```json
{
"text": "Hello!",
"voices": [ { "voice": "Isabella", "weight": 50 }, { "voice": "Harry", "weight": 50 } ],
"use_slerp": true
}
```
| Field | Type | Default | Notes |
|---|---|---|---|
| `voices` | string or array | required | Built-in voice names or ids with weights greater than 0 |
| `use_slerp` | bool | `false` | Spherical interpolation, which gives smoother results for exactly two voices |
| `text`, `speed`, `preprocess`, `auto_pause`, `response_format`, `stream` | | | Same as `/synthesize` |
---
## 5. Voice Clone Pro endpoints
Voice Clone Pro is multi-language. **One language model is loaded at a time**,
and every voice embedding belongs to the language that created it.
### One-time setup
```bash
# Download English + all 26 preset voices (~209 MB, takes a few minutes)
curl -X POST http://127.0.0.1:7860/api/v1/voice-clone/models/download \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"model": "english", "voices": "all"}'
```
This call blocks until the download finishes, so use a long client timeout (an
hour is safe). If the model is already downloaded, it returns immediately.
### `POST /api/v1/voice-clone` — generate
Give **one** voice source. If you give more than one, `preset` wins.
```json
{ "text": "Hello, this is my cloned voice!", "preset": "alba" }
```
```json
{ "text": "Hello!", "voice": "Sarah Narrator", "temperature": 0.7 }
```
```json
{ "text": "Hello!", "reference_audio": "<base64 of a WAV/MP3/FLAC/OGG file>" }
```
| Field | Type | Default | Notes |
|---|---|---|---|
| `text` | string | required | Up to 10,000 characters |
| `preset` | string | — | Preset or custom voice embedding. The fastest option |
| `voice` | string | — | Voice Store clip name (see `GET /voice-clone/voices`) |
| `reference_audio` | string | — | Base64 audio. A clean 5–15 second clip of one speaker works best |
| `language` | string | loaded model | Loaded automatically if needed (switching takes a few seconds) |
| `temperature` | number | `0.7` | 0.1 – 1.0. Lower is steadier, higher is more expressive |
| `auto_pause` | bool | `false` | |
| `preprocess` | bool | `true` | English-only rules. **Set `false` for non-English languages** |
| `response_format` | string | `"wav"` | `wav`, `pcm` or `json` |
Available languages: `english`, `english_2026-01`, `english_2026-04`,
`french_24l`, `german`, `italian`, `portuguese`, `spanish`.
### Recommended: save a clip as a reusable voice
Sending `reference_audio` on every request re-encodes the clip each time. To
avoid that, encode it once:
```bash
curl -X POST http://127.0.0.1:7860/api/v1/voice-clone/voices/save \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d "{\"name\": \"Customer Voice\", \"reference_audio\": \"$(base64 -w0 clip.wav)\"}"
# {"status":"ok","name":"customer_voice","kind":"custom","language":"english"}
```
After that, generate with `"preset": "customer_voice"`, which is fast. The
`voice` field (a Voice Store name) also works as a source here, instead of
`reference_audio`.
### Management endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | `/api/v1/voice-clone/status` | `engine_loaded`, `model_loaded`, `model`, `sample_rate`, `downloaded_models` |
| GET | `/api/v1/voice-clone/models` | All languages with `downloaded` / `loaded` / size |
| POST | `/api/v1/voice-clone/models/download` | `{"model": "spanish", "voices": "default" \| "all"}` |
| POST | `/api/v1/voice-clone/models/load` | `{"model": "spanish"}`: switch the active language |
| POST | `/api/v1/voice-clone/load` | Start the engine. Optional `{"model": ...}` |
| POST | `/api/v1/voice-clone/unload` | Free RAM. `{"shutdown": true}` also stops the engine |
| GET | `/api/v1/voice-clone/presets?language=english` | Embeddings: `[{name, kind: "preset"\|"custom", downloaded}]` |
| POST | `/api/v1/voice-clone/presets/download` | `{"language": "english", "voices": ["estelle"]}` or `{"all": true}` |
| DELETE | `/api/v1/voice-clone/presets/{name}?language=english` | Delete a **custom** voice |
| GET | `/api/v1/voice-clone/voices` | Voice Store clips: `[{id, name, transcription, originalFilename, created, kind}]` |
| POST | `/api/v1/voice-clone/voices/save` | Encode a clip into a custom voice (see above) |
Full schemas are in [`openapi.yaml`](openapi.yaml).
---
## 6. OpenAI-compatible endpoint
`POST /v1/audio/speech` accepts the same request as OpenAI's speech API. Apps
and SDKs that let you set a **base URL** work without code changes:
| Setting | Value |
|---|---|
| Base URL | `http://127.0.0.1:7860/v1` |
| API key | The MusaTTS key |
| Model | `tts-1` (any of `tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`, `musatts`, `kokoro`), or `voice-clone-pro` |
| Voice | Any MusaTTS voice or saved blend name. OpenAI names (`alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer`, `ash`, `ballad`, `coral`, `sage`, `verse`) map to similar built-in voices |
```python
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:7860/v1", api_key="musa_your_key_here")
with client.audio.speech.with_streaming_response.create(
model="tts-1", voice="My Narrator Blend", input="Hello!", response_format="wav",
) as response:
response.stream_to_file("speech.wav")
# Voice Clone Pro through the same endpoint
client.audio.speech.create(
model="voice-clone-pro", voice="alba", input="Cloned voice.", response_format="wav",
).write_to_file("cloned.wav")
```
Notes:
- **Audio format.** MusaTTS outputs WAV or PCM. If a client asks for `mp3`,
`opus`, `aac` or `flac`, it receives **WAV** (`Content-Type: audio/wav`, plus
header `x-musatts-format-fallback: wav`). Most players and SDKs handle that,
but request `response_format="wav"` explicitly when you can.
- **Extra fields.** `language` and `temperature` are accepted for Voice Clone Pro.
`stream: true` works for TTS models.
- `GET /v1/models` lists the accepted model ids.
- The same endpoint is also served at `/api/v1/audio/speech`.
---
## 7. Output formats and streaming
Every generation endpoint accepts `response_format`:
| Value | Content-Type | Body |
|---|---|---|
| `wav` (default) | `audio/wav` | 16-bit mono WAV file |
| `pcm` | `audio/pcm` | Raw 16-bit signed little-endian mono samples, no header |
| `json` | `application/json` | `{ "audio": "<base64 WAV>", "format": "wav", "sample_rate", "duration", "engine", "voice", "model"? }` |
Metadata comes back in response headers, so binary callers keep it:
| Header | Example | |
|---|---|---|
| `x-musatts-engine` | `tts` / `voice-clone-pro` | |
| `x-musatts-voice` | `Isabella` | Omitted if the name isn't ASCII |
| `x-musatts-sample-rate` | `24000` | Needed to play `pcm` |
| `x-musatts-duration` | `3.421` | Seconds. Not sent when streaming |
| `x-musatts-model` | `english` | Voice Clone Pro language |
| `x-musatts-stream` | `true` | Streamed responses only |
Browser apps can read these headers because CORS exposes them.
### Streaming (TTS only)
With `"stream": true`, `/synthesize`, `/blend` and `/v1/audio/speech` send audio
as each chunk (roughly one to three sentences) is rendered. Playback can start
after the first chunk, and texts up to 100,000 characters are accepted.
- `wav` streams a WAV header whose size fields are `0xFFFFFFFF` ("until end of
stream"), followed by PCM data. Browsers, ffmpeg and most players accept this.
If you save the stream to disk and need exact sizes, re-wrap it, or use `pcm`.
- `pcm` streams raw samples. This is the easiest option for real-time playback.
- Errors found before audio starts (bad voice, empty text) still return a normal
JSON error. If rendering fails mid-stream, the connection closes early.
- Closing the connection stops rendering.
---
## 8. Errors
Every error is JSON with a message that says how to fix it:
```json
{ "error": "Language model 'spanish' is not downloaded. POST /api/v1/voice-clone/models/download with {\"model\": \"spanish\"} first." }
```
| Status | Meaning | What to do |
|---|---|---|
| 400 | Bad input: unknown voice or language, empty text, malformed JSON, model not downloaded | Show the message. It names the fix |
| 401 | Missing or wrong API key | Ask the user to re-copy the key from MusaTTS → Settings → API |
| 404 | Unknown endpoint, or custom voice not found | |
| 500 | Generation or engine failure | Retry once, then surface the message |
| 503 | TTS engine couldn't start (model files missing) | The user should open MusaTTS and finish setup |
| *connection refused* | MusaTTS isn't running, or the API is switched off | Ask the user to open MusaTTS and enable the API |
---
## 9. Integration examples
Runnable clients are in [`examples/`](examples/):
- [`examples/musatts_client.py`](examples/musatts_client.py): Python client
(needs `requests`).
- [`examples/musatts_client.mjs`](examples/musatts_client.mjs): JavaScript /
Node 18+ client (no dependencies; also works in browsers).
### Python
```python
import requests
BASE = "http://127.0.0.1:7860/api/v1"
H = {"X-API-Key": "musa_your_key_here"}
r = requests.post(f"{BASE}/synthesize", headers=H,
json={"text": "Hello!", "voice": "My Narrator Blend"}, timeout=300)
r.raise_for_status()
open("out.wav", "wb").write(r.content)
print("duration:", r.headers["x-musatts-duration"])
```
Streaming to a file as audio arrives:
```python
with requests.post(f"{BASE}/synthesize", headers=H, stream=True, timeout=3600,
json={"text": long_text, "voice": "Benjamin",
"stream": True, "response_format": "pcm"}) as r:
r.raise_for_status()
with open("book.pcm", "wb") as f:
for chunk in r.iter_content(chunk_size=None):
f.write(chunk)
```
### JavaScript (browser or Node 18+)
```js
const BASE = "http://127.0.0.1:7860/api/v1";
const KEY = "musa_your_key_here";
const res = await fetch(`${BASE}/voice-clone`, {
method: "POST",
headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
body: JSON.stringify({ text: "Hello from my app!", preset: "alba" }),
});
if (!res.ok) throw new Error((await res.json()).error);
// Browser: play it
const url = URL.createObjectURL(await res.blob());
new Audio(url).play();
```
### C# (.NET 6+)
```csharp
using System.Net.Http.Json;
var http = new HttpClient { BaseAddress = new Uri("http://127.0.0.1:7860/api/v1/"),
Timeout = TimeSpan.FromMinutes(10) };
http.DefaultRequestHeaders.Add("X-API-Key", "musa_your_key_here");
var res = await http.PostAsJsonAsync("synthesize", new { text = "Hello!", voice = "Isabella" });
if (!res.IsSuccessStatusCode)
throw new Exception(await res.Content.ReadAsStringAsync());
await File.WriteAllBytesAsync("out.wav", await res.Content.ReadAsByteArrayAsync());
```
### Clone from an audio file (Python)
```python
import base64, requests
clip = base64.b64encode(open("speaker.wav", "rb").read()).decode()
# One-time: turn the clip into a reusable voice
saved = requests.post(f"{BASE}/voice-clone/voices/save", headers=H, timeout=600,
json={"name": "Speaker A", "reference_audio": clip}).json()
# Every time after: fast generation by name
r = requests.post(f"{BASE}/voice-clone", headers=H, timeout=600,
json={"text": "Now in the cloned voice.", "preset": saved["name"]})
open("cloned.wav", "wb").write(r.content)
```
---
## 10. Behaviour and limits
| | |
|---|---|
| Network | Listens on `127.0.0.1` only, so it is not reachable from other machines |
| App must be open | The API lives inside MusaTTS. If the app closes, the API stops |
| License | The API server needs an active MusaTTS license |
| Text length | 10,000 characters per request (100,000 for streamed TTS). Split longer jobs or stream them |
| Request size | 50 MB max (base64 reference clips) |
| Concurrency | Requests are accepted in parallel, but each engine renders one job at a time. TTS interleaves requests chunk by chunk, so a long job doesn't block a short one. Voice Clone Pro processes requests one after another |
| Shared state | The API and the app share engines, the loaded Voice Clone Pro language, the lexicon and saved blends. Changes in one show up in the other |
| Language switching | Requesting a different Voice Clone Pro `language` unloads the current one. Group requests by language if you mix them |
| Timeouts | Allow at least 5 minutes for generation and 1 hour for model downloads |
| Sample rate | 24 kHz for both engines. Read `x-musatts-sample-rate` rather than assuming it |