Text to Speech · IntroductionNEW
Text-to-Speech (TTS) API: send text, get a link to the synthesized speech (platform CDN direct link, mono 24kHz), optionally with a precisely aligned SRT subtitle file. Synchronous — one request returns the result directly, no polling and no taskId. 32 curated Chinese / English voices (322 in the full catalog), adjustable rate / pitch / volume, and per-sentence prosody control; long text is auto-segmented and stitched. Three output formats: mp3 / mp3-high / webm. Add a model field to the same endpoint to switch to the Qwen models (qwen-audio-3.1-tts-flash / qwen-audio-3.0-tts-flash / qwen-audio-3.0-tts-plus / qwen3-tts-flash): more natural voices, dialects, multiple languages, natural-language instructions and emotion tags, billed by actual usage — see the “Qwen models” tab.
Endpoints
| Usage | Method | Path |
|---|---|---|
| Synthesize speech | POST | /v1/tts |
| Voice list (curated) | GET | /v1/tts/voices |
| Voice list (all 322) | GET | /v1/tts/voices?all=1 |
| Voice list of a Qwen model | GET | /v1/tts/voices?model=<千问模型id> |
Authentication
Authorization: Bearer <API_KEY> Content-Type: application/json
Request Parameters
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
| text | string | Either | — | Text to synthesize (long text is auto-segmented and stitched); up to ~256KB. Mutually exclusive with segments |
| segments | array | Either | — | Per-sentence rate / pitch / volume — see “Per-sentence control”. Mutually exclusive with text, max 200 segments |
| voice | string | — | zh-CN-XiaoxiaoNeural | Voice — use a shortName from GET /v1/tts/voices; if set it must be a supported voice |
| format | string | — | mp3 | Output format: mp3 (default) / mp3-high / webm (web playback, max 2000 chars per request) — see “Output formats” |
| rate | string | — | +0% | Rate: x-slow / slow / medium / fast / x-fast / default, or percentage +40% / -20%, or a multiplier like 1.0 |
| pitch | string | — | +0Hz | Pitch: x-low ~ x-high / default, or a signed offset +2st / -5Hz / +10%, or an absolute value like 150Hz |
| volume | string | — | +0% | Volume: silent / x-soft / soft / medium / loud / x-loud / default, or +20% / +6dB / 0–100 |
| subtitle | bool | — | false | Also return an SRT subtitle strictly aligned to the audio — see “Subtitles”. No extra charge |
| model | string | — | edge | Omit (or edge) for the default channel described in this table; set a Qwen model id to switch — its params are in the “Qwen models” tab. Case-sensitive |
Output formats
| format | File | Use case |
|---|---|---|
| mp3 | 24kHz 48kbps mono mp3 | General purpose, smallest size; use this for long text |
| mp3-high | 24kHz 96kbps mono mp3 | Better quality, roughly 2× the size |
| webm | 24kHz Opus (WebM) | Direct web playback; limited to 2000 characters per request |
Per-sentence control (segments)
Use segments instead of text to give each sentence its own rate / pitch / volume — e.g. a normal-paced opening, a slower and louder key sentence, then a quick wrap-up. Up to 200 segments.
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
| text | string | Required | — | Text of this segment |
| rate | string | — | — | Rate for this segment; inherits the top-level rate when omitted |
| pitch | string | — | — | Pitch for this segment; inherits the top-level pitch when omitted |
| volume | string | — | — | Volume for this segment; inherits the top-level volume when omitted |
- Fields omitted in a segment inherit the top-level value, so you only write what you want to change.
- All segments share one voice (voice is top-level only). A single request cannot mix voices — use separate requests for multiple characters.
- There is no pause tag between segments — the speech service cannot insert silence. Use punctuation for pauses, or split into multiple requests and stitch them yourself.
curl -X POST https://api.cqtai.com/v1/tts \
-H 'Authorization: Bearer <API_KEY>' \
-H 'Content-Type: application/json' \
-d '{
"voice": "zh-CN-YunxiNeural",
"segments": [
{ "text": "先说一句正常语速的开场白。" },
{ "text": "这一句要慢下来,重点强调。", "rate": "-25%", "volume": "loud" },
{ "text": "最后快速收尾。", "rate": "+35%", "pitch": "+2st" }
]
}'Subtitles (SRT)
Add "subtitle": true to the request and the response gains a subtitleUrl. The timeline is exact, not recognized — it comes from the word-boundary positions the speech engine reports while synthesizing, so there are no ASR typos and nothing drifts out of sync.
curl -X POST https://api.cqtai.com/v1/tts \
-H 'Authorization: Bearer <API_KEY>' \
-H 'Content-Type: application/json' \
-d '{
"text": "第一句话。第二句话稍微长一点,用来演示断句。",
"voice": "zh-CN-XiaoxiaoNeural",
"subtitle": true
}'
# -> { "code":200, "data":{
# "url":"https://cdn.novapi.ai/tts/....mp3",
# "subtitleUrl":"https://cdn.novapi.ai/tts/....srt",
# "duration":6.20, "cost":1, "format":"mp3" } }- No extra charge and no extra latency (the data falls out of synthesis itself).
- The text matches exactly what you sent (nothing is rewritten by recognition).
- Stays aligned after rate / segments changes the pace (the timeline shifts and the subtitle shifts with it).
- subtitle works with all three formats (the subtitle file is independent of the audio format).
- Use it for a web player’s <track kind="subtitles">, import into an editor, or pass subtitleUrl straight to the video subtitle burn-in endpoint (FFmpeg burn_subtitle).
Layout rules (fixed, not configurable yet): single line; up to ~20 Chinese characters / 40 Latin characters per line; each cue lasts 0.7–7 seconds; breaks at sentence-final punctuation and at pauses longer than 0.6 s. The file is standard SubRip, UTF-8 without BOM, LF line endings.
Request Example
curl -X POST https://api.cqtai.com/v1/tts \
-H 'Authorization: Bearer <API_KEY>' \
-H 'Content-Type: application/json' \
-d '{
"text": "你好,欢迎使用语音合成服务。",
"voice": "zh-CN-XiaoxiaoNeural"
}'
# -> { "code":200, "data":{ "url":"https://cdn.novapi.ai/tts/....mp3", "duration":3.62, "cost":1, "format":"mp3" } }curl -X POST https://api.cqtai.com/v1/tts \
-H 'Authorization: Bearer <API_KEY>' \
-H 'Content-Type: application/json' \
-d '{
"text": "This is a faster, higher-pitched sample.",
"voice": "en-US-AriaNeural",
"rate": "+15%",
"pitch": "+3Hz"
}'Response Example
{
"code": 200,
"msg": "success",
"data": {
"url": "https://cdn.novapi.ai/tts/1756272000000_ab12cd.mp3",
"duration": 3.62,
"cost": 1,
"format": "mp3"
}
}Supported voices
| Voice (shortName) | Name | Gender | Locale |
|---|
- The voice list is loaded live from GET /v1/tts/voices (the docs page shows curated voices by default; switch to “Full catalog” for all 322 across 142 locales). voice accepts any shortName listed there.
- For mixed Chinese/English text use AvaMultilingual / AndrewMultilingual — one voice reads both, no per-language switching.
- In ?all=1 the voices array may occasionally be empty (the catalog is still loading right after start-up); loadedAt is then an empty string — fall back to presets rather than treating it as “no voices”.
- The live API response is always authoritative.
curl -X GET https://api.cqtai.com/v1/tts/voices \
-H 'Authorization: Bearer <API_KEY>'
# -> { "code":200, "data":[ { "id":"edge:zh-CN-XiaoxiaoNeural",
# "shortName":"zh-CN-XiaoxiaoNeural", "name":"晓晓(温柔女声)",
# "nameEn":"Xiaoxiao (warm female)", "gender":"female",
# "language":"zh", "locale":"zh-CN" }, ... ] }
# 全量音色(322 个 / 142 种语言地区)
curl -X GET 'https://api.cqtai.com/v1/tts/voices?all=1' \
-H 'Authorization: Bearer <API_KEY>'
# -> { "code":200, "data":{ "presets":[...], "voices":[...], "formats":[...], "loadedAt":"..." } }Capability limits
The default channel controls voice + rate + pitch + volume. The following are not supported — do not port parameters from other TTS products:
| Not supported | Description |
|---|---|
| Emotional styles (cheerful, sad, angry…) | Not on the default channel; use a Qwen qwen-audio-3.x model (emotion tags + instruction) |
| Role play (role) | Same as above |
| Inserted silence / pause tags | Use punctuation, or split into several requests and stitch them yourself |
| Multiple voices in one request | One voice per request; use separate requests for multiple characters |
| Raw SSML pass-through | The request body only accepts the documented fields, not custom SSML |
| Number / date reading, phoneme hints | say-as / phoneme are not supported |
Models
Same endpoint, POST /v1/tts — add model to the request to switch to a Qwen model: more natural voices, dialects, multiple languages and emotion control. Omit model (or send edge) and you get the default channel (see its tab), unchanged. model is case-sensitive and must match the table exactly.
| model | Billing (actual usage) | format | Default voice | Extra params |
|---|---|---|---|---|
| qwen-audio-3.1-tts-flash | Input 150 / output 1200 credits per 1M tokens | mp3 (default) / wav | longanhuan_v3.1 | instruction, sampleRate |
| qwen-audio-3.0-tts-flash | 100 credits / 10k characters | mp3 (default) / wav | longanhuan_v3.6 | instruction, sampleRate |
| qwen-audio-3.0-tts-plus | 140 credits / 10k characters | mp3 (default) / wav | longanlingxin | instruction, sampleRate |
| qwen3-tts-flash | 80 credits / 10k characters | wav | Cherry | language |
Request Parameters
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
| voice | string | — | — | Voice — each model only accepts its own set (see below, or GET /v1/tts/voices?model=<model id>); omit for the model default. A voice from another Qwen model returns 400 (voice xxx belongs to …) |
| instruction | string | — | — | Natural-language delivery control, e.g. “speak in a Northeastern accent”, “gentle tone, a bit slower” (qwen-audio-3.x) |
| sampleRate | int | — | — | Sample rate, e.g. 16000 / 24000 / 48000 (qwen-audio-3.x) |
| language | string | — | — | Language hint (qwen3-tts-flash): Auto / Chinese / English / Japanese / Korean / French / German / Spanish / Italian / Portuguese / Russian |
Voices (voice)
Multilingual & dialects (Shanghai / Cantonese / Northeastern / Chongqing / Shaanxi / Yunnan / Ningbo / Gansu; ja / ko / fr / de / pt / it / vi / id):longanhuan_v3.1(female · default)、longanlingxin_v3.1(female)、longanfengyue_v3.1(female)、xunanchuan_v3.1(male)
Premium Chinese (Mandarin only):yuxiaoyun_v3.1、qiaoxiaojiao_v3.1、xiaxiaochen_v3.1、anmingyuan_v3.1(male)、wenhuaiqing_v3.1、anxiaolan_v3.1、xieshurou_v3.1、baiqinglan_v3.1、xuyuyuan_v3.1、anruorou_v3.1、wenhuaizhi_v3.1、xiaoxingzhi_v3.1、guyunshu_v3.1、huozhuoshi_v3.1(male)、yeqinghe_v3.1、yunhuanhuan_v3.1、xuxiaoqiao_v3.1、baianran_v3.1、xuyanchu_v3.1、yezhiqing_v3.1、andi_v3.1(male · ABC accent)、anyuqing_v3.1
Premium English (English only):Emily_v3.1(British)、Luna_v3.1(British)、Eric_v3.1(British)、Luca_v3.1(British)、Abby_v3.1(American)、Annie_v3.1(American)、Ava_v3.1(American)、Beth_v3.1(American)、Betty_v3.1(American)、Cally_v3.1(American)、Cindy_v3.1(American)、Donna_v3.1(American)、Andy_v3.1(American)、Brian_v3.1(American)、David_v3.1(American)
Others:longanyuanfei_v3.1、longjielidou_v3.1(boy)、longanlingxi_v3.1、longhuohuo_v3.1(teen)、longyingtao_v3.1、longanya_v3.1、longwan_v3.1、longxing_v3.1、longhua_v3.1、longhan_v3.1(male)、longanzhi_v3.1(male)、longzhe_v3.1(male)、longanyang_v3.1(male)、libai_v3.1(poetry recitation)、longling_v3.1、longniuniu_v3.1、longshanshan_v3.1、longpaopao_v3.1、loongstella_v3.1、longyuan_v3.1、longmiao_v3.1、longsanshu_v3.1(male)、longanli_v3.1、longanwen_v3.1、longanlang_v3.1(male)、longxiaoxia_v3.1、longanchong_v3.1(male · live commerce)
longanhuan_v3.6(default)、longanfengyue、longanyuanfei、longanlingxi、longanxiaoxin、longjielidou_v3.6、longpaopao_v3.6、longhuohuo_v3.6、longchuanshu_v3.6、loongmary、loongeva_v3.6、loongjohn
longanlingxin(default)、longanlufeng
The two 3.0 models also have 500+ base voices named qwen-audio-3.0-tts-{plus|flash}-{suffix}; use them the same way (matching the model).
Cherry(default)、Serena、Ethan、Chelsie、Momo、Vivian、Moon、Maia、Kai、Nofish、Bella、Jennifer、Ryan、Katerina、Aiden、Eldric Sage、Mia、Mochi、Bellona、Vincent、Bunny、Neil、Elias、Arthur、Nini、Ebona、Seren、Pip、Stella、Bodega、Sonrisa、Alek、Dolce、Sohee、Ono Anna、Lenn、Emilien、Andre、Radio Gol、Jada(Shanghainese)、Dylan(Beijing dialect)、Li(Nanjing dialect)、Marcus(Shaanxi dialect)、Roy(Hokkien)、Peter(Tianjin dialect)、Sunny(Sichuanese)、Eric(Sichuanese)、Rocky(Cantonese)、Kiki(Cantonese)
All 49 above are verified. Names with spaces (e.g. Eldric Sage) are sent as-is.
Emotion / sound tags (qwen-audio-3.x only, written inside text)
- Control (applies to the text after it, until the next control tag):
[sad][amazed][angry][excited][sarcastic][curious][bored][tired][scornful][shouting][deep and loud shouting][trembling][like dracula][asmr][panicked][mischievously][empathetic][whispers][reluctantly][crying][serious][very slowly][very fast] - Sound (inserts an effect at that position):
[gasp][sighing][clears throat][giggles][laughing][cough][snorts]
curl -X GET 'https://api.cqtai.com/v1/tts/voices?model=qwen-audio-3.1-tts-flash'
# -> { "code":200, "data":{ "model":"qwen-audio-3.1-tts-flash",
# "defaultVoice":"longanhuan_v3.1", "formats":[...], "params":[...],
# "voices":{ "groups":[ { "group":"…", "groupEn":"…",
# "voices":[ { "voice":"longanhuan_v3.1", "note":"女·默认", "noteEn":"female · default" }, ... ] } ],
# "notes":[...], "notesEn":[...] } } }curl -X POST https://api.cqtai.com/v1/tts \
-H 'Authorization: Bearer <API_KEY>' \
-H 'Content-Type: application/json' \
-d '{
"model": "qwen-audio-3.1-tts-flash",
"voice": "xunanchuan_v3.1",
"text": "[excited]今天的天气真不错![laughing]我们一起出去玩吧!",
"instruction": "请用东北话表达"
}'
# -> { "code":200, "data":{ "url":"https://cdn.novapi.ai/tts/....mp3", "duration":5.18,
# "cost":0.0798, "format":"mp3", "model":"qwen-audio-3.1-tts-flash",
# "usage":{ "characters":53, "input_tokens":20, "output_tokens":64, "total_tokens":84 } } }curl -X POST https://api.cqtai.com/v1/tts \
-H 'Authorization: Bearer <API_KEY>' \
-H 'Content-Type: application/json' \
-d '{
"model": "qwen3-tts-flash",
"voice": "Dylan",
"text": "今儿个天儿真不错。",
"language": "Chinese"
}'Response Fields
| Field | Type | Description |
|---|---|---|
| url | string | Link to the synthesized speech (platform CDN direct link, mono 24kHz) |
| duration | number | Duration of synthesized audio (seconds) |
| cost | number | Credits consumed by this call |
| format | string | Actual output format |
| subtitleUrl | string | SRT subtitle link. Present only when the request set subtitle: true — never an empty string |
| model | string | Qwen models only: the model id used |
| usage | object | Qwen models only: the billing basis { characters, input_tokens, output_tokens, total_tokens }; qwen-audio-3.1 bills by tokens, the others by characters (not the same as character count) |
💰 See the pricing page for prices →
Common Errors
| Code | msg | Meaning |
|---|---|---|
| 401 | unauthorized | Missing valid credentials |
| 400 | invalid JSON | Request body is not valid JSON |
| 400 | text required | No text provided (both text and segments are empty) |
| 400 | text and segments are mutually exclusive; send one | Send only one of the two fields |
| 400 | segments contain no text | Every segment’s text is blank |
| 400 | too many segments (max 200) | More than the 200-segment limit |
| 400 | unsupported voice | Voice not in the supported list |
| 400 | unsupported format "xxx"; want one of mp3, mp3-high, webm | Invalid format value |
| 400 | invalid rate/pitch/volume "xxx" (segments[N]); want … | Malformed rate / pitch / volume — the message states the expected shape and which segment failed |
| 400 | format "webm" cannot be concatenated, so it is limited to 2000 characters … | webm text too long — see “Output formats” |
| 400 | edge tts rejected the request: … | The speech service rejected the content (should not normally happen — please report it) |
| 400 | unsupported model | model is neither edge nor a Qwen model id (case-sensitive) |
| 400 | instruction/sampleRate/language require a paid model | Qwen-model-only params sent to the default channel |
| 400 | pitch must be a string like "+5Hz" for the default model | Default-channel pitch sent as a number |
| 400 | segments/rate/volume/subtitle are not supported by … | Default-channel-only params sent to a Qwen model |
| 400 | xxx is not supported by … | This Qwen model does not accept the param (see “Extra params” in the model table) |
| 400 | unsupported format for … (…) | format not supported by this Qwen model |
| 400 | voice … belongs to …, not … | The voice belongs to another Qwen model (e.g. a 3.0 voice on 3.1); see GET /v1/tts/voices?model= for valid voices |
| 400 | InvalidParameter: … | Invalid Qwen-model parameter (e.g. a voice the model does not support) |
| 429 | rate limited, retry later | Qwen model rate-limited, retry later (nothing charged) |
| 503 | model unavailable | Qwen models are currently unavailable |
| 413 | text too long | Request body exceeds the 256KB limit |
| 402 | insufficient balance | Insufficient balance (nothing charged, no link returned) |
| 502 | tts synthesis failed | Synthesis temporarily unavailable — safe to retry (nothing charged) |
| 500 | upload failed | Result upload failed, retry (nothing charged) |