Skip to main content
Real-time Multimodal

Voice cloning API reference

Clone a voice from 10-20 seconds of audio without training. This document covers the voice cloning API parameters and usage. For model invocation, see Qwen-Omni-Realtime or Non-real-time (Qwen-Omni) .

This document applies only to the Qwen-Omni and Qwen-Omni-Realtime voice cloning API. If you use a text-to-speech model, see Speech synthesis.

Audio requirements

High-quality input audio produces better cloning results.
ItemRequirement
Supported formatsWAV (16-bit), MP3, M4A
Duration10 to 20 seconds recommended. Maximum: 60 seconds.
File size< 10 MB
Sample rate>= 24 kHz
ChannelsMono
ContentThe audio must contain at least 3 seconds of continuous, clear speech with no background sounds. The remaining portion may include brief pauses (<=2 seconds). Avoid background music, noise, or other voices throughout the entire audio. Use normal spoken audio as input. Don't upload songs or singing audio.
LanguagesChinese (zh), English (en), German (de), Italian (it), Portuguese (pt), Spanish (es), Japanese (ja), Korean (ko), French (fr), Russian (ru), Thai (th), Indonesian (id), Arabic (ar), Czech (cs), Danish (da), Dutch (nl), Finnish (fi), Hebrew (he), Hindi (hi), Icelandic (is), Malay (ms), Norwegian (no), Persian (fa), Polish (pl), Swedish (sv), Tagalog (tl), Turkish (tr), Urdu (ur), Vietnamese (vi)
Chinese dialects: Dongbei, Shannxi, Sichuan, Henan, Changsha, Tianjin, Hangzhou, Liaoning, Shenyang, Anshan

Quick start

image

1. Workflow

Voice cloning follows a "create first, then use" workflow:
  1. Create a voice Call the Create a voice API and upload an audio clip. The system analyzes the audio and creates a custom cloned voice. You must specifytarget_modelin this step to declare which omni model will drive the voice. If you already have a created voice (call the List voices API to check), skip this step and proceed to the next one.
  2. Use the voice in a conversation Call the Omni API (realtime or non-realtime) and pass in the voice obtained in the previous step. The omni model specified in this step must match thetarget_modelfrom the previous step.

2. Model configuration and prerequisites

Select models and complete the prerequisites before starting.

Model configuration

Voice cloning requires two models:
  • Voice cloning model: qwen-voice-enrollment
  • Omni model that drives the voice:
    • qwen3.8-omni-flash-realtime
    • qwen3.5-omni-plus-realtime
    • qwen3.5-omni-flash-realtime
    • qwen3.5-omni-plus
    • qwen3.5-omni-flash

Prerequisites

  1. Get an API key: Obtain an API key. For security, configure the API key as an environment variable.
  2. Install the SDK: Make sure you have installed the latest DashScope SDK.
  3. Prepare the audio for cloning: The audio must meet the Audio requirements.

3. End-to-end example

This example clones a voice and uses it in a conversation. Key principle: The target_model specified during cloning must match the model in the subsequent Omni API call. Otherwise, synthesis fails. Replace the example file voice.mp3 with your own audio.
  • Realtime
  • Non-realtime
Applicable to the Qwen3.5-Omni-Realtime series models. For more information, see Qwen-Omni-Realtime.
Python
# Requirements: dashscope >= 1.26.5, pyaudio
import os
import requests
import base64
import pathlib
import time
import pyaudio
from dashscope.audio.qwen_omni import MultiModality, OmniRealtimeCallback, OmniRealtimeConversation
import dashscope
# Singapore (ap-southeast-1). For another region, replace ap-southeast-1 in the URL.
# API keys are region-specific. See https://docs.qwencloud.com/developer-guides/getting-started/regions
dashscope.base_http_api_url = "https://ap-southeast-1.maas.qwencloudapi.com/api/v1"

# ======= Configuration =======
DEFAULT_TARGET_MODEL = "qwen3.8-omni-flash-realtime"  # Must be the same model for cloning and conversation
DEFAULT_PREFERRED_NAME = "guanyu"
DEFAULT_AUDIO_MIME_TYPE = "audio/mpeg"
VOICE_FILE_PATH = "voice.mp3"  # Path to the local audio file for voice cloning

def create_voice(file_path: str,
                 target_model: str = DEFAULT_TARGET_MODEL,
                 preferred_name: str = DEFAULT_PREFERRED_NAME,
                 audio_mime_type: str = DEFAULT_AUDIO_MIME_TYPE) -> str:
    """
    Create a custom voice and return the voice parameter.
    """
    # If you haven't set an environment variable, replace the following line with: api_key = "sk-xxx"
    api_key = os.getenv("DASHSCOPE_API_KEY")

    file_path_obj = pathlib.Path(file_path)
    if not file_path_obj.exists():
        raise FileNotFoundError(f"Audio file not found: {file_path}")

    base64_str = base64.b64encode(file_path_obj.read_bytes()).decode()
    data_uri = f"data:{audio_mime_type};base64,{base64_str}"

    url = "https://ap-southeast-1.maas.qwencloudapi.com/api/v1/services/audio/tts/customization"
    payload = {
        "model": "qwen-voice-enrollment",
        "parameters": {"voice_clone_mode": "normal"},
        "input": {
            "action": "create",
            "target_model": target_model,
            "preferred_name": preferred_name,
            "audio": {"data": data_uri}
        }
    }
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }

    resp = requests.post(url, json=payload, headers=headers)
    if resp.status_code != 200:
        raise RuntimeError(f"Failed to create voice: {resp.status_code}, {resp.text}")

    try:
        return resp.json()["output"]["voice"]
    except (KeyError, ValueError) as e:
        raise RuntimeError(f"Failed to parse voice response: {e}")

class SimpleCallback(OmniRealtimeCallback):
    def __init__(self, pya):
        self.pya = pya
        self.out = None
    def on_open(self):
        self.out = self.pya.open(
            format=pyaudio.paInt16,
            channels=1,
            rate=24000,
            output=True
        )
    def on_event(self, response):
        if response['type'] == 'response.audio.delta':
            self.out.write(base64.b64decode(response['delta']))
        elif response['type'] == 'conversation.item.input_audio_transcription.completed':
            print(f"[User] {response['transcript']}")
        elif response['type'] == 'response.audio_transcript.done':
            print(f"[LLM] {response['transcript']}")

if __name__ == '__main__':
    # If you haven't set an environment variable, replace the following line with: dashscope.api_key = "sk-xxx"
    dashscope.api_key = os.getenv("DASHSCOPE_API_KEY")
    url = "wss://ap-southeast-1.maas.qwencloudapi.com/api-ws/v1/realtime"

    # Step 1: Clone a voice
    voice = create_voice(VOICE_FILE_PATH)
    print(f"Voice cloning complete. Voice: {voice}")

    # Step 2: Start a real-time conversation with the cloned voice
    pya = pyaudio.PyAudio()
    callback = SimpleCallback(pya)
    conv = OmniRealtimeConversation(model=DEFAULT_TARGET_MODEL, callback=callback, url=url)
    conv.connect()
    conv.update_session(
        output_modalities=[MultiModality.AUDIO, MultiModality.TEXT],
        voice=voice  # Use the cloned voice
    )
    mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
    print("Conversation started. Speak into your microphone (Ctrl+C to exit)...")
    try:
        while True:
            audio_data = mic.read(3200, exception_on_overflow=False)
            conv.append_audio(base64.b64encode(audio_data).decode())
            time.sleep(0.01)
    except KeyboardInterrupt:
        conv.close()
        mic.close()
        callback.out.close()
        pya.terminate()
        print("\nConversation ended")

API reference

Use the same account across all APIs.

Create a voice

Upload audio for cloning and create a custom voice.
  • URL The following URL uses Singapore. For another region, replace ap-southeast-1 in the URL and use an API key from that region. See Regions.
POST https://ap-southeast-1.maas.qwencloudapi.com/api/v1/services/audio/tts/customization
  • Request headers | Parameter | Type | Required | Description | |---|---|---|---| | Authorization | string | Supported | Authentication token in the format Bearer $DASHSCOPE_API_KEY. Replace $DASHSCOPE_API_KEY with your actual API key. | | Content-Type | string | Supported | Media type of the request body. Set to application/json. |
  • Request body The request body includes all parameters. Omit optional fields as needed. Note the distinction between:
model: The voice cloning model. Set to qwen-voice-enrollment.target_model: The omni model that drives the voice. This must match the model used in the subsequent real-time multimodal API call. Otherwise, synthesis fails.
{
    "model": "qwen-voice-enrollment",
    "input": {
        "action": "create",
        "target_model": "qwen3.8-omni-flash-realtime",
        "preferred_name": "guanyu",
        "audio": {
            "data": "https://xxx.wav"
        },
        "text": "Optional. The transcript of the audio in audio.data.",
        "language": "Optional. The language of the audio in audio.data, such as zh."
    },
    "parameters": {
        "voice_clone_mode": "normal",
        "skip_audio_process": false
    }
}
The voice_clone_mode parameter in parameters is required when using qwen3.8-omni series models. If omitted, the system falls back to qwen3.5-omni talker system instruct mode for backward compatibility.
  • Request parameters | Parameter | Type | Default | Required | Description | |---|---|---|---|---| | model | string | - | Supported | The voice cloning model. Set to qwen-voice-enrollment. | | action | string | - | Supported | The action type. Set to create. | | target_model | string | - | Supported | The omni model that drives the voice:
    • qwen3.8-omni-flash-realtime
    • qwen3.5-omni-plus-realtime
    • qwen3.5-omni-flash-realtime
    • qwen3.5-omni-plus
    • qwen3.5-omni-flash
    This must match the omni model used in the subsequent API call. Otherwise, synthesis fails. | | preferred_name | string | - | Supported | A human-readable name for the voice. Allowed characters: digits, letters, underscores. Maximum: 16 characters. > This keyword appears in the final voice name. For example, if the keyword is "guanyu", the resulting voice name is "qwen-omni-vc-guanyu-voice-20250812105009984-838b". | | audio.data | string | - | Supported | The audio for cloning (follow the Recording guide when recording, and make sure the audio meets the Audio requirements). Submit audio data in one of the following ways: 1. Data URL Format: data:{mediatype};base64,{data}
    • {mediatype}: The MIME type
    • WAV: audio/wav
    • MP3: audio/mpeg
    • M4A: audio/mp4
    • {data}: The Base64-encoded string of the audio
    Base64 encoding increases file size. Keep the encoded result under 10 MB.
    • Example: data:audio/wav;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjI5LjEwMAAAAAAAAAAAAAAA//PAxABQ/BXRbMPe4IQAhl9
    See Example 1 below See Example 2 below 2. Audio URL (we recommend uploading your audio to OSS)
    • File size must not exceed 10 MB.
    • The URL must be publicly accessible without authentication.
    | | text | string | - | Unsupported | The transcript that matches the audio in audio.data. When provided, the server validates the audio against the text. If the mismatch is too large, an Audio.PreprocessError is returned. | | language | string | - | Unsupported | The language of the audio in audio.data. Supported values: zh (Chinese), en (English), de (German), it (Italian), pt (Portuguese), es (Spanish), ja (Japanese), ko (Korean), fr (French), ru (Russian), th (Thai), id (Indonesian), ar (Arabic), cs (Czech), da (Danish), nl (Dutch), fi (Finnish), he (Hebrew), hi (Hindi), is (Icelandic), ms (Malay), no (Norwegian), fa (Persian), pl (Polish), sv (Swedish), tl (Tagalog), tr (Turkish), ur (Urdu), vi (Vietnamese). Chinese dialects: Dongbei, Shannxi, Sichuan, Henan, Changsha, Tianjin, Hangzhou, Liaoning, Shenyang, Anshan. Set this to the actual language of the audio used for cloning. | | voice_clone_mode | string | - | Conditionally required | Voice cloning mode. Required when using qwen3.8-omni series models. If omitted, falls back to qwen3.5-omni talker system instruct mode for backward compatibility. Valid values:
    • normal: Standard mode
    • dialect: Dialect mode
    • crosslingual: Cross-lingual mode
    This parameter must be placed in parameters. | | skip_audio_process | boolean | false | Unsupported | Whether to skip audio preprocessing. Default: false. This parameter must be placed in parameters. |
Example 1 (Description):
import base64, pathlib

# input.mp3 is the local audio file for voice cloning. Replace with your own file path. Make sure the file meets the audio requirements.
file_path = pathlib.Path("input.mp3")
base64_str = base64.b64encode(file_path.read_bytes()).decode()
data_uri = f"data:audio/mpeg;base64,{base64_str}"
Example 2 (Description):
import java.nio.file.*;
import java.util.Base64;

public class Main {
    /**
     * filePath is the local audio file for voice cloning. Replace with your own file path. Make sure the file meets the audio requirements.
     */
    public static String toDataUrl(String filePath) throws Exception {
        byte[] bytes = Files.readAllBytes(Paths.get(filePath));
        String encoded = Base64.getEncoder().encodeToString(bytes);
        return "data:audio/mpeg;base64," + encoded;
    }

    // Usage example
    public static void main(String[] args) throws Exception {
        System.out.println(toDataUrl("input.mp3"));
    }
}
  • Response parameters
  {
      "output": {
          "voice": "yourVoice",
          "target_model": "qwen3.8-omni-flash-realtime"
      },
      "usage": {
          "count": 1
      },
      "request_id": "yourRequestId"
  }
Key response parameters:
ParameterTypeDescription
voicestringThe voice name. Use this value directly as the voice parameter in the real-time multimodal API.
target_modelstringThe omni model that drives the voice:
  • qwen3.8-omni-flash-realtime
  • qwen3.5-omni-plus-realtime
  • qwen3.5-omni-flash-realtime
This must match the model used in the subsequent real-time multimodal API call. Otherwise, synthesis fails.
request_idstringRequest ID.
countintegerThe number of "create voice" operations billed for this request. The cost is $ count × 0.01.
When creating a voice, count is always 1.
  • Sample code
model: The voice cloning model. Set to qwen-voice-enrollment.target_model: The omni model that drives the voice. This must match the model used in the subsequent real-time multimodal API call. Otherwise, synthesis fails.
  • curl
  • python
  • java
If you have not set the API key as an environment variable, you must replace $DASHSCOPE_API_KEY in the example with your actual API key.
  # ======= Important =======
  # === Remove these comments before running ===

  # Singapore (ap-southeast-1). For another region, replace ap-southeast-1 in the URL.
  # API keys are region-specific. See https://docs.qwencloud.com/developer-guides/getting-started/regions
  curl --location --request POST 'https://ap-southeast-1.maas.qwencloudapi.com/api/v1/services/audio/tts/customization' \
  --header "Authorization: Bearer $DASHSCOPE_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
      "model": "qwen-voice-enrollment",
      "parameters": {"voice_clone_mode": "normal"},
      "input": {
          "action": "create",
          "target_model": "qwen3.8-omni-flash-realtime",
          "preferred_name": "guanyu",
          "audio": {"data": "https://xxx.wav"}
      }
  }'

List voices

Query your created voices with pagination.
  • URL The following URL uses Singapore. For another region, replace ap-southeast-1 in the URL and use an API key from that region. See Regions.
POST https://ap-southeast-1.maas.qwencloudapi.com/api/v1/services/audio/tts/customization
  • Request headers
  • Request parameters
ParameterTypeRequiredDefaultDescription
actionstring-SupportedThe action type. Set to list.
page_indexinteger0UnsupportedPage number index. Valid values: 0 to 1,000,000.
page_sizeinteger10UnsupportedNumber of items per page. Valid values: 0 to 1,000,000.
  • Response parameters
  {
      "output": {
          "voice_list": [
              {
                  "voice": "yourVoice1",
                  "gmt_create": "2025-08-11 17:59:32",
                  "target_model": "qwen3.5-omni-plus-realtime"
              },
              {
                  "voice": "yourVoice2",
                  "gmt_create": "2025-08-11 17:38:10",
                  "target_model": "qwen3.5-omni-plus-realtime"
              }
          ]
      },
      "usage": {
          "count": 0
      },
      "request_id": "yourRequestId"
  }
Key response parameters:
ParameterTypeDescription
voicestringThe voice name. Use this value directly in the voice parameter of the real-time multimodal API.
gmt_createstringThe time when the voice was created.
target_modelstringThe omni model that drives the voice:
  • qwen3.8-omni-flash-realtime
  • qwen3.5-omni-plus-realtime
  • qwen3.5-omni-flash-realtime
  • qwen3.5-omni-plus
  • qwen3.5-omni-flash
This must match the omni model used in the subsequent API call. Otherwise, synthesis fails.
request_idstringThe request ID.
countintegerThis request is charged based on the actual number of 'Create Voice' operations. The cost for this request is $ count × 0.01 .
Listing voices is free. count is always 0.
  • Sample code
model: The voice cloning model. The value is fixed as qwen-voice-enrollment. Do not modify this value.
  • cURL
  • Python
  • Java
If you have not set the API key as an environment variable, you must replace $DASHSCOPE_API_KEY in the example with your actual API key.
  # ======= Important =======
  # === Remove these comments before running ===

  # Singapore (ap-southeast-1). For another region, replace ap-southeast-1 in the URL.
  # API keys are region-specific. See https://docs.qwencloud.com/developer-guides/getting-started/regions
  curl --location --request POST 'https://ap-southeast-1.maas.qwencloudapi.com/api/v1/services/audio/tts/customization' \
  --header "Authorization: Bearer $DASHSCOPE_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
      "model": "qwen-voice-enrollment",
      "input": {
          "action": "list",
          "page_size": 10,
          "page_index": 0
      }
  }'

Delete a voice

Delete a specific voice and release the corresponding quota.
  • URL The following URL uses Singapore. For another region, replace ap-southeast-1 in the URL and use an API key from that region. See Regions.
POST https://ap-southeast-1.maas.qwencloudapi.com/api/v1/services/audio/tts/customization
  • Request headers
  • Request body Omit optional fields as needed.
model: The voice cloning model. The value is fixed as qwen-voice-enrollment. Do not modify this value.
{
    "model": "qwen-voice-enrollment",
    "input": {
        "action": "delete",
        "voice": "yourVoice"
    }
}
  • Request parameters
ParameterTypeRequiredDefaultDescription
actionstring-SupportedThe action type. Set to delete.
voicestring-SupportedThe voice to delete.
  • Response parameters
  {
      "usage": {
          "count": 0
      },
      "request_id": "yourRequestId"
  }
Key response parameters:
ParameterTypeDescription
countintegerThis request is charged based on the actual number of 'Create Voice' operations. The cost for this request is $ count × 0.01 .
Deleting a voice is free. count is always 0.
  • Sample code
model: The voice cloning model. The value is fixed as qwen-voice-enrollment. Do not modify this value.
  • cURL
  • Python
  • Java
If you have not set the API key as an environment variable, you must replace $DASHSCOPE_API_KEY in the example with your actual API key.
  # ======= Important =======
  # === Remove these comments before running ===

  # Singapore (ap-southeast-1). For another region, replace ap-southeast-1 in the URL.
  # API keys are region-specific. See https://docs.qwencloud.com/developer-guides/getting-started/regions
  curl --location --request POST 'https://ap-southeast-1.maas.qwencloudapi.com/api/v1/services/audio/tts/customization' \
  --header "Authorization: Bearer $DASHSCOPE_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
      "model": "qwen-voice-enrollment",
      "input": {
          "action": "delete",
          "voice": "yourVoice"
      }
  }'

Use in conversation

To use a cloned voice in a conversation, see the Quick start.

Voice quota and auto-cleanup

Total limit: 1,000 voices per account.
No dedicated count endpoint exists. Call the List voices API to count your voices.
Auto-cleanup: Voices unused for one year are automatically deleted.

Billing

Voice cloning and model invocation are billed separately:
  • Voice cloning: billed at $0.01/voice. Failed creations are not billed.
QwenCloud free quota (Singapore only):
  • 1,000 free voice creations within 90 days of activating QwenCloud.
  • Failed creations don't consume the free quota.
  • Deleting a voice doesn't restore the free quota.
  • After the free quota is used up or the 90-day period expires, voice creation is billed at $0.01/voice.
  • Conversation with a cloned voice: Billed by token usage for model invocation. For details, see Model inference pricing.
You are responsible for the ownership and legal use of the voice you provide. Read the Service Agreement.

Recording guide

Recording equipment

Use a noise-cancelling microphone, or record with a phone at close range in a quiet environment.

Recording environment

Location

  • Record in a small enclosed space of 10 square meters or less.
  • Choose a room with sound-absorbing materials such as acoustic foam, carpets, or curtains.
  • Avoid large open halls, conference rooms, or classrooms where reverberation is high.

Noise control

  • Outdoor noise: Close doors and windows to block traffic, construction, and other external sounds.
  • Indoor noise: Turn off air conditioners, fans, and fluorescent light ballasts.
  • Record ambient sound with your phone and play it back at high volume to identify hidden noise sources.

Reverberation control

  • Reverberation causes audio to sound muffled and reduces clarity.
  • Reduce reflections from smooth surfaces: close curtains, open wardrobe doors, and cover desks and shelves with clothing or blankets.
  • Use irregular objects such as bookshelves and upholstered furniture to create diffuse reflections.

Script preparation

  • Match the script to the target use case. For example, use customer service dialog style for a customer service scenario.
  • Make sure the script doesn't contain sensitive or illegal content (such as political, pornographic, or violent material), as this causes cloning to fail.
  • Avoid short phrases (such as "hello" or "yes"). Use complete sentences.
  • Maintain semantic coherence and avoid frequent pauses when reading. At least 3 consecutive seconds without interruption is recommended.
  • You can convey the target emotion (such as friendly or serious), but avoid overly dramatic or theatrical delivery. Keep the tone natural.
Using a typical bedroom as an example:
  1. Close doors and windows to block external noise.
  2. Turn off air conditioners, fans, and other appliances.
  3. Close curtains to reduce glass reflections.
  4. Cover the desk surface with clothing or a blanket to reduce desktop reflections.
  5. Familiarize yourself with the script, set the character's tone, and deliver naturally.
  6. Maintain about 10 cm distance from the recording device to avoid plosive distortion or weak signal.

Error messages

If you encounter errors, see Error codes for troubleshooting.
Voice cloning API reference - QwenCloud