Skip to main content
Glama

RestfulTTS

Text-to-speech for PhonixPBX announcements, served over a REST API, an MCP server and a small web UI.

Language

Voices

Model

German (de)

thorsten (neutral)

Thorsten-Voice/Kokoro, a Kokoro-82M fine-tune on the CC0 Thorsten-Voice dataset

English (en)

af_heart (default), af_bella, am_michael, bf_emma, bm_george

hexgrad/Kokoro-82M

French (fr)

ff_siwis

hexgrad/Kokoro-82M

Italian (it)

if_sara (default), im_nicola

hexgrad/Kokoro-82M; both voices are only graded "C" by Kokoro

Inspired by nazdridoy/kokoro-tts. Unlike that project, it uses the PyTorch kokoro package instead of kokoro-onnx, because the German model only exists as a PyTorch checkpoint. It also needs the semidark forks of kokoro and misaki; only those forks contain the German G2P.

Run

With Podman (CPU only), from the released image (linux/amd64 and linux/arm64):

podman run -d -p 8000:8000 -v restfultts_data:/data ghcr.io/phonixpbx/restfultts:latest

Or built from source:

podman-compose up -d            # or: podman build -t restfultts . && podman run -p 8000:8000 -v restfultts_data:/data restfultts

The first start downloads about 650 MB of models and voices into the /data volume, which can take several minutes. Later starts load them from the volume.

Locally with uv and Python 3.12:

uv sync --extra dev
uv run restfultts download          # optional: fetch all models up front
uv run restfultts serve             # http://127.0.0.1:8000
uv run restfultts say "Guten Tag." -o hallo.wav
uv run restfultts say "Buongiorno." -l it -f ulaw -o hallo.ulaw

The UI is at /, the OpenAPI docs are at /docs, and the MCP endpoint is /mcp.

Related MCP server: TTS MCP Server

Configuration

Environment variables (or an .env file):

Variable

Default

Description

RESTFULTTS_API_TOKEN

empty

When set, REST and MCP need Authorization: Bearer <token> and the UI asks for the token. Empty means open access, so only use that on a trusted network.

RESTFULTTS_DATA_DIR

data (/data in the container)

Holds the audio cache. In the container it also holds the models (HF_HOME=/data/hf).

RESTFULTTS_PRELOAD

empty (de,en,fr,it in the container)

Languages whose models are loaded at startup; others load on the first request.

RESTFULTTS_DEVICE

auto

cpu, cuda or auto. The container ships CPU-only PyTorch.

RESTFULTTS_THORSTEN_EPOCH

5

Checkpoint of the German model (1–10).

RESTFULTTS_MAX_TEXT_CHARS

5000

Longer texts are rejected with 413.

RESTFULTTS_CACHE_MAX_MB

1024

Size limit of the audio cache; the least recently used files are removed first.

RESTFULTTS_HOST / RESTFULTTS_PORT

127.0.0.1 / 8000

Listen address (0.0.0.0 in the container).

RESTFULTTS_ALLOWED_HOSTS

empty

Comma separated host names the server answers to, e.g. tts.example.org,*.pbx.lan; * allows any. Other Host headers get 400, which protects against DNS rebinding. When empty, only localhost and 127.0.0.1 are allowed while listening on loopback, and any host otherwise.

REST API

Method and path

Description

POST /api/v1/synthesize

Body {"text", "voice"?, "language"?, "speed"? (0.5–2.0), "format"?}. Returns the audio bytes. Give either voice or language; with only a language you get its default voice.

GET /api/v1/synthesize?text=…&language=…&format=…

Same as POST, for clients that can only fetch a URL.

GET /api/v1/audio/<hash>.<ext>

A file from the cache.

DELETE /api/v1/audio/<hash>

Deletes a file from the cache, with all formats derived from the same synthesis. Returns 204, or 404 if it is not in the cache.

GET /api/v1/voices?language=de, /api/v1/languages, /api/v1/formats, /api/v1/health

Metadata.

POST /tts

Compatible with the thorstenvoice/kokoro-tts container. {"text": "..."} returns German WAV.

Synthesis responses carry the headers X-Cache: hit|miss and X-Voice.

Formats:

Format

Rate

Content

wav

24 kHz

Original quality

wav16

16 kHz

WAV

wav8

8 kHz

WAV

sln

8 kHz

Asterisk raw signed linear, 16 bit

sln16

16 kHz

Asterisk raw signed linear, 16 bit

ulaw

8 kHz

Raw G.711 µ-law

alaw

8 kHz

Raw G.711 A-law

The G.711 encoders give the same output as Python's audioop for every 16-bit sample.

curl -o ansage.wav -H 'Content-Type: application/json' \
     -d '{"text": "Guten Tag und willkommen."}' http://localhost:8000/tts
curl -o ansage.ulaw 'http://localhost:8000/api/v1/synthesize?text=Benvenuti&language=it&format=ulaw'

Each text is synthesized once and cached as 24 kHz WAV. Other formats of the same text are derived from that file.

MCP

The MCP endpoint uses Streamable HTTP (stateless, JSON responses) at http://<host>:8000/mcp. If a token is configured, clients send it as a bearer token. It offers three tools:

  • list_voices(language?)

  • list_formats()

  • delete_audio(url) deletes a file and all its formats.

  • synthesize(text, voice?, language?, speed?, format?) returns the audio as MCP audio content, plus a JSON text with the voice, the duration and the download path.

claude mcp add --transport http restfultts http://localhost:8000/mcp --header "Authorization: Bearer $TOKEN"

Development

uv run pytest -q                    # unit, API, MCP and UI tests with a fake engine
uv run pytest -m models             # real synthesis in all four languages (downloads the models)
uv run ruff check . && uv run ruff format --check . && uv run mypy src
scripts/test-container.sh <image>   # smoke test of a built image, as in CI
scripts/vendor-update.sh            # refresh Tabler 1.7.1 / htmx 4.0.0 (same versions as PhonixPBX)

Releases are tagged vX.Y.Z. CI then builds and pushes the container image and creates the GitHub release from CHANGELOG.md; see docs/RELEASING.md.

Licenses

RestfulTTS is licensed under Apache-2.0. Both models are Apache-2.0, and the Thorsten-Voice dataset is CC0.

For phonemization, misaki uses espeak-ng through phonemizer-fork and espeakng-loader. Both are GPL-3 and are installed as unmodified Python dependencies. Unlike in PhonixPBX, espeak-ng does not produce the speech here; it only converts text to phonemes for Kokoro. See NOTICE.

Related MCP Connectors

Related MCP Servers