Synthesize a dialogue

POSThttps://api.paxalabs.com/v1/tts/dialogue

POST /v1/tts/dialogue speaks a script of lines, each in its own voice, as one streamed audio file. One charge for the whole script; a failure keeps only the price of the lines delivered whole.

View as Markdown
curl -X POST https://api.paxalabs.com/v1/tts/dialogue \
  -H "Authorization: Bearer $PAXA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "paxa-tts-flash-v2",
  "lines": [
    {
      "text": "สวัสดีครับ ยินดีต้อนรับสู่ Paxa Labs",
      "voice": "khanomkrok"
    },
    {
      "text": "สวัสดีค่ะ วันนี้ให้ช่วยเรื่องอะไรดีคะ",
      "voice": "nomyen"
    }
  ]
}' \
  --output dialogue.ndjson
{"type":"chunk","line":0,"audio":"//NkxAAaOAAAAAABLBQAAAAAAABVVVVV..."}
{"type":"chunk","line":1,"audio":"//NkxAAaOAAAAAABLBQAAAAAAABVVVVV..."}
{"type":"done","lines":[{"start":0,"end":2.35},{"start":2.65,"end":4.9}]}

The Multi-speaker dialogue guide covers the stream, pauses between lines, and billing. A line a pre-charge rule refuses answers with its index in the problem's line, with no charge.

Headers#

authorizationBearer pxa_...Required

Authorization: Bearer pxa_... (recommended).

x-api-keypxa_...

Alternative to the Authorization header. Ignored when Authorization is present.

idempotency-keystring

Makes a retry safe: two requests carrying the same key are charged once. One request per key runs at a time. A concurrent duplicate answers 409 idempotency_in_flight. Reuse a key only to retry an identical request. A changed payload is rejected with 422. Accepts up to 200 printable ASCII characters other than space.

1 to 200 characterspattern ^[!-~]+$

Authentication

Body#

linesarray of objectsRequired

The lines in speaking order, from 1 to 100, each with its own voice. Together they hold at most 20,000 characters, or the request answers 400 request_too_large. The request bills their characters together, with the 0.1-credit minimum applied once.

Show properties
lines[].textstringRequired

The text this line speaks. It follows every text rule of POST /v1/tts, tags included, but has no ceiling of its own: the lines share one total.

at least 1 characterExample "สวัสดีครับ ยินดีต้อนรับสู่ Paxa Labs"
lines[].voicestringRequired

The voice that speaks this line, any voice id POST /v1/tts takes, for example "khanomkrok". A designed voice bills its own line at 1.25x the model's per-character rate, or 1.5x with timestamps.

1 to 100 charactersExample "khanomkrok"
modelstringRequired

TTS model id, for example paxa-tts-flash-v2. GET /v1/models lists the served catalog.

1 to 100 charactersExample "paxa-tts-flash-v2"
format"mp3" or "opus" or "wav" or "pcm" or "mulaw" or "alaw"

Audio format. mp3 is compressed and the default. opus is Ogg Opus, 48 kbps on Paxa TTS Flash and 64 on Paxa TTS Flash v2, the smallest, for streaming and web playback. wav is uncompressed 16-bit PCM under a RIFF header, the largest, for audio that feeds further processing. pcm is those samples with no header, 16-bit little-endian mono, for voice agents and phone systems that take raw samples. Both are 24 kHz unless sample_rate says otherwise. mulaw and alaw are G.711 mu-law and A-law with no header, one byte per sample at 8 kHz, for phone lines. Paxa TTS Flash v2 serves them, and Paxa TTS Flash answers unsupported_format.

Default "mp3"
sample_rate8000 or 16000 or 22050 or 24000 or 32000 or 44100 or 48000

Sample rate of pcm and wav, in Hz. Paxa TTS Flash v2 serves 8000, 16000, 22050, 24000, 32000, 44100, 48000, and Paxa TTS Flash serves 24000 only. For any other format the value may only restate that format's own rate, which is 8000 for mulaw and alaw. A rate the model doesn't serve answers unsupported_format. A request costs the same at every rate.

Example 16000
gap_msinteger

Silence between one line and the next, in milliseconds. It never changes the charge. The gap rounds to the nearest 20 ms in opus and to the nearest mp3 frame in mp3.

Default 300min 0max 5,000Example 300
timestamps"word" or "sentence" or "utterance"

Adds timing spans to the chunk events, measured from the start of the whole dialogue, and bills the request at 1.25x the model's per-character rate.

speednumber

Speaking rate, as a multiplier on the voice's own design speed. It runs from 0.5 to 1.5, and 1 is the voice as designed. Rate changes how the audio sounds. It does not change how many characters were sent. A request costs the same at every rate. Values round to the nearest 0.05. Timing spans are measured against the audio that was produced. They already carry the rate.

Default 1min 0.5max 1.5Example 1.1
language"auto" or "th" or "en" or "zh"

Sets how codes, digits, and symbols are read. Words keep the language of their script, and a number inside Thai or Chinese text keeps that text's language. Elsewhere, "th" reads them in Thai (AB2039 with Thai letter names and digits) and "en" in English. "zh" reads codes in English, a number next to Chinese characters in Mandarin, and other digits and symbols in Thai. "auto", the default, takes the language most of the text is written in, counting English only in words of three or more letters, and Thai on a tie. Under "auto", a bare number, AB2039, or 250 ml reads as Thai, and ISO9001 or 100 USD as English. Text with no Thai, Chinese, digits, or counted English word reads as English when a two-letter word has a lowercase letter, such as Hi or I'm OK. Pin "th" or "en" to choose how a short number or code reads. A request costs the same in every language. Every value but "auto" is a code GET /v1/voices reports as a voice's language. POST /v1/audio/speech ignores this field and reads every request as "auto".

Default "auto"Example "en"
temperaturenumber

Sets how much the pacing, pauses, breaths, and melody of a reading vary from one request to the next. It runs from 0 to 2, and 0.5 is the default. They vary least at 0 and most at 2. Paxa TTS Flash v2 reads it. Any other model accepts only a value that rounds to 0.5, and any other answers validation. A request costs the same at every temperature. Values round to the nearest 0.1. POST /v1/audio/speech and the start frame of WS /v1/tts/live read it too.

Default 0.5min 0max 2Example 0.5

Response#

application/x-ndjson One JSON event per line, arriving as audio is synthesized. Decode each chunk's audio and concatenate the bytes in arrival order for one file in the requested format; a wav stream states its sizes as 0xFFFFFFFF, read to the end. The stream ends with a done event or an error event. An error after a failure states the lines delivered whole and the credits kept for them. An error after a charge refused once the audio began charges nothing. A stream read so slowly that the request runs out of time closes with neither event, and the charge stands.

Response headers

HeaderDescription
x-request-idIdentifies the request for support and log correlation. Sent on every response, errors included, except the catalog (GET /v1/models, /v1/voices, /v1/audio/models, and /v1/audio/voices). Name it in request_ids when you send feedback about the request.
x-credits-chargedCredits charged for this request, exact to a hundredth. An idempotent replay reports the original charge. A streamed speech request whose charge is refused after its audio began ends early and is not charged. Sent on audio responses and on the OpenAI-compatible transcription endpoint, whose body follows OpenAI's shape and carries no usage object. POST /v1/translate, /v1/ocr, /v1/extract, /v1/stt, and /v1/voices/design report the charge in the usage object of the body instead.
x-ratelimit-limitRequests per minute the plan allows.
x-ratelimit-remainingRequests left in the current minute window.
x-ratelimit-resetUnix time in seconds when the current window resets.

chunkOne NDJSON line of the dialogue. The decoded chunks, concatenated in arrival order, are one file in the requested format.

type"chunk"Required
lineintegerRequired

The index in lines of the line this audio belongs to. Lines arrive in order, and the silence before a line arrives with it.

audiostringRequired

Audio chunk in the requested container, base64-encoded. Decode each chunk and concatenate the bytes in arrival order; never concatenate the base64 strings themselves. Empty on a line that only carries spans completed after the last audio.

spansarray of objects

Present when timestamps is set. Spans newly completed by the audio delivered so far, measured from the start of the dialogue.

Show properties
spans[].textstringRequired

The spoken input text this span covers. An emotion tag takes no span.

spans[].startnumberRequired

Where the span begins in the audio, in seconds.

spans[].endnumberRequired

Where the span ends in the audio, in seconds.

doneThe final NDJSON line of a dialogue that finished.

type"done"Required
linesarray of objectsRequired

Each line's place in the audio, in the order of the request's lines.

Show properties
lines[].startnumberRequired

Where the line's speech begins, in seconds.

lines[].endnumberRequired

Where the line's speech ends, in seconds.

granularity"word" or "sentence" or "utterance"

Present when timestamps is set: the granularity.

span_countinteger

Present when timestamps is set: how many spans the chunk lines delivered in total.

errorDelivery stopped after it began. This is the final line of the stream.

type"error"Required
errorobjectRequired

RFC 9457 problem details at the minimum shape, served with content type application/problem+json. Responses carry no prose: the errors reference is the documentation for every code. One code, 400 schema_invalid, adds two fields so a caller can fix the schema without guessing: path and reason. A dialogue error one line caused adds line.

Show properties
error.titlestringRequired

Stable machine-readable error code, for example "insufficient_credits". Branch on this field; the errors reference documents every code's meaning and the recommended handling.

error.statusintegerRequired

HTTP status code, mirrored from the response.

error.pathstring

Present on 400 schema_invalid alone. Names the schema field the reason applies to, in dot form ("line_items[].amount"), or an empty string for the schema as a whole.

error.reasonstring

Present on 400 schema_invalid alone. One stable code from the schema reasons list in the Document Extraction reference, naming what is wrong at path.

error.lineinteger

Present when one line of a POST /v1/tts/dialogue request caused the error: that line's index in lines, counting from 0.

linesarray of objectsRequired

Each line delivered whole before the failure, with its place in the audio, in order. Their audio is the chunks whose line is below this count. Empty when the charge was refused.

Show properties
lines[].startnumberRequired

Where the line's speech begins, in seconds.

lines[].endnumberRequired

Where the line's speech ends, in seconds.

creditsnumberRequired

Credits the request keeps, exact to a hundredth: what the lines delivered whole cost as a dialogue of their own, the rest refunded. 0 when nothing was delivered whole or the charge was refused; an idempotent replay reports the original charge.

Errors#

400validationThe request body, query, or headers failed validation against the endpoint schema, or broke a rule the endpoint's reference states. A field the schema refused adds two fields to the problem body. path names the first failing field by location and name in dot form, such as body.voice or headers.idempotency-key. A body that could not be read gives the path body alone. reason is one stable code from the validation reasons list.
413body_too_largeThe request body exceeds the size the endpoint reads, before any of it was read. Nothing was charged.
400unknown_modelThe model field does not name a served model.
400unsupported_formatThe model does not serve this format, or this format at the requested sample rate.
400unknown_voiceThe voice id names no voice on the model's roster and no designed voice this account holds.
400request_too_largeThe request's billable characters exceed its total ceiling. Translation bills every field: instructions, context, examples, do_not_translate strings, and the glossary count toward the ceiling alongside the text. A dialogue's lines together hold at most 20,000 characters.
400tag_invalidThe text holds a tag the model refuses. On Paxa TTS Flash v2, the bracket asks for what the model does not do, such as an accent, singing, or a sound outside the supported list. It can also hold no letters or carry a weight that is not a number. On a Paxa TTS Flash voice that takes emotion tags, the bracketed word is outside the tag list, or its weight is not a number. Nothing was charged.
401unauthorizedThe request carried no API key, or the key is invalid or disabled.
402insufficient_creditsThe account does not have enough credits for this request. Nothing was charged.
403key_limitThis API key reached its spending cap. Nothing was charged.
409idempotency_in_flightAnother request with this Idempotency-Key is in flight right now.
409idempotency_refundedThe original request under this Idempotency-Key failed and was refunded, in whole or in part.
422idempotency_mismatchThis Idempotency-Key was already used for a different request.
422unspeakable_textThe text contains nothing the model can voice: only tags, emoji, punctuation, or a script it does not speak. A text of tags alone is refused before the charge; any other is charged and refunded, except the lines a dialogue delivered whole before it.
429rate_limitedRequests per minute for the plan are exhausted. One window covers the whole account, across every product and every key.
429concurrency_limitedThe account holds the plan's full count of concurrent requests for this product. Nothing was charged. Each product is limited separately, and an open live connection holds one slot of its product for its lifetime.
500internalRequest state was inconsistent on the server.
502provider_errorModel inference failed after the request was charged.
503provider_unavailableThe model behind this endpoint is not available right now. Nothing was charged.