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:

AxisWhat it controlsExample
Identitygender, age, role"middle-aged male broadcaster"
Texturepitch, timbre, grain"low-pitched, magnetic, slight rasp"
Scenarioemotion, 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_review rather 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 voice field on this endpoint. To layer a voice_prompt over an existing voice, use POST /v1/audio/speech with both voice and voice_prompt populated 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 designVoice cloning
InputFree-form description5–30 s reference audio
ResultFresh synthetic timbreReplication of the source voice
ConsentSelf-attested only (no third party)Recorded consent + audit trail required
EndpointPOST /v1/audio/voices/designPOST /v1/audio/voices
Best forBrand voices, fictional characters, marketing experimentsKnown 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