CQTAI
Text to Speech · Speech

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. Billed by synthesized audio seconds: charged synchronously, non-refundable, minimum 1 credit per call; subtitles cost nothing extra.

Synchronous calla single request returns the result directly — no taskId, no polling

Endpoints

UsageMethodPath
Synthesize speechPOST/v1/tts
Voice list (curated)GET/v1/tts/voices
Voice list (all 322)GET/v1/tts/voices?all=1

Authentication

Authorization: Bearer <API_KEY>
Content-Type: application/json

Request Parameters

ParamTypeRequiredDefaultDescription
textstringEitherText to synthesize (long text is auto-segmented and stitched); up to ~256KB. Mutually exclusive with segments
segmentsarrayEitherPer-sentence rate / pitch / volume — see “Per-sentence control”. Mutually exclusive with text, max 200 segments
voicestringzh-CN-XiaoxiaoNeuralVoice — use a shortName from GET /v1/tts/voices; if set it must be a supported voice
formatstringmp3Output format: mp3 (default) / mp3-high / webm (web playback, max 2000 chars per request) — see “Output formats”
ratestring+0%Rate: x-slow / slow / medium / fast / x-fast / default, or percentage +40% / -20%, or a multiplier like 1.0
pitchstring+0HzPitch: x-low ~ x-high / default, or a signed offset +2st / -5Hz / +10%, or an absolute value like 150Hz
volumestring+0%Volume: silent / x-soft / soft / medium / loud / x-loud / default, or +20% / +6dB / 0–100
subtitleboolfalseAlso return an SRT subtitle strictly aligned to the audio — see “Subtitles”. No extra charge
When pitch uses a semitone / hertz / percentage offset it must carry a sign: +2st is fine, 2st is rejected.
An unrecognized rate / pitch / volume returns 400 immediately with the expected shape — no synthesis is run and no credits are spent.

Output formats

formatFileUse case
mp324kHz 48kbps mono mp3General purpose, smallest size; use this for long text
mp3-high24kHz 96kbps mono mp3Better quality, roughly 2× the size
webm24kHz Opus (WebM)Direct web playback; limited to 2000 characters per request
The webm length cap comes from the format itself: long text must be synthesized in several passes and stitched, but a stitched WebM only plays its first part in most players. Rather than hand you a file that contains half the text yet bills the full length, an over-long webm request returns 400 — use mp3 instead, or split the text across requests yourself.

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.

ParamTypeRequiredDefaultDescription
textstringRequiredText of this segment
ratestringRate for this segment; inherits the top-level rate when omitted
pitchstringPitch for this segment; inherits the top-level pitch when omitted
volumestringVolume 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.

subtitleUrl only appears when the request set subtitle: true — it is never returned as an empty string.
In rare cases the speech service returns no word boundaries; the whole request then fails (502, nothing charged, safe to retry) rather than succeeding with audio but no subtitle.

Request Example

Synthesize speech
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" } }
With rate / pitch
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"
  }
}

Response Fields

FieldTypeDescription
urlstringLink to the synthesized speech (platform CDN direct link, mono 24kHz)
durationnumberDuration of synthesized audio (seconds)
costnumberCredits consumed by this call
formatstringActual output format
subtitleUrlstringSRT subtitle link. Present only when the request set subtitle: true — never an empty string

Supported voices

Loading…
Voice (shortName)NameGenderLocale
  • 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.
Fetch voice list
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

This service controls voice + rate + pitch + volume. The following are not supported — do not port parameters from other TTS products:

Not supportedDescription
Emotional styles (cheerful, sad, angry…)Not exposed by the speech engine
Role play (role)Same as above
Inserted silence / pause tagsUse punctuation, or split into several requests and stitch them yourself
Multiple voices in one requestOne voice per request; use separate requests for multiple characters
Raw SSML pass-throughThe request body only accepts the documented fields, not custom SSML
Number / date reading, phoneme hintssay-as / phoneme are not supported

Billing & Credits

  • Billed by synthesized audio seconds: credits = max(1, ceil(seconds × unit price)), default 0.1 credit/sec (10s ≈ 1 credit, 60s ≈ 6 credits).
  • Charged synchronously, non-refundable: charged once synthesis succeeds and a link is produced; on insufficient balance nothing is charged and no link is returned.
  • Subtitles are free: subtitle: true costs exactly the same as without it (the timestamps come out of synthesis itself, nothing is computed twice).
  • Parameter errors (400) are returned before synthesis starts and consume no credits.
  • Minimum 1 credit per call. Final credits settle at your user rate; a dedicated unit price takes precedence if configured.

Common Errors

CodemsgMeaning
401unauthorizedMissing valid credentials
400invalid JSONRequest body is not valid JSON
400text requiredNo text provided (both text and segments are empty)
400text and segments are mutually exclusive; send oneSend only one of the two fields
400segments contain no textEvery segment’s text is blank
400too many segments (max 200)More than the 200-segment limit
400unsupported voiceVoice not in the supported list
400unsupported format "xxx"; want one of mp3, mp3-high, webmInvalid format value
400invalid rate/pitch/volume "xxx" (segments[N]); want …Malformed rate / pitch / volume — the message states the expected shape and which segment failed
400format "webm" cannot be concatenated, so it is limited to 2000 characters …webm text too long — see “Output formats”
400edge tts rejected the request: …The speech service rejected the content (should not normally happen — please report it)
413text too longRequest body exceeds the 256KB limit
402insufficient balanceInsufficient balance (nothing charged, no link returned)
502tts synthesis failedSynthesis temporarily unavailable — safe to retry (nothing charged)
500upload failedResult upload failed, retry (nothing charged)
Set the client read timeout to ≥ 180s: the endpoint is synchronous and normally returns in seconds, but concurrent synthesis is gated, so a request may queue before it runs at peak times. Exceeding the queue limit also returns 502 tts synthesis failed (nothing charged, safe to retry).