Skip to content

Add GET /v1/audio/voices endpoint for TTS voice discovery - #743

Merged
lucasnewman merged 2 commits into
Blaizzy:mainfrom
etherious1804:feat/voices-endpoint
Jun 3, 2026
Merged

Add GET /v1/audio/voices endpoint for TTS voice discovery#743
lucasnewman merged 2 commits into
Blaizzy:mainfrom
etherious1804:feat/voices-endpoint

Conversation

@etherious1804

Copy link
Copy Markdown

Summary

Adds a small GET /v1/audio/voices?model=<repo_id> handler to the mlx-audio HTTP server so OpenAI-compatible clients can populate a voice picker for voice-pack-based TTS models (Kokoro and similar). Resolves #742.

The implementation is ~34 lines, no new dependencies. Returns OpenAI-style { object, data } shape so existing clients' voice-list parsers work without changes.

Behavior

  • GET /v1/audio/voices?model=mlx-community/Kokoro-82M-bf16 → enumerates voices/*.safetensors from the HF snapshot, returns 54 entries.
  • GET /v1/audio/voices?model=<model without voices/ subdir> → returns { data: [] } so clients can fall back gracefully (relevant for voice-cloning models like Higgs Audio that ship reference WAVs in a different layout).
  • GET /v1/audio/voices (no model) → HTTP 400.
  • GET /v1/audio/voices?model=<bogus repo id> → returns { data: [], error: "..." } rather than 500, again so clients fall back instead of crashing.

Why

Marinara Engine and several other OpenAI-compatible clients already hit ${baseUrl}/audio/voices to populate their per-character voice pickers. Without this endpoint, those clients see a 404 and fall back to their hardcoded OpenAI 9-voice list — so users running mlx-audio with Kokoro (or any voice-pack TTS model) can't actually see the voices their server supports.

With this endpoint plus the small client-side change to pass the model id (Pasta-Devs/Marinara-Engine#1131), Marinara's voice picker correctly shows all 54 Kokoro voices instead of the OpenAI fallback.

Test plan

  • Start mlx_audio.server locally with default flags.
  • Trigger any TTS request with model mlx-community/Kokoro-82M-bf16 to populate the HF cache.
  • curl 'http://127.0.0.1:8080/v1/audio/voices?model=mlx-community/Kokoro-82M-bf16' → expect 54 voice entries.
  • curl 'http://127.0.0.1:8080/v1/audio/voices' → expect HTTP 400.
  • curl 'http://127.0.0.1:8080/v1/audio/voices?model=mlx-community/higgs-audio-v2-3B-mlx-q6' → expect { data: [] } (no voices/ dir in Higgs repo).

I verified the first three myself against a local mlx-audio run. The Higgs case is untested by me but follows from the voices_dir.is_dir() check returning False; happy to also test it if you'd like before merge.

🤖 Generated with Claude Code

OpenAI-compatible clients that want to populate a voice picker have
no way to ask the server which voices a given TTS model supports.
For voice-pack-based models like Kokoro, the catalog is determined
by the .safetensors files in the model's voices/ directory — readily
discoverable but not exposed over HTTP today.

This adds a small GET /v1/audio/voices?model=<repo_id> handler that
resolves the model's HF snapshot, enumerates voices/*.safetensors,
and returns {id, name} entries in an OpenAI-style { object, data }
envelope. Models without a voices/ subdirectory return an empty
data list rather than an error, so callers can fall back gracefully.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lucasnewman

Copy link
Copy Markdown
Collaborator

This API isn't standard but it's pretty self-contained so I'm ok with it. Note it won't work with most models, but it sounds like you have a specific use case in mind.

@Blaizzy

Blaizzy commented May 30, 2026

Copy link
Copy Markdown
Owner

@lucasnewman Is there a standard API for this metadata from openai spec?

@lucasnewman

Copy link
Copy Markdown
Collaborator

@lucasnewman Is there a standard API for this metadata from openai spec?

No, it's usually just docs and isn't applicable to voice matching models.

@lucasnewman
lucasnewman merged commit a64ee9d into Blaizzy:main Jun 3, 2026
12 checks passed
@etherious1804

Copy link
Copy Markdown
Author

Thanks both for taking a look!

On the standard-API point — yeah, agree it's non-standard. The motivation is that a number of OpenAI-compatible TTS clients already probe /audio/voices to populate voice pickers (the OpenAI SDK doesn't expose this, but the pattern is common in third-party TTS UIs that target multiple OAI-compatible backends). Without the endpoint they hit a 404 and silently fall back to OpenAI's hardcoded 9-voice list, so users running mlx-audio with Kokoro end up seeing alloy/echo/fable/... instead of the 54 actual Kokoro voices their server supports.

To @lucasnewman's point about most models not supporting it — totally agree, which is why the handler degrades gracefully rather than failing:

  • Model has a voices/ directory (Kokoro and similar voice-pack TTS) → enumerates them.
  • Model doesn't (voice-cloning, single-voice, reference-audio models) → returns { data: [] }, not a 500.
  • No model param → 400.

So clients always get a well-formed response and can fall back to whatever default UX they want for models that don't expose a voice list. Effectively opt-in by model architecture — surfaces voices only when the model actually has discrete voices to surface.

Happy to also run the test against a non-Kokoro model on my end before merge if that'd help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TTS server: expose available voices for a model via /v1/audio/voices

3 participants