Character voices and seed
With eleven_v3, synthesizing the same sentence with the same voice returns different audio every time.
Both timbre and energy change noticeably.
For a single narration track that hardly matters. But if it is meant to be this character's voice, used over and over, a different person on every call does not work.
This page is about how to stop that.
- AICU registered voices (
elenaand the rest) already have a fixed seed. Just pass the slug invoice - To pin it yourself, set
seedexplicitly. Same seed + same text = same audio - If you changed the seed and the audio did not change, suspect the cache (
force: true) - On
eleven_v4andeleven_v4_turbothere is no seed to pin — and you do not need one. See below
eleven_v4 does not take a seed (and does not need one)
Everything on this page is about eleven_v3 and eleven_multilingual_v2.
The v4 models ignore seed entirely, so we do not send one.
The evidence: synthesise the same line with seed: 55 three times and with seed: 999 once.
On eleven_v3 the different seed changes the audio length (41,794 → 40,541 bytes).
On eleven_v4 the length does not move at all (44,720 bytes for every seed).
Upstream agrees — GET /v1/models reports can_use_style: false for the v4 models.
That sounds like a loss, because the whole point of a pinned seed is to keep a character sounding like that character. Measured, it is not.
We re-auditioned all seven AiCuty voices on 2026-10-01: three takes of one line per voice per model, comparing MFCC distance between takes, and reading that distance as a fraction of the distance between two different characters.
eleven_v3 (seed pinned) | eleven_v4 (no seed) | |
|---|---|---|
| Take-to-take drift, worst voice | 7% of "a different person" | 6% |
| Take-to-take drift, typical | 1–3% | 1–2% |
v4 with no seed is as steady as v3 with a pinned one. Two voices (Mei, Nao) were noticeably steadier on v4.
Two things did change, and they matter if you use several characters together:
- The characters sit closer to each other on v4. The median distance between two different voices drops from 0.114 (v3) to 0.089 — about 22% less room to tell them apart
- Speaking rates converge. The same script ran 6.84–8.44s across the seven voices on v3, and 7.34–7.86s on v4. Good for choruses and timed dialogue; less good for "the fast one and the slow one"
Moving a voice from v3 to v4 also moves it. Six of the seven moved 7–34% of the distance to a different character — the range you would expect from the same person on a different day. Nao Verde moved 106%, i.e. as far as a different character.
We tried swapping the underlying voice for Nao — seven male voices, generated on v4 and measured against the current Nao. The closest candidate still sat at 60% of the distance to a different character, and the current voice at 86%. There is no way back.
Having established that, we picked the new Nao rather than dragging the old one along:
nao now defaults to eleven_v4.
curl -sD- -o nao.mp3 -X POST https://api.aicu.ai/v1/audio/speech \
-H "Authorization: Bearer $AICU_API_KEY" -H "Content-Type: application/json" \
-d '{"input": "やあ、ナオ・ヴェルデだよ。", "voice": "nao"}' | grep -i x-aicu-model
# → x-aicu-model: eleven_v4
If you depend on the previous Nao, ask for eleven_v3 explicitly — we are not
retiring it, and we do not silently override what you asked for. Every other voice
still defaults to eleven_multilingual_v2.
A voice's current model is readable from the character registry:
GET /v1/char/nao returns voice.model (and voice.seed, which is null on v4 —
those models do not take a seed, so we do not pretend there is one).
Which field carries the voice depends on which endpoint you call.
| Endpoint | Voice field | Text field |
|---|---|---|
/v1/audio/speech, /v1/tts/speech (OpenAI-compatible) | voice | input |
/v1/tts/generate (AICU's own) | slug | text |
Sending voice to /v1/tts/generate is not an error. It is silently ignored and you get the
default voice — 200, valid audio, no warning. That is a hard thing to notice.
Measured 2026-09-22, with seed fixed so the output is deterministic:
| Request | Response size (two runs) |
|---|---|
| no voice field | 22,196 / 22,196 bytes |
"voice": "mei" | 22,196 / 22,196 bytes — identical to no voice at all |
"slug": "mei" | 26,794 / 26,794 bytes |
If you batch a long script before noticing, you pay for all of it in the wrong voice. Generate one line first and check that the audio actually changed before you run the volume.
A wrong value does fail properly — {"slug": "no_such_character"} returns
400 {"error":"Unknown character: no_such_character. GET /v1/tts/voices を参照"}.
Only the wrong field name passes silently.
What happens
Send the same request three times and compare the hashes of the returned MP3s.
for i in 1 2 3; do
curl -s -X POST https://api.aicu.ai/v1/audio/speech \
-H "Authorization: Bearer $AICU_API_KEY" -H "Content-Type: application/json" \
-d '{"voice": "mina", "model": "eleven_v3", "input": "こんにちは", "seed": '"$RANDOM"'}' \
| md5sum
done
Scatter the seed and all three hashes differ — and they sound like different people.
Fix seed and all three hashes match.
Default seeds for the AiCuty voices
AICU registered voices ship with a seed we have already picked. Omit seed and that one is used.
from openai import OpenAI
client = OpenAI(base_url="https://api.aicu.ai/v1", api_key="aicu_live_…")
# No need to write a seed. Same voice however many times you call
client.audio.speech.create(
model="eleven_v3", voice="mina", input="こんにちは"
).stream_to_file("mina.mp3")
| slug | Name | Character of the voice | v3 | v2 |
|---|---|---|---|---|
elena | Elena Bloom (エレナ・ブルーム) | Calm and easy to follow; standard-Japanese female narration | 330 | 330 |
mei | Mei Soleil (メイ・ソレイユ) | Bright young girl's voice, energetic and easy even for children to follow | 721 | 721 |
mina | Mina Azure (ミナ・アズール) | Calm, intelligent narration; no listening fatigue over long stretches | 101 | 105 |
nao | Nao Verde (ナオ・ヴェルデ) | Soft voice of a gentle, science-minded young man | 55 | 55 |
saki | Saki Noire (サキ・ノワール) | Whispering voice, gentle with a hint of mystery | 1033 | 1035 |
marsha | Marsha Arancia (マーシャ・アランチャ) | Fresh, energetic and easy-to-listen female voice | 418 | 414 |
luc4 | LuC4 (ルカ/全力肯定彼氏くん) | Pleasant low male voice | 1104 | 1104 |
You can hear the actual voices at AiCuty official voices (Japanese, English and French; Marsha also has Spanish and Italian).
Why v2 and v3 use different values
The same seed delivers a different voice on a different model. A take that sounded good on v3 is not necessarily good on v2, so we re-picked for each.
Mina is 101 on v3 and 105 on v2. Saki is 1033 on v3 and 1035 on v2.
How the values are chosen
We start from the character's birthday, month and day concatenated, then audition nearby values.
Mina was born on October 1, so 101 is the starting point, and from there we listen through 102, 103, 104, 105.
Birthday-derived values are easy to remember, easy to explain, and do not collide between characters.
Pinning your own voice
When you pass a raw ElevenLabs voice_id there is no default seed. Pick one yourself and state it explicitly.
curl -X POST https://api.aicu.ai/v1/audio/speech \
-H "Authorization: Bearer $AICU_API_KEY" -H "Content-Type: application/json" \
-d '{"voice": "pqHfZKP75CvOlQylNhV4", "model": "eleven_v3", "input": "…", "seed": 42}' \
--output out.mp3
There is no single right way to choose, but committing to a value and writing it down matters more than the value itself. The worst outcome is being unable to reproduce why the voice sounds the way it does.
Audition a few candidates before you decide. Even neighbouring seeds can give a very different impression.
When you change the seed and the audio does not change
Suspect the cache.
api.aicu.ai caches on a single key of "same voice × same model × same text × same seed".
Cache hits are not billed (x-aicu-ap-cost: 0).
curl -sS -D - -o /dev/null -X POST https://api.aicu.ai/v1/audio/speech \
-H "Authorization: Bearer $AICU_API_KEY" -H "Content-Type: application/json" \
-d '{"voice": "mina", "model": "eleven_v3", "input": "こんにちは"}' \
| grep -i 'x-aicu-ap-cost\|x-cache-hit'
If x-aicu-ap-cost: 0 comes back, the audio was served from cache.
When you really do need it regenerated, use force: true on /v1/tts/generate.
The old gateway neither forwarded seed upstream nor included it in the cache key.
As a result, changing the seed still returned the previous audio — and since the response was a 200, there was no way to notice.
Both the forwarding and the cache key are fixed now.
Lining several voices up together
If you are stitching audio from several characters, normalize loudness clip by clip before you mix. Levels differ between voices, and concatenating as-is leaves only the loud one standing out.
ffmpeg -i in.mp3 -af "loudnorm=I=-18:TP=-2:LRA=9" -ac 1 -ar 44100 out.mp3
A crossfade of about 0.1 seconds at the joins sounds natural. A plain concatenation chops off the tail of each breath.
ffmpeg -i a.mp3 -i b.mp3 -filter_complex "[0][1]acrossfade=d=0.1:c1=tri:c2=tri" out.mp3
-c copy to concatenate mp3With the concat demuxer plus -c copy, mp3 encoder delay rewinds the timestamps
(non monotonically increasing dts) and the joins break up. Rebuild them with the concat filter instead.
See also
- Audio API — endpoints and handling long text
- Mastering Japanese TTS — fixing readings and controlling prosody on v3
- Character API — per-character usage records and royalty allocation