Text-to-speech API
Turn text into speech through the OpenAI-compatible or native endpoint (MiniMax Speech 2.8 and Doubao Speech), plus Doubao voice cloning.
| Item | Value |
|---|---|
| Base URL | https://api.hop-base.com |
| OpenAI-compatible | POST /v1/audio/speech |
| Native (full parameters, streaming; MiniMax only) | POST /v1/t2a_v2 |
| Voice cloning (Doubao) | POST /v1/voice_clone |
This page covers two speech model families. MiniMax Speech 2.8 belongs to its own group, MiniMax Speech Official, and is what the sections below describe by default. ByteDance Doubao Speech (seed-tts-2.0, seed-audio-1.0) belongs to a separate group and uses only the OpenAI-compatible endpoint; see Doubao Speech. Voice cloning with seed-icl-2.0, in the same group, is covered in Doubao voice cloning. A key reaches these models only if it includes the matching group; you can add the group to an existing chat or video key instead of creating a new one.
Synthesize speech
Both endpoints return the whole clip synchronously. Use the OpenAI-compatible endpoint for drop-in use with OpenAI SDKs; use the native endpoint for the full parameter set and streaming.
MiniMax Speech 2.8 turns text into audio synchronously: the response is the audio itself, with no task to poll. It uses the same Base URL (see the table at the top of this page) and the same Bearer authentication as every other model; there is no separate audio host or credential.
| Model ID | Positioning |
|---|---|
speech-2.8-hd | Highest quality |
speech-2.8-turbo | Faster, lower rate |
IDs are case-sensitive and have no aliases; tts-1 / tts-1-hd are not recognised. Use a key with this group enabled and confirm the IDs with GET /v1/models.
| Endpoint | Best for | Text limit |
|---|---|---|
POST /v1/audio/speech | Drop-in OpenAI SDK use | 4,096 characters |
POST /v1/t2a_v2 | Full parameters and streaming | 10,000 characters |
curl https://api.hop-base.com/v1/audio/speech \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "speech-2.8-turbo",
"input": "Hello, world.",
"voice": "English_expressive_narrator",
"response_format": "mp3"
}' \
--output hello.mp3OpenAI-compatible endpoint
POST /v1/audio/speech accepts the OpenAI request shape and responds with raw audio bytes. The fields are also documented in the API reference.
| Parameter | Required | Type & limits | Default | Description |
|---|---|---|---|---|
model | Required | speech-2.8-hd / speech-2.8-turbo | — | Case-sensitive |
input | Required | string, 1-4,096 characters | — | — |
voice | Optional | a system or cloned voice ID, or an OpenAI voice name | depends on the text, see below | — |
response_format | Optional | mp3 / opus / flac / wav / pcm | mp3 | aac returns 400 |
speed | Optional | number 0.25-4 | 1.0 | Outside the range returns 400 |
- Without
voice, text with Han characters usesChinese (Mandarin)_News_Anchor; other text usesEnglish_expressive_narrator. - OpenAI voice names use the same default voice.
- A
speedinside the range is clamped to 0.5-2. stream_formatis not supported and returns 400.
- Accepted OpenAI voice names:
alloy,ash,ballad,cedar,coral,echo,fable,marin,nova,onyx,sage,shimmer,verse. opusis Ogg at 24 kHz.instructionsis accepted and ignored.- Any other field returns 400
unknown field, including native ones such assample_rate,language_boost, orvoice_setting. Use the native endpoint for those. - The response
Content-Typeisaudio/mpeg,audio/ogg,audio/flac,audio/wav, oraudio/pcmto matchresponse_format.
With the official openai SDKs, only the Base URL and the model / voice values change:
from openai import OpenAI
client = OpenAI(base_url="https://api.hop-base.com/v1", api_key="sk-your-key")
response = client.audio.speech.create(
model="speech-2.8-turbo",
voice="English_expressive_narrator",
input="Hello, world.",
response_format="mp3",
)
response.write_to_file("hello.mp3")
# Long text: write the bytes to disk as they arrive
# instead of buffering the whole clip
with client.audio.speech.with_streaming_response.create(
model="speech-2.8-turbo",
voice="English_expressive_narrator",
input="A longer script goes here.",
) as streamed:
streamed.stream_to_file("long.mp3")Native endpoint
POST /v1/t2a_v2 takes the MiniMax T2A v2 request body as is, and fields the gateway does not know pass through unchanged. The JSON response follows the official structure too, except errors.
data.audio is hex-encoded audio and extra_info carries usage_characters plus the audio metadata. stream: true switches to SSE; see Streaming.
import requests
resp = requests.post(
"https://api.hop-base.com/v1/t2a_v2",
headers={"Authorization": "Bearer sk-your-key"},
json={
"model": "speech-2.8-hd",
"text": "你好,世界",
"voice_setting": {"voice_id": "Chinese (Mandarin)_News_Anchor", "speed": 1.0},
"audio_setting": {"format": "mp3", "sample_rate": 32000},
},
)
body = resp.json()
open("hello.mp3", "wb").write(bytes.fromhex(body["data"]["audio"]))
print(body["extra_info"]["usage_characters"]) # billed charactersOn the native endpoint the gateway does only three things:
- It checks
model,textlength,stream, andoutput_format. - It clamps
voice_setting.speedto the official range. - It caps the body at 1 MiB.
Everything else passes through, and a value outside the official range is rejected by the service with 400. Values and defaults in the tables below are the official ones.
| Parameter | Required | Type & limits | Default | Description |
|---|---|---|---|---|
model | Required | speech-2.8-hd / speech-2.8-turbo | — | — |
text | Required | string, 1-10,000 characters | — | Separate paragraphs with newlines |
stream | Optional | true / false | false | true switches the response to SSE |
stream_options.exclude_aggregated_audio | Optional | true / false | false | true drops the complete audio from the final chunk |
language_boost | Optional | auto, or a language listed below | unset | Strengthens recognition of that language or dialect |
pronunciation_dict.tone | Optional | array of original/replacement strings | — | All rules apply at once |
voice_modify | Optional | see below | — | Passed through, not validated on the gateway side |
output_format | Optional | hex | hex | url returns 400 |
- Inline controls for
textare under "Inline text controls". Officially, streaming is recommended above 3,000 characters. language_boostlanguages:Chinese,Chinese,Yue,English,Arabic,Russian,Spanish,French,Portuguese,German,Turkish,Dutch,Ukrainian,Vietnamese,Indonesian,Japanese,Italian,Korean,Thai,Polish,Romanian,Greek,Czech,Finnish,Hindi,Bulgarian,Danish,Hebrew,Malay,Persian,Slovak,Swedish,Croatian,Filipino,Hungarian,Norwegian,Slovenian,Catalan,Nynorsk,Tamil,Afrikaans.- With
language_boostset toautothe model detects the language; Cantonese voices needChinese,Yue. - A
pronunciation_dict.tonereplacement can be plain text (omg/oh my god) or Japanese kana (東京/トウキョウ). - It can also be pinyin with tone numbers 1-5 in parentheses (
处理/(chu3)(li3)) or IPA in parentheses (resume/(rɪˈzjuːm)). voice_modifytakespitch,intensity, andtimbreas integers -100 to 100.- Official semantics:
pitchdeeper → brighter,intensitystronger → softer,timbrefuller → crisper. voice_modify.sound_effectstakesspacious_echo/auditorium_echo/lofi_telephone/robotic, one effect at a time.- With an effect,
mp3/wav/flacwork when not streaming, and onlymp3when streaming.
| Parameter | Required | Type & limits | Default | Description |
|---|---|---|---|---|
voice_id | Required | a system or cloned voice ID | — | See System voices, Voice cloning |
speed | Optional | 0.5-2 | 1.0 | The gateway clamps values outside the range to it |
vol | Optional | above 0, up to 10 | 1.0 | Volume |
pitch | Optional | integer -12 to 12 | 0 | 0 keeps the original pitch |
emotion | Optional | see below | chosen automatically from the text | Set it only when you need a fixed emotion |
text_normalization | Optional | true / false | false | Chinese / English number reading at slightly higher latency |
latex_read | Optional | true / false | false | Chinese only; wrap formulas in $$ |
emotiontakeshappy/sad/angry/fearful/disgusted/surprised/calm/fluent/whisper.fluentandwhisperare documented for the 2.6 series;whisperis explicitly unsupported on Speech 2.8.latex_readforceslanguage_boosttoChinese.
| Parameter | Required | Type & limits | Default | Description |
|---|---|---|---|---|
format | Optional | mp3 / pcm / flac / wav / opus | mp3 | opus is Ogg/Opus |
sample_rate | Required with opus | 8000 / 16000 / 22050 / 24000 / 32000 / 44100 | not stated officially | — |
bitrate | Optional | 32000 / 64000 / 128000 / 256000 | not stated officially | mp3 only |
channel | Optional | 1 / 2 | 1 | Mono / stereo |
force_cbr | Optional | true / false | false | Constant bitrate; streamed mp3 only |
opusrequires an explicitsample_rate.- No official defaults are stated for
sample_rateandbitrate; the official examples use32000and128000.
Pause
- Write
<#x#>withxin seconds from 0.01 to 99.99, up to two decimals, for exampleHello<#0.5#>world. - Place it between speakable segments; two markers in a row are rejected.
- Each marker bills as 1 character.
Inline pronunciation
- Right after the target word, put pinyin with tone numbers 1-5, IPA, or Cantonese Jyutping with tone numbers 1-6 in half-width parentheses.
- Examples:
This is (he2)平, not (huo4)面.,pronounced (lɪv) as a verb,去街市買啲(sung3)。
Interjections (Speech 2.8 only)
(laughs) (chuckle) (coughs) (clear-throat) (groans) (breath) (pant) (inhale) (exhale) (gasps)
(sniffs) (sighs) (snorts) (burps) (lip-smacking) (humming) (hissing) (emm) (sneezes)System voices
Every voice on MiniMax's official System Voice ID List — 332 voices across 24 languages — works here; pass the voice_id exactly as listed there. The table below is only a starter selection.
Use the voice_id column verbatim — case, spaces, and parentheses included. Name is the official label.
voice_id | Language | Name |
|---|---|---|
English_expressive_narrator | English | Expressive Narrator |
English_radiant_girl | English | Radiant Girl |
English_magnetic_voiced_man | English | Magnetic-voiced Male |
English_compelling_lady1 | English | Compelling Lady |
English_Aussie_Bloke | English | Aussie Bloke |
English_captivating_female1 | English | Captivating Female |
English_Upbeat_Woman | English | Upbeat Woman |
English_Trustworth_Man | English | Trustworthy Man |
Chinese (Mandarin)_Reliable_Executive | Mandarin | Reliable Executive |
Chinese (Mandarin)_News_Anchor | Mandarin | News Anchor |
Chinese (Mandarin)_Unrestrained_Young_Man | Mandarin | Unrestrained Young Man |
Chinese (Mandarin)_Mature_Woman | Mandarin | Mature Woman |
Arrogant_Miss | Mandarin | Arrogant Miss |
Robot_Armor | Mandarin | Robot Armor |
Chinese (Mandarin)_Kind-hearted_Antie | Mandarin | Kind-hearted Antie |
Chinese (Mandarin)_HK_Flight_Attendant | Mandarin | HK Flight Attendant |
Japanese_IntellectualSenior | Japanese | Intellectual Senior |
Japanese_DecisivePrincess | Japanese | Decisive Princess |
Japanese_LoyalKnight | Japanese | Loyal Knight |
Spanish_SereneWoman | Spanish | Serene Woman |
Spanish_MaturePartner | Spanish | Mature Partner |
Spanish_CaptivatingStoryteller | Spanish | Captivating Storyteller |
Cantonese_GentleLady | Cantonese | Gentle Lady |
- Cantonese voices need
language_boost: "Chinese,Yue". - Use a Mandarin voice for Chinese text.
- Verified on 2026-09-16: only two voices on the official list fail; the service rejects them with "voice id not exist".
- Those two are
Cantonese_ProfessionalHost (F)andCantonese_ProfessionalHost (M). POST /v1/get_voicewith{"voice_type": "system"}returns the system voice list.
Streaming
On the native endpoint, stream: true switches the response to SSE. Each data: {...} event carries a hex audio chunk, and the final event has data.status 2 plus extra_info. Read the body line by line, decode each event's chunk, and append it to the file.
By default the final event's data.audio repeats the whole clip, so a naive append doubles the audio. With stream_options.exclude_aggregated_audio: true, the final event carries only extra_info.
import json
import requests
with requests.post(
"https://api.hop-base.com/v1/t2a_v2",
headers={"Authorization": "Bearer sk-your-key"},
json={
"model": "speech-2.8-turbo",
"text": "A long script goes here.",
"stream": True,
"stream_options": {"exclude_aggregated_audio": True},
"voice_setting": {"voice_id": "English_expressive_narrator"},
"audio_setting": {"format": "mp3", "sample_rate": 32000},
},
stream=True,
timeout=(10, 300),
) as resp, open("long.mp3", "wb") as out:
resp.raise_for_status()
for line in resp.iter_lines():
if not line.startswith(b"data:"):
continue
event = json.loads(line[5:])
base = event.get("base_resp") or {}
if base.get("status_code", 0) != 0: # failed mid-stream: not billed, stream ends
raise RuntimeError(base.get("status_msg"))
data = event.get("data") or {}
if data.get("audio"):
out.write(bytes.fromhex(data["audio"]))
if data.get("status") == 2:
print(event["extra_info"]["usage_characters"]) # billed charactersLatency and timeouts
Measured on short text (a sentence or two): the synchronous endpoints return the whole clip in about 1.5-2.4 s, and with stream: true the first audio chunk arrives after roughly 1 s.
Latency grows with text length, so stream anything long (MiniMax recommends streaming above 3,000 characters). For synchronous requests, follow the same client guidance as the other synchronous endpoints on Concurrency, timeouts, and billing: a read timeout of 300 s or more.
Voice cloning
Clone a voice from your own recording, then use it on both endpoints like a system voice. The flow mirrors MiniMax's official voice-cloning API, so official SDK code works after changing the Base URL and key. The key's group must include the MiniMax Speech models.
Clone only your own voice, or one whose speaker has given you explicit written permission. You are responsible for lawful use, including deep-synthesis and likeness rules.
1. Upload the audio
POST /v1/files/upload takes multipart/form-data with exactly these two fields; anything else returns 400.
| Parameter | Required | Type & limits | Default | Description |
|---|---|---|---|---|
purpose | Required | voice_clone / prompt_audio | — | See below |
file | Required | one file named .mp3 / .m4a / .wav, ≤20 MB | — | Format and size are checked at upload |
voice_cloneis the recording to clone;prompt_audiois an optional example clip.- Duration:
voice_clone10 s-5 min,prompt_audiounder 8 s. - Duration is checked when you clone.
curl https://api.hop-base.com/v1/files/upload \
-H "Authorization: Bearer sk-your-key" \
-F purpose=voice_clone \
-F file=@my_voice.mp3
# → {"file":{"file_id":123456789,"bytes":…,"created_at":…,"filename":"my_voice.mp3","purpose":"voice_clone"},"base_resp":{…}}2. Clone the voice
POST /v1/voice_clone takes the file_id and a voice_id you choose. Add text and model to get a preview clip.
curl https://api.hop-base.com/v1/voice_clone \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{
"file_id": 123456789,
"voice_id": "MyNarrator01",
"text": "Hello, this is my cloned voice.",
"model": "speech-2.8-hd",
"need_noise_reduction": true
}'
# response: input_sensitive, input_sensitive_type, demo_audio (preview download link on api.hop-base.com, valid 24 hours; empty without text), extra_info.usage_characters (billed preview characters), base_resp| Parameter | Required | Type & limits | Default | Description |
|---|---|---|---|---|
file_id | Required | integer from step 1 | — | Single-use |
voice_id | Required | 8-256 characters, rules below | — | Unique within your account |
clone_prompt | Optional | object, see below | — | Example clip |
text | Optional | preview script, ≤1,000 characters | — | Returns a preview clip, billed as speech characters |
model | Required with text | speech-2.8-hd / speech-2.8-turbo | — | — |
language_boost | Optional | same values as the native endpoint | — | — |
text_validation | Optional | expected transcript, ≤200 characters | — | Speech-recognition check of the source audio |
accuracy | Optional | number 0-1 | 0.7 | Pass threshold for text_validation |
need_noise_reduction | Optional | boolean | false | — |
need_volume_normalization | Optional | boolean | false | — |
aigc_watermark | Optional | boolean | false | — |
voice_iduses letters, digits,-, and_; it starts with a letter and does not end with-or_.clone_promptis{"prompt_audio": <file_id>, "prompt_text": "…"}, and both keys are required.prompt_textis the clip's exact transcript, ending with punctuation.
Use the cloned voice
Pass your voice_id as voice on /v1/audio/speech, or as voice_setting.voice_id (also timber_weights[].voice_id) on /v1/t2a_v2.
curl https://api.hop-base.com/v1/audio/speech \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{"model": "speech-2.8-turbo", "input": "Hello again.", "voice": "MyNarrator01"}' \
--output cloned.mp3Lifecycle
- Uploaded files are single-use: a successful clone deletes both the source and prompt files, so upload again to clone again.
- A failed clone keeps the files for a retry.
- Unused files are deleted 24 hours after upload. Deleting a file never affects voices already cloned.
- A voice not used for synthesis within 7 days of cloning is deleted, and no clone fee is charged for it.
- Voices are private to your account (on an enterprise account, shared by the member keys under the owner); other accounts cannot use them even if they know the
voice_id.
POST /v1/get_voicelists your voices (voice_id,description,created_time).POST /v1/delete_voicedeletes a voice and frees its name.GET /v1/files/list/GET /v1/files/retrieve/POST /v1/files/deletemanage uploads not yet used.- Uploaded audio cannot be downloaded.
# List your cloned voices ("system" = system voices, "all" = both; "voice_generation" is always empty)
curl https://api.hop-base.com/v1/get_voice \
-H "Authorization: Bearer sk-your-key" -H "Content-Type: application/json" \
-d '{"voice_type": "voice_cloning"}'
# Delete a cloned voice
curl https://api.hop-base.com/v1/delete_voice \
-H "Authorization: Bearer sk-your-key" -H "Content-Type: application/json" \
-d '{"voice_type": "voice_cloning", "voice_id": "MyNarrator01"}'
# Uploads not yet used: list, retrieve, delete
curl "https://api.hop-base.com/v1/files/list?purpose=voice_clone" -H "Authorization: Bearer sk-your-key"
curl "https://api.hop-base.com/v1/files/retrieve?file_id=123456789" -H "Authorization: Bearer sk-your-key"
curl https://api.hop-base.com/v1/files/delete \
-H "Authorization: Bearer sk-your-key" -H "Content-Type: application/json" \
-d '{"file_id": 123456789, "purpose": "voice_clone"}'Billing
Speech is billed per billed character. The authoritative count is extra_info.usage_characters in the native response; the OpenAI-compatible endpoint returns audio only, so read the same number as tts_characters in the usage record in the console.
Every Unicode character counts 1, and every Han (CJK) character counts 1 more, so a Han character counts 2. Punctuation, spaces, emoji, and pause markers such as <#0.5#> count 1 each.
Hello, world.→ 13 billed characters你好,世界→ 5 characters, 4 of them Han → 9 billed characters
A request rejected with 400 or failed during synthesis is not billed, and a stream that ends before the final event is not billed. Official list prices per million characters are on the pricing page; your group's rate is in the signed-in model catalog.
Clone fee
Uploading and cloning are free. The clone fee is charged once, with the voice's first successful synthesis, and appears as an extra voice_clone_activation item in that synthesis's usage record.
The preview text is billed only as speech characters and does not count as first use; failed requests are not billed. The fee is on the pricing page.
Errors and tips
Errors on both endpoints are a non-200 status with {"error":{…}}, not base_resp. The gateway's own checks (model, text, format, speed, unknown fields) carry no code; rely on message.
| Status | Cause | What to do |
|---|---|---|
| 400 | A value out of range, or stream_format / output_format: "url" | Fix the field named in message; do not retry as is |
| 400 | opus without sample_rate | Pass sample_rate explicitly |
| 400 | Unknown voice ID, or a cloned voice that was deleted or expired | Use an ID from System voices verbatim, or clone again |
| 400 | Text over the limit (4,096 / 10,000 characters) | Split the script and send it in parts |
| 400 | Unsupported format, such as response_format: "aac" | Use mp3 / opus / flac / wav / pcm |
| 400 | Voice cloning: file, voice name, duration, or check problems | Fix as noted in the messages below |
| 402 | Account balance or key quota exhausted | Top up or raise the key quota; do not retry in a loop |
| 404 | Model not in the key's group | Confirm with GET /v1/models |
| 429 | Rate or concurrency limit | Retry after Retry-After and lower your concurrency |
| 503 | No service available right now | Back off and retry (2 s, 5 s, 15 s, up to three attempts) |
Voice-cloning 400s cover a used or expired file_id, a voice_id already taken, audio duration out of range, or a failed text_validation. Common messages and fixes:
400 voice id not exist unknown voice ID, or a deleted / expired cloned voice
400 file_id ... does not exist; uploaded files are single-use and expire 24 hours after upload upload again
400 voice_id ... already exists pick another name or delete the old voice
400 (cloning) audio duration out of range source 10 s-5 min, prompt under 8 s
400 (cloning) speech-recognition check failed only with text_validation: fix the transcript or lower accuracy
503 voice cloning is temporarily unavailable retry later
400 sensitive content / >10% invalid chars rewrite or clean the text
400 invalid request parameters; check text, voice_setting and audio_setting (opus requires an explicit sample_rate)
413 request body exceeds the 1048576 byte limit (got N bytes) body over 1 MiB
502 response_size_limit_exceeded (audio over 64 MiB) use stream: true, shorter text or a lower bitrate
stream failing midway: a last data: {"base_resp":{...}} event, then the stream closes; not billedDoubao Speech
The two ByteDance Doubao Speech models belong to their own group, Doubao Speech Cloud. A key reaches them only if it includes this group; you can add it to an existing key. They use only the OpenAI-compatible POST /v1/audio/speech endpoint, return the whole clip synchronously, and do not stream. The native /v1/t2a_v2 endpoint does not support these two models and returns 400, pointing you to /v1/audio/speech.
| Model ID | Positioning | Official price |
|---|---|---|
seed-tts-2.0 | Doubao Seed TTS 2.0 (Doubao-Seed-TTS-2.0), text to speech | ¥3 / 10,000 characters |
seed-audio-1.0 | Doubao Seed Audio 1.0, generates audio with tone and sound effects from a scene description | ¥1 / minute (by generated audio length) |
seed-icl-2.0 | Doubao Voice Cloning 2.0, synthesizes with a voice you cloned; see Doubao voice cloning | ¥3 / 10,000 characters |
curl https://api.hop-base.com/v1/audio/speech \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "seed-tts-2.0",
"input": "你好,欢迎使用豆包语音合成。",
"voice": "zh_female_vv_uranus_bigtts"
}' \
-o out.mp3With the official openai SDK, change only the Base URL and the model / voice values. For seed-audio-1.0, write the scene and the lines in input:
from openai import OpenAI
client = OpenAI(base_url="https://api.hop-base.com/v1", api_key="sk-your-key")
response = client.audio.speech.create(
model="seed-tts-2.0",
voice="en_female_dacey_uranus_bigtts",
input="Hello, welcome to Doubao text to speech.",
)
response.write_to_file("out.mp3")
# Scene audio: write "scene description + lines" in input; generation takes about 10-30 s
scene = client.audio.speech.create(
model="seed-audio-1.0",
voice="alloy", # the SDK requires voice; an OpenAI voice name counts as unset, so the description decides the voice
input="先是一声手机震动的声音,随后一名男子(中青年,嗓音低沉)用严肃语气说道:“钟sir,你是什么时候被收买的?”",
timeout=300,
)
scene.write_to_file("scene.mp3")Doubao Speech parameters
| Parameter | Required | Type and limits | Default | Notes |
|---|---|---|---|---|
model | Required | seed-tts-2.0 / seed-audio-1.0 | — | Case-sensitive |
input | Required | String; at most 5,000 characters for seed-tts-2.0 and 3,000 for seed-audio-1.0 | — | Counted in characters: each Chinese character, letter, or punctuation mark counts as 1; longer input returns 400 input_too_long |
voice | Optional | A Doubao voice ID or an OpenAI voice name | See below | An unknown voice returns 400 unknown_voice |
response_format | Optional | seed-tts-2.0: mp3 / opus / aac / wav / pcm; seed-audio-1.0: mp3 / wav / pcm / flac | mp3 | Formats not listed return 400 |
speed | Optional | 0.5 to 2.0 | 1.0 | Values out of range return 400 |
instructions | Optional | String | — | Accepted but has no effect; put tone and emotion in input |
seed-tts-2.0: withoutvoice, or with an OpenAI standard voice name such asalloy, the default voice follows the text's language:zh_female_vv_uranus_bigttsfor Chinese anden_female_dacey_uranus_bigttsfor other languages. You can pick other 2.0 voices (IDs ending in_uranus_bigtts, such aszh_male_m191_uranus_bigtts).seed-audio-1.0: write "scene description + lines" ininput, as in the example above. Withoutvoice, or with an OpenAI standard voice name, the description decides the voice; you can also set a Doubao voice ID. If the description is too long, the audio it calls for can exceed the model's maximum length and the request returns 400duration_out_of_range; shorten it and send again.- Output:
seed-tts-2.0is 24 kHz mono (pcmis raw 24 kHz 16-bit mono, the same as OpenAI);seed-audio-1.0is 48 kHz stereo. - Streaming is not supported;
stream_formatreturns 400. If the voice does not match the text's language (for example an English voice reading Chinese-only text) and no audio is produced, the request returns 400no_audio. - For longer text, split it on the client within the limit, request each chunk, and join the audio.
Latency and billing
seed-tts-2.0: about 1-3 s for a short sentence, about 70 s for 2,000 characters.seed-audio-1.0: about 10-30 s.- Both return only after the whole clip is generated. Set the client read timeout as for other synchronous endpoints in Concurrency, timeouts, and billing: at least 300 seconds.
seed-tts-2.0is billed by input characters andseed-audio-1.0by the length of the generated audio; official prices are in the model table above, and the actual rate for your group is shown in the model catalog after you sign in. Requests rejected with 400 or that fail during generation are not billed.
Any value outside the table above returns 400; fix the request as the error says instead of retrying it unchanged. Calling /v1/t2a_v2 also returns 400. 402, 404, 429, and 503 mean the same as in Errors and tips above.
Doubao voice cloning
Clone a custom voice from a recording of your own, then synthesize speech with it. The model is seed-icl-2.0 (Doubao Voice Cloning 2.0). It sits in the same group as Doubao Speech, Doubao Speech Cloud, and a key reaches it only if it includes this group. There are three steps: send the recording to POST /v1/voice_clone to get a voice_id (starting with hbv_), pass it as voice to POST /v1/audio/speech, and list or delete the voice when you are done.
The request format differs from MiniMax voice cloning above: the recording goes straight into the JSON as base64, with no separate file upload, and all three cloning endpoints require "model": "seed-icl-2.0" in the body. If you use both MiniMax voice cloning and Doubao voice cloning, use a separate key for each.
Clone only your own voice, or one whose speaker has given you explicit written permission. You are responsible for lawful use, including deep-synthesis and likeness rules.
Beta allowance: During the beta, each account can keep one cloned voice. To switch voices, delete the old one and clone again, or retrain it by passing its voice_id. Cloned-voice capacity is limited; when it is full the request returns 503 voice_capacity_full, so try again later.
1. Clone a voice
POST /v1/voice_clone takes a JSON body of up to 16 MiB.
| Parameter | Required | Type and limits | Default | Notes |
|---|---|---|---|---|
model | Required | seed-icl-2.0 | — | Case-sensitive |
audio | Required | The recording as base64 or data:audio/…;base64,…; at most 10 MB once decoded | — | wav / mp3 / ogg / m4a / aac / pcm |
format | Optional | wav / mp3 / ogg / m4a / aac / pcm | Detected | If omitted, taken from the data URI type or the file header; pcm must be 24 kHz mono and always needs an explicit format |
text | Optional | String, at most 1,000 characters | — | A verbatim transcript of the recording. If it differs a lot from the audio, cloning fails with 400 audio_text_mismatch; leave it out if unsure |
language | Optional | A language code, see below | — | Language of the recording |
demo_text | Optional | 4-300 characters | — | The response then includes a preview of the new voice reading this text, billed at the synthesis price |
denoise | Optional | Boolean | — | Denoise the recording before cloning |
voice_id | Optional | One of your existing hbv_… IDs | — | Retrains that voice in place: the old voice is replaced, the voice_id stays the same, and one training attempt is used. Omit it to create a new voice |
languageacceptszh,en,ja,es,id,pt,de,fr,ko,it,th,vi,ru,fil,ms,ar,es-mx,pt-br,pl,tr,sv.- Cloning finishes synchronously: in our test, a 17-second recording returned in about 33 seconds. Set the client read timeout to 300 s or more.
# Put the recording into the request body as base64 (jq 1.6+)
jq -n --rawfile audio <(base64 < my_voice.wav | tr -d '\n') \
'{model: "seed-icl-2.0", audio: $audio, language: "en", demo_text: "Hello, this is my cloned voice."}' > clone.json
curl https://api.hop-base.com/v1/voice_clone \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
--max-time 300 \
-d @clone.json{
"voice_id": "hbv_k3m9x2p7q8r4t6w1y5z0a2b4",
"status": "ready",
"language": "en",
"remaining_trainings": 14,
"created_at": 1792051200,
"demo_audio": "<base64 audio>",
"demo_audio_format": "wav"
}| Field | Notes |
|---|---|
voice_id | The cloned voice ID; pass it as voice when synthesizing |
status | ready can be used; training is still in progress, so check again later with POST /v1/get_voice; failed means training failed, and you can retrain it by passing the voice_id or delete it |
language | Language of the recording |
remaining_trainings | Training attempts left for this voice (15 per voice); when they run out, delete the voice and clone again |
created_at | Creation time, Unix seconds |
demo_audio / demo_audio_format | Only when demo_text was sent: the preview audio as base64 and its format |
2. Synthesize with the cloned voice
Call POST /v1/audio/speech with model set to seed-icl-2.0 and voice set to your voice_id. Everything else (parameters, formats, and limits) matches seed-tts-2.0 (see Doubao Speech parameters): input up to 5,000 characters, response_format one of mp3 / opus / aac / wav / pcm, speed 0.5-2.0, 24 kHz mono output, the whole clip returned synchronously, and no streaming.
curl https://api.hop-base.com/v1/audio/speech \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{"model": "seed-icl-2.0", "input": "The meeting has moved to 3 pm.", "voice": "hbv_k3m9x2p7q8r4t6w1y5z0a2b4"}' \
-o cloned.mp3seed-icl-2.0only works with a voice you cloned: omittingvoice, or passing a system voice or an OpenAI voice name, returns 400voice_required.- A cloned voice works only with
seed-icl-2.0; any other model returns 400unknown_voice. - Cloned voices are private to your account (on an enterprise account, shared by the member keys under the owner); other accounts cannot use them even if they know the
voice_id.
3. List and delete
Both endpoints are free, and the body also needs model.
# List your cloned voices (add voice_id to fetch just that one)
curl https://api.hop-base.com/v1/get_voice \
-H "Authorization: Bearer sk-your-key" -H "Content-Type: application/json" \
-d '{"model": "seed-icl-2.0"}'
# → {"voices":[{"voice_id":"hbv_k3m9x2p7q8r4t6w1y5z0a2b4","status":"ready","language":"en","remaining_trainings":14,"created_at":1792051200}]}
# Delete a cloned voice
curl https://api.hop-base.com/v1/delete_voice \
-H "Authorization: Bearer sk-your-key" -H "Content-Type: application/json" \
-d '{"model": "seed-icl-2.0", "voice_id": "hbv_k3m9x2p7q8r4t6w1y5z0a2b4"}'
# → {"voice_id":"hbv_k3m9x2p7q8r4t6w1y5z0a2b4","deleted":true}With voice_id, get_voice returns that voice's object directly (same fields as the clone response, without preview audio). After deletion the voice_id can no longer be used, and the allowance is freed right away.
Voice cloning billing
- Synthesis with a cloned voice is billed by input characters at the official price of ¥3 per 10,000 characters, the same as
seed-tts-2.0. - Preview:
demo_textis billed per character at the same price. - Cloning itself, listing, and deleting are free; there is currently no cloning fee. Failed requests are not billed.
The actual rate for your group is shown in the model catalog after you sign in.
Voice cloning errors
Errors use the same format as other endpoints: a non-200 status plus {"error":{"message","type","code"}}. Parameter validation errors carry no code; read the message.
| Status | code | Cause | What to do |
|---|---|---|---|
| 400 | — | model missing or not seed-icl-2.0; audio is not valid base64, exceeds 10 MB, or has an unsupported or undetectable format; text too long, unsupported language, or demo_text of the wrong length; voice_id does not start with hbv_ | Fix what the message points to |
| 400 | voice_limit_reached | The account already has one cloned voice | Delete it, or retrain it by passing its voice_id |
| 400 | audio_text_mismatch | The recording does not match text | Correct text or leave it out |
| 400 | invalid_audio | The recording was rejected, for example its duration, sample rate, or encoding | Use a clean recording |
| 400 | no_trainings_left | This voice has no training attempts left | Delete it and clone again |
| 400 | unknown_voice | The voice_id does not exist, was deleted, or belongs to another account; or a cloned voice was used with a model other than seed-icl-2.0 | Check with POST /v1/get_voice |
| 400 | voice_required | Synthesis with seed-icl-2.0 without a cloned voice | Clone first, then pass the voice_id as voice |
| 400 | voice_not_ready | The voice is still being created or trained | Try again shortly |
| 400 | voice_training_failed | Training failed | Retrain by passing the voice_id, or delete it |
| 409 | voice_clone_in_progress | Another clone or delete request from the same account is still running | Send again after it finishes |
| 413 | — | The request body exceeds 16 MiB | Compress or shorten the recording |
| 503 | voice_capacity_full | Cloned-voice capacity is temporarily full | Try again later |
| 503 | voice_unavailable, voice_registry_unavailable | Temporarily unavailable | Retry with backoff |
Next steps
- Available models — every model ID and its group
- Create speech API reference
- MiniMax Hailuo H3 video API
- Error codes and retries
- Concurrency, timeouts, and billing