---
title: "Voiceover text: markup, normalization, billing"
url: https://lumean.app/de/developers/docs/guide/text
locale: de
description: "What the server strips from the text before generation, what still gets voiced, what you pay for, and how to check all of it for free."
updated: 2026-09-04
source_locale: en
sources: [post-orders, post-orders-chunks-preview, get-orders-order-items-item-text]
errors: ["422", text-empty, content-blocked]
spec_version: 0.0.1
---

# Voiceover text: markup, normalization, billing

The text of a TTS order does not go to the generator as is: markup is stripped first, then the
text is normalized, and only then is it split into chunks. Each step changes both what you hear
and what you pay for; none of them is visible in the [`POST /orders`](/developers/docs/api/post-orders)
response.

## Three text channels, one set of rules

Text is accepted by three equivalent fields — `input_text` at the root of the request, a `.txt`
inside `input_files[]` (up to 20 MB per file), and `task_data.text` for template-less services;
all three go through the same checks. For TTS, put the text in the top-level `input_text`,
**not** in `task_data`.

## What is stripped from the voiceover

Three kinds of markup are recognized; none of them is read aloud verbatim, but only one is free:

| Kind | Examples | What it does | Free |
| --- | --- | --- | --- |
| Pause | `{{pause=2.5}}`, `{{pause:500ms}}`, `{{pause}}` | Silence; without a value, 1 second | Yes |
| SSML pause | `<break time="2s"/>` | The same in SSML notation | Unclear, see below |
| Direction to the narrator | `[шёпот]`, `[confident]`, `[pause]` | A hint about the reading style | Unclear, see below |

The difference is not cosmetic: `{{…}}` is our format — the worker builds the silence from it,
and the string never reaches the provider. Brackets and `<break/>` are the provider's own
syntax: the provider receives them in full and counts them toward its length limit, so they
**take up space in the chunk**: a 999-character chunk with `[confident]` inside holds 988
spoken characters.

> [!WARNING]
> Sources disagree on whether `[…]` and `<break/>` are **billed**, and the disagreement is inside
> the spec itself. “Billed,” say the description of the `billable_length` field (“only our
> `{{pause=2s}}` pause tags are free; `[шёпот]` and `<break/>` go to the provider”) and the LLM
> reference (§8.3). “Not billed,” say the descriptions of `summary.total_effective_length` in the
> preview and of the `regenerate` request body. Budget for the more expensive reading, and take
> the exact figure from the response: the free preview computes `total_price_units` with the same
> pipeline as the order, and `billable_length` in `GET /orders/{order}/items/{item}/text` is the
> source of truth for a chunk.

### Any square brackets are markup, not just a list of keywords

**Any** `[…]` span with no nested brackets and no line break inside counts as a direction
(regex `/\[[^\[\]\n]+\]/u`). A footnote `[1]`, a `[sic]` note, a timecode `[00:12]` will not
be spoken — the model will read them as directions about the reading style.

Parentheses, angle-bracket tags, and asterisks, by contrast, **are voiced and billed**: the
narrator will read `(пауза)` and `*шёпотом*` aloud.

## Pause caps

They are checked **before** the order is created; exceeding them returns `422`, and no money is
reserved. No more than **500** pauses per text, no single pause longer than **10 seconds**, and
no more than **3600 seconds** of silence in total. Only `{{pause}}` and `<break/>` count; the
legacy form `[pause=2s]` does not.

A separate rejection applies to text with nothing pronounceable left after markup is stripped:
only tags → `text_empty`; only punctuation or only bracketed remarks → `text_not_voiceable`
(named `paragraph_not_voiceable` in the OpenAPI spec). Both are `422`.

### 422 · `text_empty` — text_empty — nothing to voice

| Что произошло | Что делать |
| --- | --- |
| 422 with `reason: "text_empty"`. The order was not created and no money is reserved. When there is no text at the input in the first place (an empty field), the same case arrives differently — as ordinary validation, with the field in `errors` and no `reason`. | Check whether the body has `errors`: if it does, the field simply was not filled in; if not, the text arrived but consisted entirely of markup. Parentheses and angle brackets, asterisks, and other typography, on the contrary, ARE voiced and billed — removing them will not help. |

`Retry-After`: нет. Разбор: https://lumean.app/de/developers/docs/errors/text-empty

## Normalization before chunking

Chunk offsets (`start`/`end`) are counted from the **modified** text, not from the text you sent:

| What | How |
| --- | --- |
| `\r\n`, `\r` | → `\n` |
| Tab | → space |
| More than 3 line breaks in a row | → exactly 3 |
| More than 5 spaces in a row | → exactly 5 |
| `Привет.Мир` | → `Привет. Мир` (voiceover only) |

The last rule (a space after `.`/`!`/`?` before a letter or digit) leaves decimals,
abbreviations, acronyms, URLs with a scheme, emails, and tag contents untouched. The trade-off:
a bare domain without a scheme (`example.com` mid-sentence) is indistinguishable from
`Привет.Мир` and will get a space — write such addresses with a scheme.

## Paragraph mode (`generation_mode`)

By default, the text is cut sequentially by the technical chunk size, and paragraph boundaries
play no role. Paragraph mode first splits the text on **blank lines**, and then each paragraph
is chunked on its own. The key lives **not inside `tts_settings`** but at the root of
`config_override` (or at the root of the template's `config` — then the mode is permanent):

```json
{ "template_id": "<UUID>", "input_text": "Первый абзац.\n\nВторой абзац.",
  "config_override": { "generation_mode": "paragraph" } }
```

The values are `paragraph` and `default`. Supported by `elevenlabs` and `heygen`; other
services silently ignore the key — there is no rejection, the mode simply does not turn on. It
is not listed in `GET /templates/config-options`: that endpoint lists service settings, while
the mode is set per order. At most 10 single-chunk paragraphs plus 5 multi-chunk paragraphs run
concurrently per order.

The response gains `paragraphs[]` (for each paragraph: `paragraph_index`, `chunks_count`,
`completed_chunks`, `status`, `progress_percent`) and a `paragraph_index` on chunks. Pitfalls:

- `paragraphs[].status` has its own vocabulary of four values (`pending`, `in_progress`,
  `failed`, `completed`); a chunk blocked by policy collapses into `failed`;
- **only the original** chunks (`item_type = chunk`) are counted: a successful retry of a failed
  chunk does not change the paragraph status. `retry` and `regeneration` records have
  `paragraph_index` set to `null` — get the paragraph from the parent via `parent_item_id`.

> [!CAUTION]
> The [minimum billable volume](/developers/docs/guide/billing) applies here to **each paragraph
> separately**. Five short paragraphs cost five minimums, whereas the same text in the default
> mode would cost one. The threshold is visible in the preview: `summary.min_billable_chars`.

## Calculate all of this in advance, for free

`POST /orders/chunks/preview` is a dry run of the same pipeline without creating an order: it
costs no money and does not spend the key's token quota. `summary` returns `voiced_length`
(what will actually be voiced), `total_effective_length`, `total_price_units`,
`min_billable_chars`, and three diagnostic flags: `voiced_empty` (a future `text_empty`),
`caps_ok` with `caps_exceeded` (`count`/`single`/`total`), and `has_blocked_content` with
`blocked_chunks[]` — the last one shows blocklisted chunks before the order is even created
(see [retries](/developers/docs/guide/retries)). Unlike `POST /orders`, the preview does not
fail on caps.

### POST /orders/chunks/preview

Preview Chunk Split

Справочник: https://lumean.app/de/developers/docs/api/post-orders-chunks-preview

**Правильно**

Run the text through the preview and check `voiced_length` and `total_price_units` against your
expectations.

**Неправильно**

Estimate the cost from the string length: markup, the minimum billable volume, and paragraph
mode move the figure in both directions.

---

- Diese Seite im Browser: https://lumean.app/de/developers/docs/guide/text
- Vollständige maschinenlesbare Referenz: https://api.lumean.app/docs/llm.md
