DEV Community

Cover image for Why qvox Keeps TTS as a Thin API and a Small CLI
Manuel Bruña
Manuel Bruña

Posted on

Why qvox Keeps TTS as a Thin API and a Small CLI

Why qvox Keeps TTS as a Thin API and a Small CLI

A text-to-speech model is only half a product. The other half is the boundary around it. If the boundary is noisy, every integration gets heavier than it should be.

That is the core idea behind qwen3-tts-api, the repo behind qvox: keep the interface small, keep the backend swappable, and keep state in files instead of hiding it behind a database.

QVox logo

The repo is intentionally split into a few clear jobs. Node handles the CLI, the HTTP API, the web panel, and the daemon lifecycle. Python, managed through uv, handles inference behind a TTSBackend interface. That split matters because it keeps the app shell stable even when the model engine changes.

The README shows two backends: mlx for Apple Silicon and torch for CUDA, ROCm, and CPU. That is the right shape for a local TTS service. Hardware should pick the backend, not force you to rewrite the app. The service code should only care that it can ask for audio and get audio back.

That is also why the command surface stays small. The repo does not try to become a giant platform. It gives you a few commands that map to the real jobs:

qvox setup
qvox serve
qvox status
qvox models list
qvox speak "Hi there, how are you?" --voice aiden --out demo.wav
qvox speak "Hello" --clone /path/to/voice.wav --out clone.wav
Enter fullscreen mode Exit fullscreen mode

That is a good sign. A small CLI means the mental model stays honest. setup prepares the machine. serve starts the daemon. speak generates audio. status tells you what is alive. Nothing in that list asks the user to care about internal model plumbing.

The API is the real contract. qvox exposes an OpenAI-compatible endpoint at /v1/audio/speech, which is the part that makes the repo useful for real integrations. If you already know how to talk to an HTTP text-to-speech service, you do not need a custom client story to get started.

curl -X POST http://127.0.0.1:5111/v1/audio/speech \
  -H "content-type: application/json" \
  -H "x-api-key: YOUR_KEY" \
  -d '{"input":"Hello world","language":"English","instruct":"A warm voice"}' \
  -o out.wav
Enter fullscreen mode Exit fullscreen mode

That kind of API shape is what keeps adoption low-friction. The server can run on localhost for a personal workflow, or on 0.0.0.0 for a VPS. If you expose it beyond your own machine, the repo supports an API key. Same service, same contract, different deployment surface.

I like the no-database choice too. Configuration lives in ~/.qvox as JSON, with env vars taking priority over config and config taking priority over defaults. That is easy to inspect, easy to version mentally, and easy to fix when something breaks. For a local daemon, that beats a hidden state store.

This is the pattern I would recommend to anyone building around a model: do not let the model architecture leak into the app architecture. Put the model behind a thin API. Put the CLI in front of the API. Keep the config visible. Keep the backend replaceable.

That is why qvox feels practical instead of theatrical. It is not trying to be a giant voice platform. It is a small local service that makes Qwen3-TTS easy to call from a terminal, a script, or another app.

If you want the source, start here:

https://github.com/tecnomanu/qwen3-tts-api

Top comments (0)