Text-to-Speech
Convert text to natural-sounding speech. Drop-in replacement for POST /v1/audio/speech with automatic fallback across OpenAI and SiliconFlow TTS models.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
model | string | Required | Standard model alias. Must declare speech capability. See supported models below. |
input | string | Required | The text to convert. Maximum 4,096 characters for OpenAI models; SiliconFlow models accept up to 1,000 bytes. |
voice | string | Voice to use. OpenAI models: alloy, echo, fable, onyx, nova, shimmer. Open-weight models publish their own values β read them from the model's voices list in GET /v1/audio/models and send one verbatim, since an unrecognised value is rejected rather than quietly replaced. Omit voice when sending reference_audio; the two are mutually exclusive. | |
reference_audio | object | Clone a voice from a clip you supply, on models that support it. Give either file_id (upload the clip to POST /v1/files first β the recommended form) or audio (a small inline data:audio/<format>;base64,... URI), plus text (the clip's transcript). A URL is not accepted. Check GET /v1/audio/models for a model's reference_audio block: accepted formats, maximum duration, and whether a transcript is required. The clip is validated before any upstream call, so a clip that cannot be used costs you nothing. Not available on POST /v1/jobs. | |
response_format | string | Output format: mp3 (default), opus, aac, flac, wav, pcm. | |
speed | number | Speaking speed multiplier, 0.25 to 4.0. Default 1.0. Supported by OpenAI models only. |
Response
Returns audio bytes with the appropriate Content-Type header (e.g. audio/mpeg for mp3). Status 200 on success.
Quick start
from openai import OpenAI
client = OpenAI(
base_url="https://api.therouter.ai",
api_key="YOUR_API_KEY",
)
with client.audio.speech.with_streaming_response.create(
model="openai/tts-1",
voice="alloy",
input="Hello from TheRouter.ai!",
) as response:
response.stream_to_file("output.mp3")
CJK / Chinese text
For Chinese and other CJK text, use alibaba/cosyvoice2-0.5b (SiliconFlow). It is billed per UTF-8 byte rather than per character, making CJK billing proportional to actual data size. The fallback from openai/tts-1 to CosyVoice2 is automatic when the OpenAI provider is unavailable.
# SiliconFlow CosyVoice2 β optimised for Chinese text
# Billed per UTF-8 byte (3 bytes per CJK character)
response = client.audio.speech.create(
model="alibaba/cosyvoice2-0.5b",
voice="alloy",
input="δ½ ε₯½οΌζ¬’θΏδ½Ώη¨ TheRouterοΌ",
)
Supported models
| Name | Type | Required | Description |
|---|---|---|---|
openai/tts-1 | $15/1M chars | OpenAI TTS-1. Low-latency, optimised for speed. Automatic fallback to CosyVoice2 (SiliconFlow) at priority 1. | |
openai/tts-1-hd | $30/1M chars | OpenAI TTS-1 HD. Higher fidelity, slower generation. No SiliconFlow fallback. | |
openai/gpt-4o-mini-tts | $12/1M tokens | GPT-4o Mini TTS. Supports natural-language instructions via the instructions parameter. | |
alibaba/cosyvoice2-0.5b | $7.72/1M bytes | SiliconFlow CosyVoice2 0.5B. CJK-optimised, byte-based billing. Also serves as fallback for openai/tts-1. | |
fishaudio/fish-speech-1.5 | $7.72/1M bytes | Fish Audio Fish-Speech 1.5. Expressive multilingual TTS. | |
indexteam/indextts-2 | $7.72/1M bytes | IndexTTS-2. Neural TTS with eight preset voices (alex, anna, bella, benjamin, charles, claire, david, diana). Returns MP3. |
Voice cloning
Some models can synthesise speech in a voice you supply rather than a preset one. Send reference_audio instead of voice:
Upload the clip to TheRouter first, then reference it by id. Your audio is stored by us and sent to the provider from our storage β we never hand a provider a URL of yours to fetch.
# 1. Upload the clip (same endpoint you already use for images)
curl https://api.therouter.ai/v1/files \
-H "Authorization: Bearer $THEROUTER_API_KEY" \
-F purpose=user_data \
-F file=@my-voice.mp3
# -> { "id": "file_abc123", ... }
# 2. Synthesise in that voice
curl https://api.therouter.ai/v1/audio/speech \
-H "Authorization: Bearer $THEROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "indexteam/indextts-2",
"input": "The text you want spoken.",
"reference_audio": {
"file_id": "file_abc123",
"text": "Exact transcript of the clip."
}
}' --output cloned.mp3For a small clip you can skip the upload and inline it instead, as "audio": "data:audio/mp3;base64,...". A URL is not accepted in either field.
Discover support and limits per model from GET /v1/audio/models β a cloning-capable model carries a reference_audio block naming its accepted formats, maximum clip duration, and whether a transcript is required. For indexteam/indextts-2 today that is MP3 or WAV, at most 30 seconds (8β10 seconds is what the model does best with), transcript required.
Your clip is validated before the request reaches a provider, so an unsupported format, an over-long clip or a missing transcript comes back as a 400 naming the limit β and is not billed. A cloning request is never quietly downgraded to a preset voice: if no available route can clone, the request fails rather than returning a different voice than you asked for.
Reusable voices
Sending the clip on every request is wasteful if you always use the same voice. Create it once and refer to it by id:
# Create a voice from an uploaded clip
curl https://api.therouter.ai/v1/audio/voices \
-H "Authorization: Bearer $THEROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "indexteam/indextts-2",
"name": "Narrator",
"reference_audio": { "file_id": "file_abc123", "text": "Transcript of the clip." }
}'
# -> { "id": "voice_9f2c...", "object": "audio.voice", "name": "Narrator", ... }
# Then just name it
curl https://api.therouter.ai/v1/audio/speech \
-H "Authorization: Bearer $THEROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "indexteam/indextts-2",
"input": "Anything you like, in that voice.",
"voice": "voice_9f2c..."
}' --output narrated.mp3
curl https://api.therouter.ai/v1/audio/voices # list yours
curl -X DELETE https://api.therouter.ai/v1/audio/voices/voice_9f2c...A voice belongs to the account that created it and is usable only with the model it was created for. Up to 50 voices per account; delete one to make room.
Creating a voice is not billed on any model we currently serve β you pay for synthesis, as usual.
POST /v1/jobs with an audio model β the job is queued, the output stored in S3, and a presigned URL returned when complete. Voice cloning is not available on that path.