---
title: "Synthesize a multi-speaker dialogue"
method: POST
path: "/api/v1/dialogue"
tags: ["Public API v1"]
---

# Synthesize a multi-speaker dialogue

`POST /api/v1/dialogue`

Synthesizes an ordered list of turns (1–50), each with its own voice and emotion, into a single concatenated WAV. Tokens are deducted upfront, billed by the submitted character count (minimum 50, × the AI surcharge only when you opt in with `aiRefine: true`), and refunded if synthesis fails. With `aiRefine: true` the dialogue is moderated and each turn is pronunciation-normalized (formulas, acronyms, mixed English), billed with the surcharge; by default (`false`) turns are synthesized as provided — no content check, no normalization, no surcharge. Pass `engine` ("v3" default, "v4" premium — billed at its own multiplier) to choose the TTS engine; every turn's voice must belong to it. Returns a presigned `audioUrl`.

## Request body

- PublicDialogueRequestDto
  - `turns` PublicDialogueTurnDto[], required — Ordered list of dialogue turns (1–50).
    - `speaker` string, required — Speaker label (display/metadata only).
    - `voiceId` string, required — Preset voice ID from GET /v1/voices.
    - `text` string, required — Vietnamese text for this turn (max 5000 chars).
    - `emotion` 'natural' | 'storytelling' | 'tin_tuc' | 'tu_nhien' | 'doc_truyen' | 'emotion_0' | 'emotion_1' | 'emotion_2' | 'emotion_3' | 'emotion_4' | 'emotion_5' | 'emotion_6' | 'emotion_7' | 'emotion_8' | 'emotion_9' | 'emotion_10' | 'emotion_11' | 'emotion_12' | 'emotion_13' | 'emotion_14' | 'emotion_15' | 'emotion_16' | 'emotion_17' | 'emotion_18' | 'emotion_19'
  - `pauseBetweenTurnsMs` number — Silence gap between turns (ms, 0–5000).
  - `temperature` number — Sampling temperature applied to all turns (0.0–2.0).
  - `aiRefine` boolean — AI text refinement. Defaults to FALSE on the public API: the text is synthesized as submitted, with no AI moderation and no pronunciation normalization, billed at the plain per-character rate. Set true to run the same AI step the web app uses — formulas, acronyms and mixed-in English are read correctly and the content is checked — billed with the AI surcharge and one extra model round-trip of latency. Deterministic text preparation is applied either way. Applies across every turn.
  - `engine` 'v3' | 'v4' — TTS engine — "v3" (default) or "v4" (premium, billed at its own multiplier). Every turn's voice must belong to this engine (see GET /v1/voices?engine=…). Omit to use the platform default.

## Response `201`

The concatenated dialogue, as a presigned WAV URL.

- PublicDialogueResponseDto
  - `success` boolean, required
  - `turnsCount` number, required — Number of turns synthesized.
  - `speakers` string[], required — Unique speaker labels in order of first appearance.
  - `audioUrl` string, required — Presigned S3 download URL for the concatenated WAV.
  - `audioUrlExpiresIn` number, required — Seconds until audioUrl expires.
  - `duration` number, required — Duration of the concatenated audio (seconds).
  - `tokenCost` number, required — Tokens deducted for this request.

## Other responses

- `400` — Empty or over-long turn list, an unknown voice, or a voice from another engine.
- `401` — API key missing, malformed, or revoked.
- `403` — Your plan does not include dialogue or this engine, or the grant is out of tokens / expired.
- `422` — Content refused by moderation. Only reachable with `aiRefine: true`.
- `429` — Rate limit or token quota exceeded. When the limit came from the application the response carries `Retry-After` (seconds) and the `X-RateLimit-*` headers and is counted against your API key; a 429 with none of those headers came from the edge proxy and is counted against your source address, shared with every other key calling from it. Back off on either.

---

[API](https://skmtc.dev/vieneu/apis/vieneu-api.md) · [All operations](https://skmtc.dev/vieneu/apis/vieneu-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/vieneu/vieneu-api/revisions/11c8166a6c4d/schema)
