Speech Synthesis API Reference
One text-to-speech endpoint: JSON by default (audio URL, usage and billing details), or raw audio bytes for OpenAI-compatible clients. Works with OpenAI TTS, Bailian Qwen-Audio-TTS and other speech models.
Note: JSON is returned by default: when audio.storage=qiniu, audio.url is a file copied to the EasyAPI Qiniu bucket (upstream URLs are never exposed); if Qiniu is disabled or the copy fails, the response falls back to audio.storage=inline with base64 audio.data. To receive audio bytes, send Accept: application/octet-stream (the official OpenAI SDK does this automatically); Content-Type then follows response_format: mp3 -> audio/mpeg, wav -> audio/wav, pcm -> audio/pcm, opus -> audio/ogg. stream_format=sse always returns a binary stream. Valid voices depend on the model: Bailian Qwen-Audio-TTS voices depend on the model (qwen-audio-3.0-tts-plus uses longanlingxin / longanlufeng; 3.0-flash uses longanhuan_v3.6 and others), OpenAI TTS uses alloy / nova, so check the model voice list. Provider-specific parameters can be passed through metadata.
Example model
Examples below use qwen-audio-3.0-tts-plus (Qwen Audio TTS Plus). Replace model and adjust size, quality, etc. per console docs when switching models.
Create speech
POST
https://token.easyapi.com/v1/audio/speechSynthesize the input text. JSON is returned by default (audio.url or audio.data, usage, billing); audio bytes are returned for Accept: application/octet-stream or audio/*. Sentence-level streaming is not supported: stream_format=sse is answered with the complete clip.
Request parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
Authorization | header | string | Yes | EasyAPI API Key. Format: Bearer `API_KEY` |
Content-Type | header | string | Yes | Always application/json |
Accept | string | No | Omitted or application/json: structured JSON (default); application/octet-stream or audio/*: raw audio bytes |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
model | string | Yes | Speech model ID, e.g. qwen-audio-3.0-tts-plus, tts-1 |
input | string | Yes | Text to synthesize; SSML / LaTeX supported where the model allows it |
voice | string | Yes | Voice name, model-specific; Bailian defaults to longanhuan_v3.6 |
response_format | string | No | Audio encoding: mp3 (default), wav, pcm, opus |
speed | number | No | Speed multiplier, mapped to Bailian rate (0.5–2.0) and clamped to that range |
instructions | string | No | Synthesis instruction for dialect, emotion or role, mapped to Bailian instruction |
metadata | object | No | Provider-native parameters passed through: volume(0-100), pitch(0.5-2.0), bit_rate(6-510, opus only), sample_rate, enable_ssml, word_timestamp_enabled, seed, language_hints, hot_fix, enable_aigc_tag; keys matching standard fields take precedence |
Example
{
"model": "qwen-audio-3.0-tts-plus",
"input": "你好,欢迎使用 EasyAPI 语音合成。",
"voice": "longanlingxin",
"response_format": "mp3",
"speed": 1.0,
"instructions": "用轻松自然的语气朗读"
}Response fields
| Name | Type | Description |
|---|---|---|
id | string | Request ID, same as the X-EasyAPI-Request-Id response header |
object | string | Always audio.speech |
created | integer | Response creation time (Unix seconds) |
model | string | Requested model ID |
voice | string | Voice used for this request |
audio.storage | string | qiniu: uploaded to Qiniu, see audio.url; inline: base64 in audio.data |
audio.format | string | Audio codec |
audio.content_type | string | Audio MIME type |
audio.sample_rate | integer | Sample rate in Hz; omitted when unknown |
audio.bytes | integer | Audio size in bytes |
audio.duration_seconds | number | Duration in seconds; 0 when it cannot be parsed |
audio.url | string | Qiniu CDN URL when audio.storage=qiniu |
audio.data | string | Base64 audio when audio.storage=inline |
usage.characters | integer | Billed characters when the provider charges by character (Bailian 3.0) |
usage.input_tokens | integer | Billed input tokens; falls back to upstream input_tokens or a local estimate |
usage.output_tokens | integer | Billed output (audio) tokens; upstream output_tokens wins when present |
usage.total_tokens | integer | Sum of input and output tokens |
usage.input_source | string | Input usage source: upstream or local_estimate |
usage.output_source | string | Output usage source: upstream, duration_estimate or bytes_estimate |
billing.mode | string | Billing mode: tiered_expr, ratio or price |
billing.currency | string | Amount currency, currently USD |
billing.quota | integer | Actual quota charged (500000 = 1 USD) |
billing.amount | number | Amount converted from quota (USD) |
billing.quota_per_unit | number | Quota units per USD |
billing.group_ratio | number | Effective group ratio |
billing.discount | number | Effective discount multiplier (0-1) |
billing.model_ratio | number | ratio mode: model ratio |
billing.model_price | number | price mode: per-request price |
billing.audio_ratio | number | ratio mode: audio input ratio |
billing.audio_completion_ratio | number | ratio mode: audio output ratio |
billing.matched_tier | string | tiered_expr mode: matched tier name |
billing.expression | string | tiered_expr mode: billing expression |
billing.expression_cost | number | tiered_expr mode: expression result before quota conversion |
billing.cost_multiplier | number | tiered_expr mode: cost multiplier (e.g. peak hours) |
billing.inputs | object | tiered_expr mode: variables passed to the expression (p, c, ai, ao, len) |
Default response (JSON with a Qiniu URL)
{
"id": "2026110206055130815495705874a1XipEBXYs",
"object": "audio.speech",
"created": 1793599552,
"model": "qwen-audio-3.0-tts-plus",
"voice": "longanlingxin",
"audio": {
"storage": "qiniu",
"format": "mp3",
"content_type": "audio/mpeg",
"sample_rate": 24000,
"bytes": 258048,
"duration_seconds": 4.032,
"url": "https://qiniu.easyapi.com/easyapi-ai/audio/2026/11/02/2026110206055130815495705874a1XipEBXYs.mp3"
},
"usage": {
"characters": 15,
"input_tokens": 15,
"output_tokens": 67,
"total_tokens": 82,
"input_source": "upstream",
"output_source": "duration_estimate"
},
"billing": {
"mode": "tiered_expr",
"currency": "USD",
"quota": 79,
"amount": 0.000158,
"quota_per_unit": 500000,
"group_ratio": 1,
"discount": 0.5,
"matched_tier": "speech",
"expression": "tier(\"speech\", p * 20.9895)",
"expression_cost": 314.8425,
"cost_multiplier": 1,
"inputs": { "p": 15, "c": 67, "ai": 0, "ao": 67, "len": 15 }
}
}Binary response (Accept: application/octet-stream)
HTTP/1.1 200 OK
Content-Type: audio/mpeg
Content-Length: 258048
X-EasyAPI-Audio-Bytes: 258048
X-EasyAPI-Audio-Seconds: 4.032
X-EasyAPI-Audio-Format: mp3
X-EasyAPI-Audio-Sample-Rate: 24000
<二进制音频数据>Status codes
| Status code | Meaning | Description | Data model |
|---|---|---|---|
200 | OK | JSON by default (audio.url or audio.data + usage + billing); raw audio bytes when Accept requests a binary type | AudioSpeechResponse | audio/* |
400 | Bad request | Invalid parameter or rejected by upstream, e.g. unknown voice or empty text | ErrorResponse |
401 | Unauthorized | Missing or invalid API key | ErrorResponse |
402 | Insufficient quota | Account balance cannot cover this synthesis | ErrorResponse |
429 | Rate limited | Too many requests, retry later | ErrorResponse |
500 | Server error | Upstream or gateway failure, safe to retry | ErrorResponse |
Response: Billing matches the admin usage log: billing.inputs are the variables the expression actually saw, and billing.quota is the final charged quota. billing is a whitelist and never includes channel-internal data.