# Direct the delivery with tags

Direction tags are bracketed directions such as [whispering], sounds such as [laughs], hesitations, and <emph> and <whisper> marks, written into the text of a Paxa TTS Flash v2 request. How each is read, weights, blends, billing, and every tag the API refuses.

On Paxa TTS Flash v2, every voice reads tags written into the `text` of [POST /v1/tts](https://paxalabs.com/docs/api/text-to-speech). A direction in square brackets, such as `[whispering]`, sets how the text after it is delivered. A sound such as `[laughs]` plays where it is written, a hesitation such as `[um]` is spoken as a word, and `<emph>` and `<whisper>` mark a span. The API reads every tag before any charge, and a tag the model refuses answers [400 tag_invalid](https://paxalabs.com/docs/errors#tag_invalid). On Paxa TTS Flash (`paxa-tts-flash-v1`), only its experimental voices read [emotion tags](https://paxalabs.com/docs/emotion-tags), and every other voice skips them.

curl:

```bash
curl -X POST https://api.paxalabs.com/v1/tts \
  -H "Authorization: Bearer $PAXA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "[warm, unhurried] ยินดีต้อนรับกลับมาครับ [chuckles] วันนี้มีข่าวดี [เอ่อ] <whisper>แต่ขอเก็บเป็นความลับไว้ก่อนนะครับ</whisper>",
    "voice": "khanomkrok",
    "model": "paxa-tts-flash-v2"
  }' \
  --output speech.mp3
```

TypeScript:

```typescript
import { writeFile } from "node:fs/promises";

const response = await fetch("https://api.paxalabs.com/v1/tts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PAXA_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    text: "[warm, unhurried] ยินดีต้อนรับกลับมาครับ [chuckles] วันนี้มีข่าวดี [เอ่อ] <whisper>แต่ขอเก็บเป็นความลับไว้ก่อนนะครับ</whisper>",
    voice: "khanomkrok",
    model: "paxa-tts-flash-v2",
  }),
});

if (!response.ok) throw new Error(`TTS failed: ${response.status}`);
await writeFile("speech.mp3", Buffer.from(await response.arrayBuffer()));
```

Python:

```python
import os
import requests

response = requests.post(
    "https://api.paxalabs.com/v1/tts",
    headers={"Authorization": f"Bearer {os.environ['PAXA_API_KEY']}"},
    json={"text": "[warm, unhurried] ยินดีต้อนรับกลับมาครับ [chuckles] วันนี้มีข่าวดี [เอ่อ] <whisper>แต่ขอเก็บเป็นความลับไว้ก่อนนะครับ</whisper>", "voice": "khanomkrok", "model": "paxa-tts-flash-v2"},
)
response.raise_for_status()

with open("speech.mp3", "wb") as f:
    f.write(response.content)
```

## Directions

Directions are natural language. Write how the words after the tag should sound, in English, Thai, or Chinese, and the voice delivers them that way until the next direction. Any words in square brackets that are not a sound, a hesitation, a pause, or a refused setting read as a direction. Some to start from:

- `[whispering]`, `[calm, measured]`, `[building excitement]`, `[barely awake, at three in the morning]`
- `[อบอุ่น พูดช้า ๆ]`, `[เขินอาย พูดเบา ๆ]`, `[ค่อย ๆ ตื่นเต้นขึ้น]`
- `[害羞地，小声]`, `[冷冷地，带着笑意]`, `[像是等这一刻等了十年]`

Text before the first direction keeps the voice's own delivery, and `[neutral]` returns to it. A sound or a hesitation leaves the direction in force.

- Nothing in square brackets is spoken except a hesitation. A tag runs from `[` to the next `]` with no other `[` and no line break between them, and full-width brackets `［ ］` work the same way.
- Tags are read without regard to case, full-width letters and digits read as their ASCII forms, and runs of spaces read as one: `[Sad]`, `[ＳＡＤ]`, and `[ sad ]` are all `[sad]`.
- A bracket without letters, such as `[1]` or `[...]`, answers `tag_invalid`.

## Weights and blends

A weight follows a colon, as in `[sad:0.5]` or `[laughs:0.3]`. Write it as a number such as `0.5`, `.5`, or `0,5`, or as a percentage such as `50%`. Thai and full-width digits read too. A weight below 0 reads as 0. There is no upper limit: `[excited:2]` is valid. On `[neutral]`, on an emotion word from the [emotion tag list](https://paxalabs.com/docs/emotion-tags#tags) such as `[sad]`, and on a sound, a weight that is not a number, such as `[sad:half]`, answers `tag_invalid`. On any other direction the colon is part of its words: `[note: see below]` is one direction.

`+` joins directions into a blend, each part with its own weight: `[happy+sad]`, `[happy:0.7+sad:0.3]`. A blend takes directions only: `[happy+laughs]` and `[sad+um]` answer `tag_invalid`.

## Sounds

A sound plays where it is written. Write any sound below by its name or one of its spellings, with a weight if you like, as in `[soft laugh:0.8]`. A sound at weight 0 is left out.

| Sound | Also written as |
| --- | --- |
| [sighs] | sigh, a sigh, deep sigh, heavy sigh, long sigh, sighs deeply, sighs heavily, soft sigh, sighs softly, ถอนหายใจ, ถอนใจ, ถอนหายใจยาว, 叹气, 叹息, 唉 |
| [laughs] | laugh, laughter, a laugh, short laugh, big laugh, soft laugh, laughs softly, quiet laugh, เสียงหัวเราะ, 笑声, 大笑 |
| [chuckles] | chuckle, a chuckle, soft chuckle, small chuckle, light chuckle, chuckles softly, หัวเราะหึๆ, หัวเราะหึ ๆ, หัวเราะในลำคอ, 轻笑, 窃笑 |
| [exhales sharply] | exhale sharply, sharp exhale, huff, huffs, พ่นลมหายใจแรงๆ, พ่นลมหายใจแรง ๆ, 猛地呼气 |
| [gasps] | gasp, a gasp, sharp gasp, soft gasp, หายใจเฮือก, 倒吸一口气, 倒抽一口冷气 |
| [inhales deeply] | inhale deeply, deep inhale, deep breath, a deep breath, takes a deep breath, breathes in deeply, หายใจเข้าลึกๆ, หายใจเข้าลึก ๆ, สูดหายใจลึกๆ, สูดหายใจลึก ๆ, 深吸一口气, 深呼吸 |
| [giggles] | giggle, a giggle, soft giggle, หัวเราะคิกคัก, คิกคัก, 咯咯笑, 嘻嘻 |
| [sobs] | sob, a sob, soft sob, sobs quietly, เสียงสะอื้น, 抽泣, 啜泣, 呜咽 |
| [yawns] | yawn, a yawn, หาว, 打哈欠, 哈欠 |
| [clears throat] | clear throat, clears his throat, clears her throat, clears their throat, throat clear, ahem, กระแอม, กระแอมไอ, 清嗓子, 清了清嗓子 |
| [inhales] | inhale, breathes in, quick inhale, หายใจเข้า, 吸气 |
| [breath] | a breath, takes a breath, quick breath, หายใจ, 呼吸 |
| [exhales] | exhale, breathes out, หายใจออก, 呼气 |
| [whimpers] | whimper, a whimper, soft whimper, เสียงหงิงๆ, เสียงหงิง ๆ, เสียงครางหงิงๆ, เสียงครางหงิง ๆ, 嘤嘤, 嘤咛, 呜呜 |
| [scoffs] | scoff, a scoff, pfft, pff, psh, pshh, soft scoff, แค่นเสียง, หึ, เสียงหึ, 嗤, 嗤了一声, 冷哼, 冷哼一声, 哼 |

These sounds answer `tag_invalid` in any spelling.

| Sound | Also written as |
| --- | --- |
| [sniffs] | sniff, sniffle, sniffles, สูดจมูก, 吸鼻子, 抽鼻子 |
| [gulps] | gulp, a gulp, กลืนน้ำลาย, 咽口水, 吞口水 |
| [coughs] | cough, a cough, dry cough, coughing fit, soft cough, small cough, coughs softly, ไอ, เสียงไอ, ไอแค่กๆ, ไอแค่ก ๆ, ไอค่อกแค่ก, 咳嗽, 咳, 咳了一声, 干咳 |
| [sneezes] | sneeze, a sneeze, achoo, ah-choo, atchoo, small sneeze, soft sneeze, จาม, เสียงจาม, ฮัดชิ่ว, ฮัดเช้ย, 打喷嚏, 喷嚏, 阿嚏 |
| [groans] | groan, a groan, soft groan, groans softly, เสียงคราง, เสียงครางเบาๆ, เสียงครางเบา ๆ, 呻吟声, 一声呻吟 |
| [grunts] | grunt, a grunt, soft grunt, ฮึ่ม, เสียงฮึ่ม, อึ๊บ, เสียงอึ๊บ, 闷哼, 闷哼一声, 低哼 |
| [snorts] | snort, a snort, soft snort, พ่นลมจมูก, พ่นลมทางจมูก, เสียงพ่นลมจมูก, 喷鼻息, 鼻哼, 鼻子哼了一声 |
| [pants] | pant, pants heavily, pants softly, เสียงหอบ, เสียงหอบหายใจ, 喘气声, 喘息声, 大口喘气 |
| [shushes] | shush, a shush, shh, shhh, จุ๊ๆ, จุ๊ ๆ, ชู่ว, ชู่ว์, เสียงชู่ว, 嘘, 嘘了一声 |
| [tsks] | tsk, tsk tsk, tut, tuts, tut tut, tut-tut, tongue click, clicks tongue, clicks her tongue, clicks his tongue, clicks their tongue, จิ๊, จิ๊ปาก, จุ๊ปาก, เดาะลิ้น, 啧, 啧啧, 咂嘴 |
| [hums] | hum, a hum, soft hum, hums softly, ฮัม, เสียงฮัม, ฮัมเพลง, 哼歌, 哼小曲, 哼曲子 |
| [screams] | scream, a scream, shriek, shrieks, a shriek, small shriek, กรี๊ด, เสียงกรี๊ด, เสียงกรีดร้อง, 尖叫声, 一声尖叫, 惊叫声 |

## Hesitations

`[um]` and `[hmm]` are hesitations. Each is spoken as the word written inside the brackets. A text with a hesitation always has something to say. A hesitation takes no weight: `[um:0.5]` answers `tag_invalid`.

| Hesitation | Also written as |
| --- | --- |
| [hmm] | hm, hmmm, mmm, อืม, 嗯 |
| [um] | umm, uh, uhh, er, เอ่อ, เอ่อๆ, เอ่อ ๆ, อ่า, 呃, 呃呃, 额 |

The spellings `[uhm]`, `[erm]`, `[อืมม]` answer `tag_invalid`.

## Pauses

`[short pause]`, `[pause]`, `[long pause]`, and an exact length such as `[pause 300ms]` add silence where they are written. [Pauses](https://paxalabs.com/docs/pauses) covers their lengths and the 500 ms limit.

## Emphasis and whisper

`<emph>` and `<whisper>` mark a span of text. `<emph>...</emph>` stresses the words inside, and `<whisper>...</whisper>` whispers them.

- A weight goes after a colon, as in a bracket: `<emph:0.5>`, `<whisper:0.3>`. A mark whose weight is not a number, such as `<emph:half>`, is ignored along with its close.
- The close restores the direction in force before the mark, or the voice's own delivery when there was none. A direction written inside the span replaces what the close restores.
- A close ends the innermost open mark of its kind. A close with no open mark is ignored, and a mark left open holds like a direction.
- Case and spaces inside a mark do not matter: `< EMPH >` and `</ emph >` both read.
- Emphasis can also be written as a direction, `[เน้นเสียง]` or `[เน้น]`, `[เน้นคำ]`, `[เน้นคำนี้]`, `[เน้นย้ำ]`, `[ลงน้ำหนัก]`, `[emphasize]`, `[emphasise]`, `[emphasized]`, `[emphasised]`, `[emphasis]`, `[with emphasis]`, `[强调]`, `[強調]`, `[重读]`, `[重讀]`, `[加重语气]`, `[加重語氣]`.

## Tags the API refuses

Each of these answers [400 tag_invalid](https://paxalabs.com/docs/errors#tag_invalid) before any charge, even in a text that holds nothing else.

| Tag | Examples |
| --- | --- |
| A bracket without letters | `[1]`, `[...]` |
| A sound outside the list of sounds | `[sniffs]`, `[gulps]`, `[coughs]`, `[sneezes]`, `[groans]`, `[grunts]`, `[snorts]`, `[pants]`, `[shushes]`, `[tsks]`, `[hums]`, `[screams]` |
| A hesitation spelling the model does not read | `[uhm]`, `[erm]`, `[อืมม]` |
| A weight on a hesitation | `[um:0.5]` |
| A weight that is not a number on `[neutral]`, an emotion word, or a sound | `[sad:half]`, `[laughs:lots]` |
| A blend part that is not a direction | `[happy+laughs]`, `[sad+um]` |
| A request setting written as a tag | `[voice]`, `[language]` |
| An accent | `[british accent]`, `[สำเนียงอีสาน]`, `[北京口音]` |
| A different voice | `[robotic]`, `[old man voice]`, `[เสียงเด็ก]`, `[机器人]` |
| Singing | `[singing]`, `[ร้องเพลง]`, `[唱歌]`; `[sing-song]` is a direction |
| A sound effect | `[door slams]`, `[applause]`, `[sound effect]`, `[เสียงประกอบ]` |
| A language | `[in thai]`, `[english]`, `[ภาษาไทย]`, `[中文]` |

## Billing

Tags are text. Every character of a tag, brackets and marks included, counts toward the request's length and its charge at the model's per-character rate. The 126-character example above costs 1.26 credits. A text of tags alone, hesitations aside, answers [422 unspeakable_text](https://paxalabs.com/docs/errors#unspeakable_text) before anything is charged. [Credits and billing](https://paxalabs.com/docs/credits) covers rounding and the minimum.

## Tags on a live connection

On a [live connection](https://paxalabs.com/docs/live-streaming) a `text` frame whose tag the model refuses answers an `error` event with `tag_invalid`, and the buffer keeps the text it held before the frame. Auto-flush holds back a tag or mark still open at the end of the buffer, and one split across frames is read whole. One still open past the length threshold that flushes untagged text is read as text and is not checked as a tag. Tags that no words follow yet wait in the buffer for them. Each synthesis leg opens in the directions and marks the previous leg ended in. Those opening tags carry no charge. They count toward `max_chars` and may fill at most half of it. A frame that would leave longer opening tags, or take the tags waiting in the buffer and the opening tags past `max_chars`, answers `text_too_long`. `cancel` returns the connection to the voice's own delivery.

> The [OpenAI-compatible endpoint](https://paxalabs.com/docs/api/speech) reads tags in `input` under the same rules.
