Skip to content
Lumean API
POST/orders/chunks/preview

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 returns 401 for API keys. That is wrong: all three preview operations are part of the public surface and require the orders.read scope — as described by both the OpenAPI document and §7.1 of the very same llm.md. If an AI assistant cites §12, it is mistaken.

  • Billing for [whisper] and <break/> markup is inconsistent

    Three places in the spec state that markup tags ([whisper], <break/>) are not billed, while the description of billable_length states 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 from POST /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 carries summary (totals for the whole text), the first page of chunks[], and page (pagination meta).
  • 202 status=processing — TTS text past the threshold: the calculation runs in the background, the body carries only preview_id. The result arrives as a .ready WebSocket event on the private-chunks.preview.{userId} channel (see POST /broadcasting/auth), or fetch it manually via GET /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

application/jsonrequired

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.

Example body
{
  "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

template_idrequired
uuid
string
input_textrequired
string
task_typeoptional
nullable
string
previous_preview_idoptional

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.

nullable
string
text_hashoptional

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).

nullablepattern: ^[a-f0-9]{64}$
string
generation_modeoptional

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.

nullable
Valuesdefaultparagraph
string
config_overrideoptional

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.

object

Possible responses

200

Success. The payload is in data.

Body example
"string"
Response fields
successrequired

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.

boolean
messagerequired

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.

string
datarequired

The response payload.

object
statusrequired

Always ready for a 200 response — the calculation is done.

Valuesready
string
preview_idrequired

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}).

string
summaryrequired

Totals for the whole text — not just the current page of chunks.

object
chunks_countrequired

Total number of chunks in the split.

integer
total_effective_lengthrequired

Total billable length across all chunks, in characters (pauses and stage directions are excluded).

integer
total_price_unitsrequired

Total cost in tokens — the same amount POST /orders would charge for the identical text.

integer
voiced_lengthrequired

Actual voiced length BEFORE topping up to the minimum billable volume — for an honest character count display.

integer
min_billable_charsrequired

The minimum billable volume (0 — no minimum). If voiced_length is below this threshold, the cost is still computed against the threshold.

integer
pause_countrequired

Number of pauses in the text ({{pause}}/<break/>). Pauses are free — excluded from the cost.

integer
total_pause_secondsrequired

Total silence duration, in seconds.

number
caps_okrequired

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.

boolean
caps_exceededrequired

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.

nullable
string
voiced_emptyrequired

Nothing speakable is left in the text (only tags/pauses/stage directions). POST /orders with this text would return 422.

boolean
blocked_chunksrequired

Indexes of chunks whose text already tripped the content policy and sits in the blocklist. An empty array means no blocks.

array<integer>
has_blocked_contentrequired

Whether at least one chunk is blocked — the same thing as blocked_chunks !== [], as a boolean for convenience.

boolean
paragraphsrequired

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.

nullable
array<object>
array item
paragraph_indexrequired

The paragraph's index, zero-based.

integer
chunks_countrequired

How many chunks fall inside this paragraph (a long paragraph can span several chunks).

integer
first_chunk_indexrequired

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.

integer
text_lengthrequired

The raw length of the paragraph text in characters, including tags/pauses.

integer
effective_lengthrequired

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.

integer
price_unitsrequired

The paragraph's cost in tokens.

integer
paragraphs_countrequired

Number of paragraphs, or null outside paragraph mode. Stays a number even when paragraphs is collapsed by the WS cap.

nullable
integer
cost_lmcoptional

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.

number
cost_displayoptional

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.

nullable
object
chunksrequired

Only the CURRENT page (see data.page), not the full list — use pagination (GET .../{previewId}?page=) for the rest.

array<object>
array item
indexrequired

The chunk's index across the whole text (does not reset at paragraph boundaries).

integer
textrequired

The chunk text exactly as the voice provider will receive it.

string
startrequired

The chunk's start offset (in characters) within the NORMALIZED input text — the same normalization POST /orders applies before splitting.

integer
endrequired

The chunk's end offset in the normalized text (half-open interval: end is exclusive).

integer
text_lengthrequired

The raw length of the chunk text in characters, INCLUDING tags/pauses.

integer
effective_lengthrequired

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).

integer
price_unitsrequired

The chunk's cost in tokens.

integer
paragraph_indexrequired

The index of the paragraph this chunk belongs to, or null outside paragraph mode.

nullable
integer
pagerequired

Pagination meta for the CURRENT page of data.chunks.

object
current_pagerequired

The current page number, 1-based.

integer
per_pagerequired

The page size (how many chunks were requested, or the default).

integer
totalrequired

Total chunks in the split (across all pages).

integer
has_morerequired

Whether more pages follow the current one.

boolean
202

Accepted for processing. The result arrives asynchronously.

Body example
"string"
Response fields
successrequired

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.

boolean
messagerequired

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.

string
datarequired

The response payload.

object
statusrequired

Always processing for a 202 response — the calculation is running in the background.

Valuesprocessing
string
preview_idrequired

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.

string
401

The key is missing, unknown, disabled or expired.

Analyse this response
Body example
{
  "success": false,
  "message": "API key is missing. Provide it in the X-API-KEY header.",
  "data": null
}
Response fields
successrequired

Always false in error responses.

boolean
messagerequired

Error text in the key's language (set through the X-Locale header).

string
datarequired

Always null when the request is rejected before processing.

nullable
null
403

The key lacks the required permission, or the object belongs to another user.

Analyse this response
Body example
{
  "success": false,
  "message": "This API key does not have permission to perform this action."
}
Response fields
successrequired

Always false in error responses.

boolean
messagerequired

Error text in the key's language (set through the X-Locale header).

string
422

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).

Analyse this response
Body example
{
  "message": "string",
  "errors": {
    "key": [
      "string"
    ]
  }
}
Response fields
messagerequired

Errors overview.

string
errorsrequired

A detailed description of each field that failed validation.

object
*optional
arbitrary keys
array<string>
429

Limit exceeded: either the key's request rate or its token quota. Retry no earlier than Retry-After.

Analyse this response
Body example
{
  "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
}
Response fields
successrequired

Always false in error responses.

boolean
messagerequired

Error text in the key's language (set through the X-Locale header).

string
reasonrequired

Machine-readable failure reason — branch on this, not on the message text.

string
error_coderequired

The same machine-readable code as in reason — a second name for the field. The values always match; branch on either.

string
retry_afteroptional

How many seconds to wait before retrying (the same value is in the Retry-After header).

nullable
integer

Events this produces

Progress arrives over WebSocket: polling the order in a loop is not required. Subscribing needs the same scope as reading orders.

Feed this page to an AI

Open .mdllm.md

spec 0.0.1