Audio output
Create speech or submit a music generation job using a model advertised in your environment.
Discover models with audio output through GET /api/v1/models?output_modalities=all and inspect their endpoint route. Speech synthesis and music generation have different request and response shapes; neither is audio input to chat.
Speech synthesis
POST /api/v1/audio/speech takes a TTS model, input text, a supported voice and format. Voice names and optional controls depend on the model. The synchronous route returns audio bytes; POST /api/v1/audio/speech/jobs offers an asynchronous workflow for supported requests. Select a case-sensitive voice from the named-model examples below and the selected provider's voice reference before submission. For fixed speech tasks, the Capabilities catalogue has per-endpoint references.
Named synchronous synthesis also accepts provider.order and provider.only. For safe fallback limits, same-settings provider estimates and explicitly confirmed reruns, seemedia routing.
Music generation
# Keep this key and identical body for any reconciliation of this submit. : "${TOKENHUB_IDEMPOTENCY_KEY:=$(python3 -c 'import uuid; print(uuid.uuid4())')}" export TOKENHUB_IDEMPOTENCY_KEY curl --fail-with-body https://tokenhub-api.belive.sg/api/v1/audio/generations \ -H "Authorization: Bearer $TOKENHUB_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $TOKENHUB_IDEMPOTENCY_KEY" \ -d '{"model":"<music-model-id>","prompt":"A calm instrumental piano melody with soft strings, no vocals."}'
Replace <music-model-id> with the id of a model whose endpoints[].route is audio_generations in GET /api/v1/models?output_modalities=all.
The music submit accepts model, prompt and an optional callback_url. Callbacks require a workspace webhook signing secret and a public HTTPS URL. Duration and format are not request fields. Idempotency-Key is required; replay with the same key and identical body is supported for 24 hours. Save the returned job ID and poll GET /api/v1/audio/generations/<id>; retrieve output through its authenticated content route. A new submission is 202; supported replay uses the same idempotency key and body. To recover an uncertain submit, replay it with the same key.
Availability and cost
Use only catalogue-listed models. Audio generation can have token, character or output-duration pricing, depending on the route. Read its price table and terminal usage; a successful submission is not a completed, settled output. See retry guidance and supported webhook families.
Support and limits
Speech synthesis, transcription, music jobs and realtime are separate routes. Voices, formats and input lengths are listed per model. Use the returned polling and content URLs, authenticate as required, and read the expiry field.
Gateway validation, defaults and response forms are in the operation index and the OpenAPI schemas.
Error codes and recovery are in Errors and recovery. Keep the request or job ID from each response: it identifies the call in Activity and for support.
Named-model speech example
Named synchronous synthesis accepts 1 to 600 Unicode characters and only response_format: "wav". The output is WAV audio, not JSON or raw PCM. Do not send OpenRouter's pcm or mp3 format here. With model, omit language; the voice determines it. Generic OpenAI controls such as speed are not part of this request contract.
curl --fail-with-body 'https://tokenhub-api.belive.sg/api/v1/audio/speech' \ -H "Authorization: Bearer $TOKENHUB_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"model":"qwen/qwen-audio-3.0-tts-flash","input":"Welcome to BeLive. Your delivery arrives on Thursday.","voice":"loongjohn","response_format":"wav"}' \ --output speech.wav
| Model family | Example voice | Format |
|---|---|---|
qwen/qwen-audio-3.0-tts-flash | loongjohn or longanhuan_v3.6 | WAV |
qwen/qwen-audio-3.0-tts-plus | longanlingxin or longanlufeng | WAV |
These are examples, not a shared cross-model voice namespace. Voice names are case-sensitive. The model must be listed with an audio_speech endpoint in your environment. See the provider's voice catalogue for the full list, and the SpeechRequest schema for the gateway's accepted fields. A model-less fixed speech request and a long-form speech job are different contracts; do not reuse this body for those routes.
Xiaomi MiMo speech on chat completions
xiaomi/mimo-v2.5-tts, xiaomi/mimo-v2.5-tts-voicedesign and xiaomi/mimo-v2.5-tts-voiceclone are served on POST /api/v1/chat/completions, not on /audio/speech. They use their own request shape:
- The text to speak is the final message, with role
assistant. This is not a chat reply; it is the script. - An earlier
usermessage is an optional style instruction, for exampleSpeak warmly. audiois an optional top-level object:{"format": "mp3" | "wav" | "pcm16", "voice": "Milo"}. Voice names are MiMo's own and case-sensitive.Milois known to work. Omitvoicefor the model's default voice."stream": trueworks; the audio arrives in chunks.
curl --fail-with-body https://tokenhub-api.belive.sg/api/v1/chat/completions \ -H "Authorization: Bearer $TOKENHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "xiaomi/mimo-v2.5-tts", "messages": [ {"role": "user", "content": "Speak warmly."}, {"role": "assistant", "content": "Hello from AI Hub."} ], "audio": {"format": "mp3", "voice": "Milo"} }' \ | jq -r '.choices[0].message.audio.data' | base64 --decode > speech.mp3
The audio is base64 in choices[0].message.audio.data, in the format you asked for. message.content is an empty string. message.audio also carries id, expires_at and transcript; the last two were null on a non-streamed call.
import base64 import os from openai import OpenAI client = OpenAI(base_url="https://tokenhub-api.belive.sg/api/v1", api_key=os.environ["TOKENHUB_API_KEY"]) response = client.chat.completions.create( model="xiaomi/mimo-v2.5-tts", messages=[ {"role": "user", "content": "Speak warmly."}, {"role": "assistant", "content": "Hello from AI Hub."}, ], extra_body={"audio": {"format": "mp3", "voice": "Milo"}}, ) # The audio is base64 in message.audio.data. If your type checker does not know # `.audio` on the message, read it from response.model_dump() instead. audio = base64.b64decode(response.choices[0].message.audio.data) with open("speech.mp3", "wb") as output: output.write(audio)
Voice design. xiaomi/mimo-v2.5-tts-voicedesign takes a user message that describes the voice, then the assistant text. It has no voice field.
{ "model": "xiaomi/mimo-v2.5-tts-voicedesign", "messages": [ {"role": "user", "content": "A calm, warm adult voice."}, {"role": "assistant", "content": "Hello from AI Hub."} ], "audio": {"format": "mp3"} }
Voice clone. xiaomi/mimo-v2.5-tts-voiceclone takes the reference recording as a data URL in audio.voice:
{ "model": "xiaomi/mimo-v2.5-tts-voiceclone", "messages": [{"role": "assistant", "content": "Hello from AI Hub."}], "audio": {"format": "mp3", "voice": "data:audio/mpeg;base64,<base64 of the reference recording>"} }
AI Hub also accepts these OpenAI-style variants and reshapes them for you:
- A final
usermessage holding the text is moved toassistant. - A
systemmessage is treated as a style instruction. - An OpenAI voice name such as
alloyis dropped, and the model's default voice is used.
A voice-design request without a description is refused with a 400 that names the expected shape. The native shape above is the recommended one: it is what the model receives, and it does not depend on any reshaping.
Price meters and music results
Speech can be priced per character or with separate input-text/output-audio token legs. Music can be priced per request: a model can use one request meter per output, rather than a text-token or per-second charge. Read the live price table; a zero token price does not make the output free.
A music job is complete only when its terminal status is completed. Save generation_id, read terminal usage, and download from the returned authenticated content URL before expiry. A music result can be an MP3; speech synthesis above returns WAV. Use the returned content type rather than assigning one extension to all audio.