Voice design
Conjure a fresh voice from a natural-language description. Hakim Mulhim v3 synthesises a sample from your prompt; you can listen, iterate, and (when the timbre lands) save it to your voice library so subsequent speech calls reuse it exactly.
curl https://api.tryhakim.ai/v1/audio/voices/design \
-H "Authorization: Bearer $HAKIM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "warm raspy elderly woman, low-pitched, magnetic, slight breathy tremor",
"sample_text": "Welcome. This is a quick sample of the voice you described.",
"save_as_voice": { "name": "Broadcaster Demo" }
}' \
| jq '.voice.id'
# "voi_01HX3K..."
The response is a JSON envelope:
{
"sample_b64": "UklGRoYf...",
"sample_mime": "audio/wav",
"sample_rate": 48000,
"voice": {
"id": "voi_01HX3KZJ8WD6J9KVWBQJ3WJK4G",
"slug": "broadcaster-demo",
"name": "Broadcaster Demo",
"kind": "designed",
"preview_url": "https://api.tryhakim.ai/v1/audio/voices/.../preview.mp3?...",
"created_at": "2026-05-10T11:30:00Z"
}
}
sample_b64 is the rendered preview as base64 WAV · prefix with data:audio/wav;base64,
for direct <audio> playback. The voice block is set only when save_as_voice was
present in the request and the persistence step succeeded.
Prompt structure
Three axes ladder up well · combine all three for the best results:
| Axis | What it controls | Example |
|---|---|---|
| Identity | gender, age, role | "middle-aged male broadcaster" |
| Texture | pitch, timbre, grain | "low-pitched, magnetic, slight rasp" |
| Scenario | emotion, pacing, context | "passionate news bulletin" |
A description like:
"middle-aged male broadcaster, low-pitched and magnetic with a slight rasp, passionate news bulletin"
reads end-to-end as one prompt to the model · there's no required syntax. Both English and Arabic prompts are supported.
Iterate, then save
Each generation is unique by design. Re-running the same prompt yields a similar but
distinct voice · if the first one isn't right, regenerate. When you hear what you want,
add save_as_voice to pin it down:
curl https://api.tryhakim.ai/v1/audio/voices/design \
-H "Authorization: Bearer $HAKIM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "...",
"sample_text": "...",
"save_as_voice": {
"name": "My Designed Voice",
"gender": "neutral"
}
}'
The persisted voice is a regular Voice row with kind: "designed". Use its id on every
subsequent POST /v1/audio/speech call:
curl https://api.tryhakim.ai/v1/audio/speech \
-H "Authorization: Bearer $HAKIM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "hakim-v3",
"input": "[breath] Welcome back. Today we have something special.",
"voice": "voi_01HX3KZJ8WD6J9KVWBQJ3WJK4G"
}' \
--output episode-intro.mp3
Tier gating
The endpoint is part of the Mulhim capability set. Calls with model set to any other
tier return:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": {
"type": "invalid_request_error",
"code": "model_does_not_support_voice_prompt",
"message": "Voice design requires hakim-v3.",
"param": "model"
}
}
Unlike the speech endpoint (which gracefully strips an unsupported voice_prompt), design
rejects because the prompt IS the request · there's no fallback that makes sense.
Pitfalls
- The model is creative, not pixel-perfect. Expect timbre variation between runs even with identical prompts. Save when you hear what you want; don't expect deterministic reruns.
- Public-figure impersonation prompts are flagged for review. Designed voices flow
through the same moderation pipeline as cloned voices. Prompts that read as "voice that
sounds like <real person>" get held in
pending_manual_reviewrather than auto-cleared. - Description length matters. Sub-50-character prompts often under-constrain the model and produce generic output. 100–250 chars hits the sweet spot.
- The base voice doesn't carry over. Voice design generates a fresh timbre from
scratch · there's no
voicefield on this endpoint. To layer avoice_promptover an existing voice, usePOST /v1/audio/speechwith bothvoiceandvoice_promptpopulated on Mulhim.
Compared to voice cloning
Voice design and voice cloning both produce a re-usable Voice row, but
they fit different use cases:
| Voice design | Voice cloning | |
|---|---|---|
| Input | Free-form description | 5–30 s reference audio |
| Result | Fresh synthetic timbre | Replication of the source voice |
| Consent | Self-attested only (no third party) | Recorded consent + audit trail required |
| Endpoint | POST /v1/audio/voices/design | POST /v1/audio/voices |
| Best for | Brand voices, fictional characters, marketing experiments | Known speakers (host, narrator, employee) with consent |
Cookbook
Recipe · Build a brand voice library (3 prompts, 3 saves)
Tier: Mulhim v3. The point is reproducibility: design three distinct voices for a brand (e.g. narrator, alert, mascot), save each, and reference them by id everywhere else.
import { HakimClient } from '@hakim/node';
const hakim = new HakimClient({ apiKey: process.env.HAKIM_API_KEY });
const brandVoices = [
{
description: 'Confident male broadcaster, mid-30s, warm baritone, deliberate pacing',
sample: 'This week on Sahibi, three founders share what nearly broke them.',
name: 'Brand · Host',
},
{
description: 'Energetic young woman, late 20s, bright timbre, slight uptick on questions',
sample: 'New episode out now. Tap to listen.',
name: 'Brand · Alert',
},
{
description: 'Playful elder voice, warm grandmother, raspy texture, slow magnetic delivery',
sample: 'Once upon a time, there was a small fishing village by the Red Sea.',
name: 'Brand · Storyteller',
},
];
for (const v of brandVoices) {
const result = await hakim.audio.voices.design({
description: v.description,
sample_text: v.sample,
save_as_voice: { name: v.name },
});
console.log(v.name, '→', result.voice?.id);
}
Once saved, the voices are regular Voice rows with kind: "designed" —
addressable on every tier (Saree' / Faseeh / Mulhim) by their id. No need to
re-design per script.
Recipe · Iterate to nail a timbre
Tier: Mulhim v3. Voice design is creative, not deterministic. Two identical prompts produce similar-but-distinct voices. Iterate until you hear what you want, then save.
import { HakimClient } from '@hakim/node';
const hakim = new HakimClient({ apiKey: process.env.HAKIM_API_KEY });
async function pickBest(description: string, sample: string, attempts = 4) {
const candidates = await Promise.all(
Array.from({ length: attempts }, () =>
hakim.audio.voices.design({ description, sample_text: sample }),
),
);
// Caller picks the best one in their UI (e.g. play each preview,
// present a chooser, save the chosen `sample_b64` audio + the
// prompt so iteration history is reproducible).
return candidates;
}
Pacing tips:
- Sub-50-character prompts under-constrain the model. "Old woman" gives you something old-woman-ish but unpredictable. "Warm raspy elderly woman, low-pitched, magnetic, slight breathy tremor" lands more consistently.
- 100–250 chars is the sweet spot. Beyond ~300 the model starts ignoring the tail; trim adjectives rather than stacking them.
- Don't try to clone a specific person. Public-figure prompts ("voice that sounds like Morgan Freeman") are flagged for manual review and delayed by hours-to-days. If you need a celebrity-style voice, do it via proper cloning with consent.
Recipe · Layer voice_prompt over a preset voice (no design)
Tier: Mulhim v3 only. Sometimes you don't need a fresh voice · you need
the existing voice with a slight tonal overlay. Pair a preset voice id
with a voice_prompt on the standard speech endpoint:
curl https://api.tryhakim.ai/v1/audio/speech \
-H "Authorization: Bearer $HAKIM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "hakim-v3",
"input": "Once upon a time, there was a small fishing village.",
"voice": "layla-egyptian",
"voice_prompt": "speaking in a hushed bedtime-story register",
"response_format": "mp3"
}' \
--output bedtime.mp3
The base voice (Layla, Egyptian) carries the dialect and timbre; the
voice_prompt overlay shifts the register without re-designing. Useful for
single-shot tonal variation in a script (a whispered aside, a panicked
shout) without saving a new voice.
If the call goes to hakim-fast-v1 or hakim-v2, voice_prompt is
silently dropped and x-hakim-warnings: voice_prompt_dropped_by_model_capability
fires · the audio still plays without the overlay. See the
graceful-downgrade recipe
for the detection pattern.
Recipe · Designed voice + non-verbal tags on every render
Tier: Mulhim v3. The voice you designed pairs with the full tag set naturally. Useful for storyteller voices where pauses + breaths land the material:
curl https://api.tryhakim.ai/v1/audio/speech \
-H "Authorization: Bearer $HAKIM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "hakim-v3",
"input": "Once upon a time, [breath] there was a small fishing village. [think] But you already know how this story ends, don'\''t you?",
"voice": "voi_01HX3KZJ8WD6J9KVWBQJ3WJK4G",
"response_format": "mp3"
}'
Same voice id as the design call returned; tags inherit the designed voice's emotional register the same way they would on a preset voice.
Next steps
- Models · full tier ladder; design lives on Mulhim only.
- Non-verbal tags · pair with designed voices for full expressiveness on the speech endpoint.
- API reference → voice design · request/response schemas, error codes, rate limits.