Skip to content

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.

ItemValue
Base URLhttps://api.hop-base.com
OpenAI-compatiblePOST /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 IDPositioning
speech-2.8-hdHighest quality
speech-2.8-turboFaster, 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.

EndpointBest forText limit
POST /v1/audio/speechDrop-in OpenAI SDK use4,096 characters
POST /v1/t2a_v2Full parameters and streaming10,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.mp3

OpenAI-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.

ParameterRequiredType & limitsDefaultDescription
modelRequiredspeech-2.8-hd / speech-2.8-turbo—Case-sensitive
inputRequiredstring, 1-4,096 characters——
voiceOptionala system or cloned voice ID, or an OpenAI voice namedepends on the text, see below—
response_formatOptionalmp3 / opus / flac / wav / pcmmp3aac returns 400
speedOptionalnumber 0.25-41.0Outside the range returns 400
  • Without voice, text with Han characters uses Chinese (Mandarin)_News_Anchor; other text uses English_expressive_narrator.
  • OpenAI voice names use the same default voice.
  • A speed inside the range is clamped to 0.5-2.
  • stream_format is not supported and returns 400.

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 characters

On the native endpoint the gateway does only three things:

  • It checks model, text length, stream, and output_format.
  • It clamps voice_setting.speed to 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.

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_idLanguageName
English_expressive_narratorEnglishExpressive Narrator
English_radiant_girlEnglishRadiant Girl
English_magnetic_voiced_manEnglishMagnetic-voiced Male
English_compelling_lady1EnglishCompelling Lady
English_Aussie_BlokeEnglishAussie Bloke
English_captivating_female1EnglishCaptivating Female
English_Upbeat_WomanEnglishUpbeat Woman
English_Trustworth_ManEnglishTrustworthy Man
Chinese (Mandarin)_Reliable_ExecutiveMandarinReliable Executive
Chinese (Mandarin)_News_AnchorMandarinNews Anchor
Chinese (Mandarin)_Unrestrained_Young_ManMandarinUnrestrained Young Man
Chinese (Mandarin)_Mature_WomanMandarinMature Woman
Arrogant_MissMandarinArrogant Miss
Robot_ArmorMandarinRobot Armor
Chinese (Mandarin)_Kind-hearted_AntieMandarinKind-hearted Antie
Chinese (Mandarin)_HK_Flight_AttendantMandarinHK Flight Attendant
Japanese_IntellectualSeniorJapaneseIntellectual Senior
Japanese_DecisivePrincessJapaneseDecisive Princess
Japanese_LoyalKnightJapaneseLoyal Knight
Spanish_SereneWomanSpanishSerene Woman
Spanish_MaturePartnerSpanishMature Partner
Spanish_CaptivatingStorytellerSpanishCaptivating Storyteller
Cantonese_GentleLadyCantoneseGentle 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) and Cantonese_ProfessionalHost (M).
  • POST /v1/get_voice with {"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.

Set exclude_aggregated_audio to true.

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 characters

Latency 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.

Only clone voices you have the right to use.

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.

ParameterRequiredType & limitsDefaultDescription
purposeRequiredvoice_clone / prompt_audio—See below
fileRequiredone file named .mp3 / .m4a / .wav, ≤20 MB—Format and size are checked at upload
  • voice_clone is the recording to clone; prompt_audio is an optional example clip.
  • Duration: voice_clone 10 s-5 min, prompt_audio under 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
ParameterRequiredType & limitsDefaultDescription
file_idRequiredinteger from step 1—Single-use
voice_idRequired8-256 characters, rules below—Unique within your account
clone_promptOptionalobject, see below—Example clip
textOptionalpreview script, ≤1,000 characters—Returns a preview clip, billed as speech characters
modelRequired with textspeech-2.8-hd / speech-2.8-turbo——
language_boostOptionalsame values as the native endpoint——
text_validationOptionalexpected transcript, ≤200 characters—Speech-recognition check of the source audio
accuracyOptionalnumber 0-10.7Pass threshold for text_validation
need_noise_reductionOptionalbooleanfalse—
need_volume_normalizationOptionalbooleanfalse—
aigc_watermarkOptionalbooleanfalse—
  • voice_id uses letters, digits, -, and _; it starts with a letter and does not end with - or _.
  • clone_prompt is {"prompt_audio": <file_id>, "prompt_text": "…"}, and both keys are required.
  • prompt_text is 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.mp3

Lifecycle

  • 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.

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.

StatusCauseWhat to do
400A value out of range, or stream_format / output_format: "url"Fix the field named in message; do not retry as is
400opus without sample_ratePass sample_rate explicitly
400Unknown voice ID, or a cloned voice that was deleted or expiredUse an ID from System voices verbatim, or clone again
400Text over the limit (4,096 / 10,000 characters)Split the script and send it in parts
400Unsupported format, such as response_format: "aac"Use mp3 / opus / flac / wav / pcm
400Voice cloning: file, voice name, duration, or check problemsFix as noted in the messages below
402Account balance or key quota exhaustedTop up or raise the key quota; do not retry in a loop
404Model not in the key's groupConfirm with GET /v1/models
429Rate or concurrency limitRetry after Retry-After and lower your concurrency
503No service available right nowBack 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 billed

Doubao 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 IDPositioningOfficial price
seed-tts-2.0Doubao Seed TTS 2.0 (Doubao-Seed-TTS-2.0), text to speech¥3 / 10,000 characters
seed-audio-1.0Doubao Seed Audio 1.0, generates audio with tone and sound effects from a scene description¥1 / minute (by generated audio length)
seed-icl-2.0Doubao 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.mp3

With 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

ParameterRequiredType and limitsDefaultNotes
modelRequiredseed-tts-2.0 / seed-audio-1.0—Case-sensitive
inputRequiredString; 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
voiceOptionalA Doubao voice ID or an OpenAI voice nameSee belowAn unknown voice returns 400 unknown_voice
response_formatOptionalseed-tts-2.0: mp3 / opus / aac / wav / pcm; seed-audio-1.0: mp3 / wav / pcm / flacmp3Formats not listed return 400
speedOptional0.5 to 2.01.0Values out of range return 400
instructionsOptionalString—Accepted but has no effect; put tone and emotion in input
  • seed-tts-2.0: without voice, or with an OpenAI standard voice name such as alloy, the default voice follows the text's language: zh_female_vv_uranus_bigtts for Chinese and en_female_dacey_uranus_bigtts for other languages. You can pick other 2.0 voices (IDs ending in _uranus_bigtts, such as zh_male_m191_uranus_bigtts).
  • seed-audio-1.0: write "scene description + lines" in input, as in the example above. Without voice, 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 400 duration_out_of_range; shorten it and send again.
  • Output: seed-tts-2.0 is 24 kHz mono (pcm is raw 24 kHz 16-bit mono, the same as OpenAI); seed-audio-1.0 is 48 kHz stereo.
  • Streaming is not supported; stream_format returns 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 400 no_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.0 is billed by input characters and seed-audio-1.0 by 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.

Only clone voices you have the right to use.

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.

ParameterRequiredType and limitsDefaultNotes
modelRequiredseed-icl-2.0—Case-sensitive
audioRequiredThe recording as base64 or data:audio/…;base64,…; at most 10 MB once decoded—wav / mp3 / ogg / m4a / aac / pcm
formatOptionalwav / mp3 / ogg / m4a / aac / pcmDetectedIf omitted, taken from the data URI type or the file header; pcm must be 24 kHz mono and always needs an explicit format
textOptionalString, 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
languageOptionalA language code, see below—Language of the recording
demo_textOptional4-300 characters—The response then includes a preview of the new voice reading this text, billed at the synthesis price
denoiseOptionalBoolean—Denoise the recording before cloning
voice_idOptionalOne 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
  • language accepts zh, 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"
}
FieldNotes
voice_idThe cloned voice ID; pass it as voice when synthesizing
statusready 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
languageLanguage of the recording
remaining_trainingsTraining attempts left for this voice (15 per voice); when they run out, delete the voice and clone again
created_atCreation time, Unix seconds
demo_audio / demo_audio_formatOnly 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.mp3
  • seed-icl-2.0 only works with a voice you cloned: omitting voice, or passing a system voice or an OpenAI voice name, returns 400 voice_required.
  • A cloned voice works only with seed-icl-2.0; any other model returns 400 unknown_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_text is 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.

StatuscodeCauseWhat 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
400voice_limit_reachedThe account already has one cloned voiceDelete it, or retrain it by passing its voice_id
400audio_text_mismatchThe recording does not match textCorrect text or leave it out
400invalid_audioThe recording was rejected, for example its duration, sample rate, or encodingUse a clean recording
400no_trainings_leftThis voice has no training attempts leftDelete it and clone again
400unknown_voiceThe 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.0Check with POST /v1/get_voice
400voice_requiredSynthesis with seed-icl-2.0 without a cloned voiceClone first, then pass the voice_id as voice
400voice_not_readyThe voice is still being created or trainedTry again shortly
400voice_training_failedTraining failedRetrain by passing the voice_id, or delete it
409voice_clone_in_progressAnother clone or delete request from the same account is still runningSend again after it finishes
413—The request body exceeds 16 MiBCompress or shorten the recording
503voice_capacity_fullCloned-voice capacity is temporarily fullTry again later
503voice_unavailable, voice_registry_unavailableTemporarily unavailableRetry with backoff

Next steps