Non-verbal tags
Drop a bracketed token into your input text and Hakim Faseeh / Mulhim weave the cue into
the audio. Useful for podcasts, voiceover-heavy ads, voicebot persona work, and anywhere
the absence of a sigh or a chuckle would read as flat.
curl https://api.tryhakim.ai/v1/audio/speech \
-H "Authorization: Bearer $HAKIM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "hakim-v2",
"input": "Wait. Did that just happen? [laugh] Honestly, [breath] I have no words.",
"voice": "layla-egyptian"
}' \
--output reaction.mp3
Token reference
| Token | Effect |
|---|---|
[laugh] | Laughter |
[breath] | Sigh / exhale |
[think] | Hesitation, "uhm" |
[whisper] | Soft / hushed delivery |
[ask] | Question intonation |
[wow] | Surprise |
[hmph] | Grumble / dissatisfaction |
The set is intentionally small · every token has been tuned across the supported voice catalogue to land naturally rather than as a stage cue. If you want a richer non-verbal palette, open an issue describing the cue you're trying to land; we expand the table when there's reliable demand.
Capability gating
| Tier | Behaviour |
|---|---|
hakim-fast-v1 (Saree') | Tags are stripped before synthesis; the response carries x-hakim-warnings: tags_stripped_by_model_capability. Audio plays clean. |
hakim-v2 (Faseeh) | Tags from the table above are honoured. Anything outside the table (typo, custom token, stale form from another vendor) is stripped + flagged with the same warning header. |
hakim-v3 (Mulhim) | Same allowlist as Faseeh. |
The strip is silent and graceful · the request still returns 200, the audio still
plays, the only signal is the x-hakim-warnings header. Sniff the header presence as a
yes/no check; sniff the comma list to fan out per-code remediation.
Best practices
- Use sparingly. One or two tags per sentence reads natural; five reads as a stage direction. The model is good but not magic.
- Match the surrounding tone. A
[laugh]after "I'm telling you, the meeting was brutal" lands; the same tag after "Please confirm your account number" doesn't. - Mix with prose.
Hello.[laugh] How are you?works;[laugh][laugh][laugh]doesn't. The model wants context to anchor the cue. - Test on the playground first.
/app/playground/ttsships a tag-insert chip rail under the composer; clicking inserts the token at the caret so you don't have to memorise the spelling.
Migrating from another vendor's tag set
If you're porting from a system that uses different bracketed tokens ([Question-ah],
[Surprise-yo], etc.), Hakim's parser silently strips them · you'll hear the prose
without the cue and see the warning header fire. See the tag-map recipe in the models
guide for the
canonical mapping table; the closest correspondences are usually obvious.
Cookbook
Recipe · Podcast intro with three tag types
Tier: Faseeh v2 (hakim-v2) or Mulhim v3 (hakim-v3). The same script on
Saree' would strip every tag silently and play as plain narration.
curl https://api.tryhakim.ai/v1/audio/speech \
-H "Authorization: Bearer $HAKIM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "hakim-v2",
"input": "Welcome back. [breath] So. [think] Where do I even start? [laugh] Right. Let'\''s do this.",
"voice": "layla-egyptian",
"response_format": "mp3"
}' \
--output intro.mp3
What lands:
[breath]after "Welcome back." · a deliberate exhale before the first beat. Adds the "the host is settling in" feel.[think]after "So." · a soft "uhm"-like hesitation. Reads as the host searching for the right opening line.[laugh]before "Right." · a brief amused exhale, like the host catching themselves. Lands the energy reset.
Three tags over six sentences is the upper bound of "feels natural" in our testing. Beyond that the audio starts reading as a stage direction; tags are flavour, not dialogue.
Recipe · Tag tone match to dialect
Tier: Faseeh / Mulhim. Tags inherit the voice's emotional register, so the
same [laugh] reads differently on each:
| Voice | [laugh] register | Pairs with |
|---|---|---|
layla-egyptian | bright, energetic | comedy intros, contemporary fiction |
mohammed-msa | reserved, dry chuckle | news commentary, business podcasts |
aya-levantine | warm, conversational | lifestyle content, interviews |
omar-khaleeji | measured, formal | corporate narration, documentaries |
Pick the voice for the show's identity; the tag set rides along with it naturally. Don't try to "force" a register that fights the voice · the mismatch is audible.
Recipe · A/B prosody with and without tags
When you're A/B testing whether tags lift listener retention, render both versions on the same Faseeh request batch and serve them randomly:
import { HakimClient } from '@hakim/node';
const hakim = new HakimClient({ apiKey: process.env.HAKIM_API_KEY });
async function renderBoth(script: string, voice: string) {
// Version A: tagged
const tagged = await hakim.audio.speech.create({
model: 'hakim-v2',
input: script,
voice,
response_format: 'mp3',
});
// Version B: plain (same model, same voice, same input minus tags)
const plain = await hakim.audio.speech.create({
model: 'hakim-v2',
input: script
.replace(/\[[a-z]+\]/g, '')
.replace(/\s+/g, ' ')
.trim(),
voice,
response_format: 'mp3',
});
return { tagged, plain };
}
Measure listen-through rate, not first-byte latency · the renders are identical on the wire from a TTFB perspective; the difference is qualitative in the audio itself.