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.

POST/v1/audio/speech

Request body

NameTypeRequiredDescription
model
stringRequiredStandard model alias. Must declare speech capability. See supported models below.
input
stringRequiredThe text to convert. Maximum 4,096 characters for OpenAI models; SiliconFlow models accept up to 1,000 bytes.
voice
stringVoice 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
objectClone 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
stringOutput format: mp3 (default), opus, aac, flac, wav, pcm.
speed
numberSpeaking 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

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

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

NameTypeRequiredDescription
openai/tts-1
$15/1M charsOpenAI TTS-1. Low-latency, optimised for speed. Automatic fallback to CosyVoice2 (SiliconFlow) at priority 1.
openai/tts-1-hd
$30/1M charsOpenAI TTS-1 HD. Higher fidelity, slower generation. No SiliconFlow fallback.
openai/gpt-4o-mini-tts
$12/1M tokensGPT-4o Mini TTS. Supports natural-language instructions via the instructions parameter.
alibaba/cosyvoice2-0.5b
$7.72/1M bytesSiliconFlow CosyVoice2 0.5B. CJK-optimised, byte-based billing. Also serves as fallback for openai/tts-1.
fishaudio/fish-speech-1.5
$7.72/1M bytesFish Audio Fish-Speech 1.5. Expressive multilingual TTS.
indexteam/indextts-2
$7.72/1M bytesIndexTTS-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.

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

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

Billing is unchanged by cloning β€” you are charged for the text you synthesise, in the model's normal unit. The reference clip is not a billed input.

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:

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

The sync endpoint returns audio inline. For long-form or background TTS, use 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.
Help & contact