Descriptions are in English
Preview Chunk Split
Dry-run of the text-to-chunks split and cost calculation — without creating an order.
The same computation POST /orders uses internally: check your split/price here
before the real submission. Does not spend the key's token quota — preview is free.
Spec fixes · 2
Chunk preview is available with an API key
The machine-readable
llm.md§12 claims chunk preview is a web-cabinet endpoint that returns401for API keys. That is wrong: all three preview operations are part of the public surface and require theorders.readscope — as described by both the OpenAPI document and §7.1 of the very samellm.md. If an AI assistant cites §12, it is mistaken.Billing for
[whisper]and<break/>markup is inconsistentThree places in the spec state that markup tags (
[whisper],<break/>) are not billed, while the description ofbillable_lengthstates they are. Until the backend resolves this, assume the worst case and count markup characters into your cost estimate. The exact price of a given text comes fromPOST /orders/chunks/preview, which returns the length already computed.
A single ready envelope covers both the sync and async paths:
- 200
status=ready— TTS text ≤ 50,000 characters (and any non-TTS text): the result is ready immediately, the body carriessummary(totals for the whole text), the first page ofchunks[], andpage(pagination meta). - 202
status=processing— TTS text past the threshold: the calculation runs in the background, the body carries onlypreview_id. The result arrives as a.readyWebSocket event on theprivate-chunks.preview.{userId}channel (seePOST /broadcasting/auth), or fetch it manually viaGET /orders/chunks/preview/{previewId}.
Incremental re-preview: pass the previous response's preview_id as
previous_preview_id along with the new input_text — only the edited neighborhood
is recomputed, and the result is identical to a full recalculation.
Order settings: pass the same config_override you will send to POST /orders —
the preview splits by the template config with it applied, exactly like the order
(settings that affect the split apply here too). A value that order creation would
reject with 422 is rejected by the preview as well.
Pauses/caps: exceeding the pause limits (>500 pauses, >3600s total, >10s single) or the
order size (>1500 fragments) does NOT fail the request — you get
summary.caps_ok=false + caps_exceeded. This is what sets preview apart from
POST /orders, where the same overage returns 422.
The preview cache is shared with a web session of the same account (if the key owner also uses the website): the same text+template+mode, already computed in the browser, comes back instantly.
Required key permission: orders.read (preview does not mutate data — it is a read).
On this page(4)
Request body
Chunk-preview parameters — a dry-run of the text-to-chunks split and its cost, without creating an order. Free of charge, does not spend the key's token quota.
{
"template_id": "9f8b2c1a-4d3e-4f5a-8b9c-0d1e2f3a4b5c",
"task_type": "string",
"input_text": "string",
"previous_preview_id": "string",
"text_hash": "string",
"generation_mode": "default",
"config_override": []
}Fields
The preview_id of a prior preview response, for incremental re-preview: only the edited neighborhood is recomputed. Works for sync texts (≤50,000 characters); for larger ones the edit still goes through a full async recalculation. If it has expired, just omit it — you get a full recalculation instead.
Not required for POST — the server computes the hash itself from input_text. Send it only if you already know it for some other reason; it is otherwise ignored here (used by GET /orders/chunks/preview for the preflight lookup instead).
Generation mode — the same value accepted by config_override.generation_mode when creating an order. paragraph splits the text on blank lines first, and each paragraph becomes its own generation unit (see POST /orders for the full explanation). Omitted — a continuous split, the previous behavior.
Generation settings on top of the template config — the same JSON object you will send to POST /orders. Settings that affect the split apply to the preview exactly as to the order; keys the service does not declare are dropped. Omitted — the split follows the template config alone.
Possible responses
200Success. The payload is in data.
Success. The payload is in data.
"string"Success flag. Branch on the HTTP status code instead: any 2xx response carries true here, while an error comes back in a different shape that need not contain this field at all.
A human-readable note in the account language. Fine to show to a user; do not parse it — the wording changes without notice and is not an error code.
datarequiredThe response payload.
object
The response payload.
Always ready for a 200 response — the calculation is done.
A content-based preview id (sha256 of text+template+mode+algorithm version). Use it for incremental re-preview (previous_preview_id) and for pagination (GET .../{previewId}).
summaryrequiredTotals for the whole text — not just the current page of chunks.
object
Totals for the whole text — not just the current page of chunks.
Total number of chunks in the split.
Total billable length across all chunks, in characters (pauses and stage directions are excluded).
Total cost in tokens — the same amount POST /orders would charge for the identical text.
Actual voiced length BEFORE topping up to the minimum billable volume — for an honest character count display.
The minimum billable volume (0 — no minimum). If voiced_length is below this threshold, the cost is still computed against the threshold.
Number of pauses in the text ({{pause}}/<break/>). Pauses are free — excluded from the cost.
Total silence duration, in seconds.
The order sanity caps are not exceeded. false does NOT fail the preview (unlike POST /orders, where the same overage returns 422) — it is only a warning.
Which cap was exceeded: count (more than 500 pauses), single (one pause longer than 10s), total (total silence over 3600s) or chunks (the order yields more than 1500 fragments — more than the system can assemble into one file). null means the caps are fine.
Nothing speakable is left in the text (only tags/pauses/stage directions). POST /orders with this text would return 422.
Indexes of chunks whose text already tripped the content policy and sits in the blocklist. An empty array means no blocks.
Whether at least one chunk is blocked — the same thing as blocked_chunks !== [], as a boolean for convenience.
paragraphsrequirednull means the preview is not paragraph-mode (the same generation_mode value used for orders drives this field). In paragraph mode, an aggregate per paragraph.
nullablearray<object>
null means the preview is not paragraph-mode (the same generation_mode value used for orders drives this field). In paragraph mode, an aggregate per paragraph.
The paragraph's index, zero-based.
How many chunks fall inside this paragraph (a long paragraph can span several chunks).
The cross-text index of the paragraph's first chunk — use it to locate the paragraph within the paginated data.chunks, even if its chunks landed on a different page.
The raw length of the paragraph text in characters, including tags/pauses.
The paragraph's billable length — in paragraph mode the minimum billable volume is enforced PER PARAGRAPH, so short paragraphs can cost more than the same text split continuously.
The paragraph's cost in tokens.
Number of paragraphs, or null outside paragraph mode. Stays a number even when paragraphs is collapsed by the WS cap.
Cost in LMC (currency-independent base). The field may be ABSENT (not null) if the cost anchor could not be computed — this is best-effort and not critical for a preview.
The cost converted to the account's display currency — the same multi-currency breakdown used across the public API (amount/amount_formatted per currency code). The field may be ABSENT if cost_lmc was not computed or the rate is unavailable.
chunksrequiredOnly the CURRENT page (see data.page), not the full list — use pagination (GET .../{previewId}?page=) for the rest.
array<object>
Only the CURRENT page (see data.page), not the full list — use pagination (GET .../{previewId}?page=) for the rest.
The chunk's index across the whole text (does not reset at paragraph boundaries).
The chunk text exactly as the voice provider will receive it.
The chunk's start offset (in characters) within the NORMALIZED input text — the same normalization POST /orders applies before splitting.
The chunk's end offset in the normalized text (half-open interval: end is exclusive).
The raw length of the chunk text in characters, INCLUDING tags/pauses.
The chunk's billable length (pauses excluded, topped up to the minimum where applicable) — the sum across a page's chunks need not equal total_effective_length (which covers ALL chunks, not just this page).
The chunk's cost in tokens.
The index of the paragraph this chunk belongs to, or null outside paragraph mode.
pagerequiredPagination meta for the CURRENT page of data.chunks.
object
Pagination meta for the CURRENT page of data.chunks.
The current page number, 1-based.
The page size (how many chunks were requested, or the default).
Total chunks in the split (across all pages).
Whether more pages follow the current one.
202Accepted for processing. The result arrives asynchronously.
Accepted for processing. The result arrives asynchronously.
"string"Success flag. Branch on the HTTP status code instead: any 2xx response carries true here, while an error comes back in a different shape that need not contain this field at all.
A human-readable note in the account language. Fine to show to a user; do not parse it — the wording changes without notice and is not an error code.
datarequiredThe response payload.
object
The response payload.
Always processing for a 202 response — the calculation is running in the background.
The preview id. Use it to subscribe to the private-chunks.preview.{userId} WS channel (the .ready event), or to poll GET /orders/chunks/preview/{previewId} manually.
401The key is missing, unknown, disabled or expired.
The key is missing, unknown, disabled or expired.
{
"success": false,
"message": "API key is missing. Provide it in the X-API-KEY header.",
"data": null
}Always false in error responses.
Error text in the key's language (set through the X-Locale header).
Always null when the request is rejected before processing.
403The key lacks the required permission, or the object belongs to another user.
The key lacks the required permission, or the object belongs to another user.
{
"success": false,
"message": "This API key does not have permission to perform this action."
}Always false in error responses.
Error text in the key's language (set through the X-Locale header).
422Validation error (input_text is required), or reason: template_model_outdated — the template or config_override carries settings of the service's previous schema, the same refusal as order creation (paths in fields).
Validation error (input_text is required), or reason: template_model_outdated — the template or config_override carries settings of the service's previous schema, the same refusal as order creation (paths in fields).
{
"message": "string",
"errors": {
"key": [
"string"
]
}
}Errors overview.
errorsrequiredA detailed description of each field that failed validation.
object
A detailed description of each field that failed validation.
429Limit exceeded: either the key's request rate or its token quota. Retry no earlier than Retry-After.
Limit exceeded: either the key's request rate or its token quota. Retry no earlier than Retry-After.
{
"success": false,
"message": "Too many attempts. Please wait a moment and try again.",
"reason": "rate_limit_exceeded",
"error_code": "rate_limit_exceeded",
"retry_after": 30
}Always false in error responses.
Error text in the key's language (set through the X-Locale header).
Machine-readable failure reason — branch on this, not on the message text.
The same machine-readable code as in reason — a second name for the field. The values always match; branch on either.
How many seconds to wait before retrying (the same value is in the Retry-After header).
Events this produces
Progress arrives over WebSocket: polling the order in a loop is not required. Subscribing needs the same scope as reading orders.
Nearby
The same in words
Neighbouring operations
Feed this page to an AI
spec 0.0.1