Text-to-Speech

Convert text to natural-sounding speech. Drop-in replacement for POST /v1/audio/speech. Model availability, voices, and pricing vary by model.

POST/v1/audio/speech

Request body

NameTypeRequiredDescription
model
stringRequiredStandard model alias. Must declare speech capability. See supported models below.
input
stringRequiredThe text to convert. Input limits depend on the selected model and provider.
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/v1",
    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")
The previously listed CosyVoice2 and Fish Speech aliases are no longer in the current model catalog. Do not assume they are automatic fallbacks.

Supported models

NameTypeRequiredDescription
openai/tts-1
speechOpenAI TTS-1; consult its model page for current pricing.
openai/tts-1-hd
speechOpenAI TTS-1 HD; check supported voice and format options.
openai/gpt-4o-mini-tts
speechGPT-4o Mini TTS; check the model page for current pricing.
indexteam/indextts-2
speechIndexTTS-2; see voice discovery and reference-audio details below.

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