Capabilities

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

bash
# 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.

bash
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 familyExample voiceFormat
qwen/qwen-audio-3.0-tts-flashloongjohn or longanhuan_v3.6WAV
qwen/qwen-audio-3.0-tts-pluslonganlingxin or longanlufengWAV

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 user message is an optional style instruction, for example Speak warmly.
  • audio is an optional top-level object: {"format": "mp3" | "wav" | "pcm16", "voice": "Milo"}. Voice names are MiMo's own and case-sensitive. Milo is known to work. Omit voice for the model's default voice.
  • "stream": true works; the audio arrives in chunks.
bash
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.

python
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.

json
{
  "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:

json
{
  "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 user message holding the text is moved to assistant.
  • A system message is treated as a style instruction.
  • An OpenAI voice name such as alloy is 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.