← all projects

Voice Synthesis REST API

A standalone Flask microservice that exposes text-to-speech as a single, reusable REST endpoint.

A single-endpoint Flask service that exists because a capability trapped inside a bigger pipeline is only useful to that pipeline. Extracting text-to-speech into a standalone REST API made it callable from anything, not just the one script it originally lived in.

PythonFlaskCoqui XTTS-v2
~/voice-synthesis-api/architecturesynchronous · single endpoint · warm model process
100%
Any caller can hit this endpoint — the whole point of extracting it from the media pipeline was to make it reusable, not tied to one caller.ClientcallerA single POST /synthesize endpoint accepts text and returns audio. Model loading is isolated from caller-specific logic, keeping the service generic.FlaskPOST /synthesizetext → audioThe model is loaded once and kept warm rather than reloaded per request — the deliberate trade-off that keeps request latency reasonable in a synchronous Flask service.XTTS-v2warm modelSynthesized speech is returned directly in the response — no queue, no polling, since the model is already warm.Audioresponse

Requirements

  • Expose XTTS-v2 synthesis as a plain REST endpoint any caller can hit, not something coupled to the media pipeline's internals
  • Keep request latency low enough to be usable as a real API — solving model load time, not just wiring up an endpoint
  • No client SDK — the contract itself had to be simple enough that a new caller doesn't need one

Problem

  • The XTTS-v2 text-to-speech engine was originally embedded inside the media pipeline — useful there, but unusable by anything else without duplicating the model-loading code.

Architecture

  • A Flask microservice with a single REST endpoint: accepts text input, runs it through the XTTS-v2 model, returns synthesized speech.
  • Model loading and inference are isolated from any caller-specific logic, keeping the service generic.

API Design

  • A single POST /synthesize endpoint — deliberately minimal. Text in, audio out, no auth layer yet since the only current caller is trusted internal code.

Database

  • None. The service is stateless — every request is independent and nothing is persisted between calls.

Authentication

None yet, by design for now — the only current caller is trusted internal code (the media pipeline). Documented plainly in the roadmap as the first thing to add before any external caller is given access.

Infrastructure

  • Runs as a long-lived Flask process so the XTTS-v2 model can be loaded once and kept warm, rather than a serverless function that would cold-load the model on every invocation.

Deployment

  • A single Python process (app.py) — no containerization or orchestration layer yet, appropriate for its current single-caller usage.

Monitoring

No monitoring or alerting set up — the service is small enough that failures surface directly to the caller as a failed request. That's a real gap the moment there's more than one consumer or the service runs unattended.

Engineering Decisions

  • Decoupled the TTS engine from the main content pipeline into an independently callable service, so any future project can call the same endpoint instead of re-implementing voice synthesis.

Challenges & Trade-offs

  • Balancing model load time against request latency in a synchronous Flask service — informed how the service is intended to be run (warm process, not cold-started per request).

Performance

  • Request latency is dominated by model inference time rather than network or serialization overhead, because the model stays warm in memory across requests instead of reloading per call.

Scaling

  • Vertical only today — one warm model per process.
  • The natural next step is horizontal: multiple warm-model worker processes behind a load balancer, since the model itself, not I/O, is the bottleneck.
  • Request queuing would matter before worker count does, since a single slow request currently blocks the worker handling it.

Lessons Learned

  • Extracting a single well-defined capability into its own service made it directly reusable — the same pattern is a natural next step for the transcription stage too.

Future Roadmap

  • Add authentication for external callers, and move to multiple warm-model workers behind a load balancer if concurrent traffic increases.

Process timeline

The real execution order of this system, stage by stage — not a development calendar, the actual request/data flow, with what dominates time at each step (also explorable interactively above via "Trace a request").

  1. 1ClientDetermined entirely by the caller's own request pattern — nothing the service controls.
  2. 2Flask endpointSingle-worker synchronous handling means one slow request blocks the next — the main throughput ceiling at current traffic.
  3. 3XTTS-v2 (warm model)Removing model load from the request path is the single biggest latency win in this service — the whole reason it's warm-loaded.
  4. 4Audio responseSynchronous return keeps the response path simple but ties up the connection for the full synthesis duration.

Code examples

Where each piece actually lives in the repository — pointers to the real source, not reconstructed snippets. Browse the full repo →

  • app.pyFlask endpoint
  • model/xtts_loader.pyXTTS-v2 (warm model)
  • app.pyAudio response