# Qwen3-TTS (audio) > Text-to-speech with nine natural preset voices and a zero-shot cloned voice from a speaker embedding. It is best at a brand voice that never expires and needs no provider-side registry. Status: roasOS API v1.1. Catalog `model-catalog@2026-10-06.v5`, pricing `customer-pricing@2026-11-05.v15`. The endpoint `POST /v1/audio/qwen3-tts` is live. `POST /v1/generations` stays for one minor version as a deprecated alias (see "Today"). ## Endpoint POST https://api.roasos.com/v1/audio/qwen3-tts Auth: Authorization: Bearer roasos_sk_… (scope api:write). Idempotency-Key required. quote_only / quote_id / webhook_url as in https://docs.roasos.com/llms.txt. ## Good at The clone path is the strength. `POST /v1/voices { model: "qwen3-voice-clone" }` returns a speaker embedding that roasOS stores in a private bucket; this endpoint synthesizes from it, so nothing expires and no provider holds your voice. Nine presets cover English, Chinese, Japanese, and Korean speakers. A free-text `style_instruction` steers the delivery of a preset voice ("Very happy."). Set `language` when you know it. It does not accept image, video, or audio media. `style_instruction` is ignored by the provider when a cloned `voice_id` is present. ## Pricing Unit: characters. Pricing key: `qwen3-tts`. | request | credits | |---|---| | `1000_characters` | 2 | | `2000_characters` | 3 | | `5000_characters` | 6 | Credits come from the roasOS pricing table (`MODEL_COSTS`) through the catalog. No other field changes the price. ## Modes - `text-to-speech` Provider endpoints: `fal-ai/qwen-3-tts/text-to-speech/1.7b`. ## Request fields | field | type | required | default | rule | |---|---|---|---|---| | `language` | enum | no | `auto` | `auto`, `en`, `zh`, `es`, `fr`, `de`, `it`, `ja`, `ko`, `pt`, `ru` | | `quote_id` | string | no | — | UUID | | `quote_only` | boolean | no | `false` | — | | `reference_text` | string | no | — | ≥ 1 character; ≤ 10,000 characters | | `style_instruction` | string | no | — | ≥ 1 character; ≤ 2,000 characters | | `temperature` | number | no | `0.9` | 0–1 | | `text` | string | yes | — | ≥ 1 character; ≤ 10,000 characters | | `voice` | enum | no | `Aiden` | `Aiden`, `Dylan`, `Ryan`, `Eric`, `Vivian`, `Serena`, `Sohee`, `Ono_Anna`, `Uncle_Fu` | | `voice_id` | string | no | — | UUID | | `wait_timeout_seconds` | integer | no | `0` | 0–60 | | `webhook_url` | string | no | — | https URL; ≤ 4,096 characters | The schema is strict: a field that is not in this table returns `400 unsupported_option`. Field names are the roasOS role names, one name per role across every family. ## Media - `voice_id` — a saved voice clone - Refused: `avatar_id`, `elements`, `product_id`, `product_images`, `references`, `reference_images`, `image_url`, `end_image_url`, `scene_id`, `scene_image`, `video_urls`, `video_url`, `audio_urls`, `audio_url`. A refused role returns `400 unsupported_option`. - Every URL must be an immutable object on `https://cdn.roasos.com/…` that your workspace owns; any other URL is `400 reference_media_not_workspace_owned`. Upload with `POST /v1/uploads` (direct POST of the bytes to `put_url`, or a `source_url` import), then use the returned CDN URL. ## Prompt `text` limit 10,000 characters; `style_instruction` limit 2,000. roasOS adds no wrapper. - Write `text` as spoken words. Numbers and units read best when spelled out. - Use `style_instruction` for delivery, not content: "Calm, slow, a little tired." or "Very happy, fast." - Do not send `style_instruction` with `voice_id`; the provider ignores it and roasOS gives no signal today (spec gap 2). - Send `reference_text` with a cloned voice; the exact transcript of the sample improves the clone. - Set `language` when the text is not English; `auto` can guess wrong on short lines. - Keep the text short. The provider default token budget (200 codec tokens) can truncate a long read (spec gap 1). ## Not exposed - `top_k`: pinned to `50`. - `top_p`: pinned to `1`. - `repetition_penalty`: pinned to `1.05`. - `subtalker_dosample`: pinned to `true`. - `subtalker_top_k`: pinned to `50`. - `subtalker_top_p`: pinned to `1`. - `subtalker_temperature`: pinned to `0.9`. - `max_new_tokens`: pinned to `200 codec tokens, the fal default; it caps the clip length`. - `speaker_voice_embedding_file_url`: pinned to `server-minted from voice_id; never a caller field`. - `temperature`: pinned to `the 0-1 clamp is ours; fal documents only the default`. - `references`: not accepted by this public endpoint. - `avatar_id`: not accepted by this public endpoint. - `product_id`: not accepted by this public endpoint. - `audio_urls`: not accepted by this public endpoint. - `image_url`: not accepted by this public endpoint. - `speed`: not accepted by this public endpoint. - `pitch`: not accepted by this public endpoint. - `emotion`: not accepted by this public endpoint. ## Result Result kind `audio`. Always present: `url`. Present when the run produces it: `timestamps`. ```json { "data": { "id": "gen_…", "kind": "audio", "model": "qwen3-tts", "status": "completed", "result": {"timestamps":[{}],"url":"https://cdn.roasos.com/workspaces/ws_01/result"} } } ``` Files live on `https://cdn.roasos.com` and are immutable; a result URL can be a media input to the next call. Poll `GET /v1/jobs/{id}/status` → `{ id, status, updated_at }`, then `GET /v1/jobs/{id}/result` (`409 run_not_completed` until the job is `completed`, `failed` or `canceled`). Statuses: `queued → processing → completed | failed | canceled`. ## Example ```bash # 1. quote — nothing is spent curl -X POST https://api.roasos.com/v1/audio/qwen3-tts \ -H "Authorization: Bearer roasos_sk_live_…" \ -H "Idempotency-Key: 0f1c9e2a-qwen3-tts-0001" \ -H "Content-Type: application/json" \ -d '{"quote_only":true,"text":"Two drops, morning and night. That is the whole routine."}' # → 200 { "data": { "kind": "quote", "quote_id": "q_7Hk…", "credits": 2, "expires_at": "…", "confirmation": { "model": "qwen3-tts", "example": "1000_characters" } } } # 2. run — the same body and the same Idempotency-Key, quote_id instead of quote_only curl -X POST https://api.roasos.com/v1/audio/qwen3-tts \ -H "Authorization: Bearer roasos_sk_live_…" \ -H "Idempotency-Key: 0f1c9e2a-qwen3-tts-0001" \ -H "Content-Type: application/json" \ -d '{"quote_id":"q_7Hk…","text":"Two drops, morning and night. That is the whole routine."}' # → 201 { "data": { "id": "gen_…", "kind": "audio", "model": "qwen3-tts", "status": "queued", "credits_reserved": 2, "links": { "status": "/v1/jobs/gen_…/status", "result": "/v1/jobs/gen_…/result" } } } # 3. poll curl https://api.roasos.com/v1/jobs/gen_…/status -H "Authorization: Bearer roasos_sk_live_…" curl https://api.roasos.com/v1/jobs/gen_…/result -H "Authorization: Bearer roasos_sk_live_…" ``` ## Today `POST /v1/audio/qwen3-tts` is the endpoint. `POST /v1/generations` still works with `{ "type": "audio", "model": "qwen3-tts", "prompt": "", "options": { "voice": "Serena", "language": "en", "style_instruction": "Very happy.", "temperature": 0.9 } }`; there the script goes in `prompt`. `voice_id` names a saved clone from `POST /v1/voices`, and `reference_text` travels with it. `options.speed`, `pitch`, `stability`, and `similarity_boost` have no provider field and are accepted at their default values only. ## See also https://docs.roasos.com/models/audio/qwen3-tts · https://api.roasos.com/v1/catalog/audio/qwen3-tts · fal-ai/qwen-3-tts/text-to-speech/1.7b