Docs · Text to speech

One voice, and it stays that voice

One TTS alias — auto/tts, behind POST /v1/audio/speech. Two models sit behind it, and the alias moves between them on exactly one condition: the current model has been retired upstream. Not on a slow response. Not on a rate limit. Here is the shape it returns, and why it is not the shape you might expect.

The call

curl https://freemodel.online/v1/audio/speech \
  -H "Authorization: Bearer sk-your-key" \
  -H "Content-Type: application/json" \
  -d '{"model":"auto/tts","input":"Read this back to me.","voice":"zhimao"}'

You get JSON, not audio:

{
  "object":     "audio.speech",
  "url":        "https://dashscope-result-….oss-cn-beijing.aliyuncs.com/…",
  "expires_at": "2026-09-28T12:40:00Z",
  "format":     "wav"
}

Fetch the url while it is live, or copy the file somewhere you control. It is a temporary object-store link, not a permanent CDN address.

import requests

r = requests.post(
    "https://freemodel.online/v1/audio/speech",
    headers={"Authorization": "Bearer sk-your-key"},
    json={"model": "auto/tts",
          "input": "Read this back to me.",
          "voice": "zhimao"},
).json()

audio = requests.get(r["url"]).content
open("speech." + (r["format"] or "wav"), "wb").write(audio)

Why this is not the OpenAI shape

OpenAI's /v1/audio/speech streams audio bytes back on the same connection. Ours does not, and the reason is worth stating plainly.

The model behind the alias returns a temporary URL. To hand you bytes instead, the gateway would have to pull that audio down itself and stream it back out — meaning every generated second of audio crosses the machine twice. For a feature that is free to use, that is a lot of bandwidth spent on nothing but shape fidelity.

So the shape is hybrid: JSON, with the URL lifted to the top level and the format declared. It is the one place in this API where we knowingly diverge from OpenAI's contract, and it is documented rather than papered over.

If your code calls OpenAI's speech endpoint today, this is the one line you have to change: read url from the response instead of consuming a stream. Everything else — the endpoint path, the auth header, the model / input / voice fields — is unchanged.

What the alias will and will not do

What happens upstreamWhat you get
Model retired (404 / 410)The next model in the list answers
TimeoutAn error. Same model.
Rate limitedAn error. Same model.
Upstream 5xxAn error. Same model.

Switching TTS models does not degrade your output — it changes it. Different voice, different pacing. If you are producing a 40-part audio series, a silent substitution halfway through is not a slightly worse result; it is a broken series you have to regenerate.

A retired model is the one case where falling through beats failing, because the alternative is no audio at all. So that case falls through. It is the only one.

The same rule governs every non-chat alias here. It is the opposite of pool-based routing, and it is why the embedding alias has a single candidate rather than a chain.

Request fields

FieldRequiredNotes
modelYesauto/tts. A specific model name works too.
inputYesThe text to speak.
voiceNoDefaults to zhimao.

Bodies are capped at 25 MB and rejected with 413 before any upstream call. Content-Type: multipart/form-data is accepted as well as JSON — both reach the same handler.

Related capabilities

Same naming scheme, one alias per capability — all listed in /v1/models:

asr

auto/asr

Speech to text.

embedding

auto/embed

1024-dimension vectors, one candidate.

rerank

auto/rerank

Reordering retrieved documents by relevance.

image

auto/image

Text to image.

video

auto/video

Text to video, asynchronous.