> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://developers.deepgram.com/docs/flux-tts/server-messages/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.deepgram.com/_mcp/server. # Server Messages The server replies to your [Client Messages](/docs/flux-tts/client-messages) with JSON text frames interleaved with binary audio frames. This page documents every server-to-client message and the error/warning code reference. **Cadence at a glance:** `SpeechStarted` marks the start of a turn and `SpeechMetadata` marks its end — **every audio frame for a turn arrives between the two.** `Flush` is your signal that all of the turn's text has been sent; once it arrives, `SpeechMetadata` is our signal that all of the turn's audio has been sent. ## Connected Sent immediately on a successful connection. Successor to v1/speak's `Metadata` message. ```json { "type": "Connected", "request_id": "550e8400-e29b-41d4-a716-446655440000", "model_name": "flux-haley-en", "model_version": "2026.06.01", "model_uuids": ["b3e47c20-9f81-4a2e-bd15-8d7c6e2a1f09"] } ``` | Field | Type | Description | | --------------- | ---------------------- | ------------------------------------------------------------------------------------------------------- | | `request_id` | string (UUID) | ID of the `/v2/speak` request. | | `model_name` | string | Resolved model name (e.g. `flux-haley-en`). | | `model_version` | string | Resolved model version. | | `model_uuids` | array of string (UUID) | Resolved model UUIDs. A list, because a resolved model may be backed by more than one underlying model. | ## SpeechStarted Emitted at the **start of each new turn**, before audio streaming begins. Carries the server-assigned `speech_id`. Fires **once per turn**. ```json { "type": "SpeechStarted", "speech_id": "dg_sp_a1b2c3d4e5f6" } ``` The `speech_id` is a server-minted identifier of the form `dg_sp_<12 hex digits>`. It is informational — useful for correlating logs. **When a turn becomes active.** A `Speak` received while idle (the first `Speak`, or the first after a `SpeechMetadata`) starts a new active turn, and we emit `SpeechStarted`. There is only ever **one active turn**: a `Speak` received *after* you've `Flush`ed the active turn but *before* its `SpeechMetadata` starts a **pending** turn. The active turn stays active until its `SpeechMetadata` — if you haven't received `SpeechMetadata`, the turn is still being synthesized — at which point the next pending turn becomes active and gets its own `SpeechStarted`. ## SpeechMetadata Emitted once per turn, after we've sent **all** of the turn's audio — our signal that synthesis for the turn is complete and no more audio is coming for it. It follows your `Flush`, which tells us no more text is coming for the turn. Reports billing and timing for the completed turn. ```json { "type": "SpeechMetadata", "speech_id": "dg_sp_a1b2c3d4e5f6", "audio_duration_ms": 3200, "input_character_count": 47, "billable_character_count": 47, "controls_applied": { "pronunciations_applied": 0, "breaks_applied": 0, "pronunciation_warnings": 0 } } ``` | Field | Type | Description | | -------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `speech_id` | string | Server-assigned turn identifier. Informational. | | `audio_duration_ms` | integer | Total audio duration produced for this turn, in milliseconds. | | `input_character_count` | integer | Raw input character count for this turn, before text normalization. | | `billable_character_count` | integer | Billable character count for this turn — the input minus stripped inline-control characters; always ≤ `input_character_count`. | | `controls_applied` | object | Counts of the inline controls the server acted on this turn: `pronunciations_applied` (pronunciation overrides that took effect), `breaks_applied` (pauses that took effect), and `pronunciation_warnings` (entries that failed validation). A pronunciation override that triggers an IPA warning is still applied best-effort and counted in `pronunciations_applied`; the warning is reported separately. `breaks_applied` is always `0` on the WebSocket, where inline pause is not supported. | > **Info** > > **Per-turn vs. cumulative.** Per-turn counts are reported here, once per turn (at `Flush`). Cumulative totals are reported once, at session end, in [`SessionMetadata`](#sessionmetadata) as `total_*`. ## SpeechInterrupted Sent in response to an [`Interrupt`](/docs/flux-tts/client-messages#interrupt). Reports exactly what the user heard and what they didn't, plus a nested `metadata` block carrying the same shape as a standalone `SpeechMetadata` body. ```json { "type": "SpeechInterrupted", "audio_played_ms": 2340, "text_spoken": "Sure, I can help you cancel your subscription.", "text_remaining": " Let me pull up your account.", "metadata": { "speech_id": "dg_sp_a1b2c3d4e5f6", "audio_duration_ms": 2340, "input_character_count": 47, "billable_character_count": 47, "controls_applied": { "pronunciations_applied": 0, "breaks_applied": 0, "pronunciation_warnings": 0 } } } ``` | Field | Type | Description | | ----------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `audio_played_ms` | integer | How much audio the client had played when the interrupt landed, in milliseconds **from the start of the session**. Echoes `Interrupt.playback_offset` when supplied; otherwise the server's own count of audio generated so far. When you interrupt without an offset, use this as the baseline your next `playback_offset` must advance past. | | `text_spoken` | string (optional) | The portion of the turn's text the user heard, aligned against `audio_played_ms`. **Omitted when the `Interrupt` carried no `playback_offset`.** | | `text_remaining` | string (optional) | The portion the user did not hear. Omitted under the same condition. | | `metadata` | object | Per-turn billing/timing for the interrupted turn — the same body as a standalone [`SpeechMetadata`](#speechmetadata). | > **Info** > > **One event per interrupted turn.** On `Interrupt`, only `SpeechInterrupted` fires — the server does not also emit a standalone `SpeechMetadata`. Read `SpeechMetadata.` for completed turns and `SpeechInterrupted.metadata.` for interrupted ones. Feed `text_spoken` back into your LLM context so the next turn doesn't repeat what the user already heard. ## Flushed Emitted when the turn's buffer has actually been flushed after a manual [`Flush`](/docs/flux-tts/client-messages#flush) — **not** on receipt of the `Flush`, and it can be held back behind earlier pending turns. The turn's `SpeechMetadata` follows. ```json { "type": "Flushed", "speech_id": "dg_sp_a1b2c3d4e5f6" } ``` ## SessionMetadata Final server message before the WebSocket closes. Reports cumulative session totals — the sum across all turns. ```json { "type": "SessionMetadata", "total_audio_duration_ms": 184500, "total_input_character_count": 4280, "total_billable_character_count": 4280 } ``` An `Interrupt` rebases `total_audio_duration_ms` onto the audio the client actually played — audio generated past the interrupt point stops counting toward the session, so the total can decrease after a barge-in. This is the one place cumulative totals are reported. Combined with the per-turn numbers in `SpeechMetadata`, you get clean reconciliation: per-turn for granular tracking, plus a final authoritative total. ## ConfigureSuccess / ConfigureFailure ### ConfigureSuccess Sent when a [`Configure`](/docs/flux-tts/client-messages#configure) message is accepted. Echoes the applied configuration. Fires on receipt and validation, not on application — the change takes effect at the next segment boundary. ```json { "type": "ConfigureSuccess", "applied": { "speed": 1.15 } } ``` ### ConfigureFailure Sent when a `Configure` message is rejected. The failing message has no effect — the prior configuration is retained and synthesis continues uninterrupted. ```json { "type": "ConfigureFailure", "code": "SPEED_OUT_OF_RANGE", "field": "speed", "value": 3.5, "description": "speed must be between 0.5 and 1.5 in 0.05 increments" } ``` `field` and `value` identify the rejected field and the value you sent; they are present only when the failure is tied to a specific field/value. See the [ConfigureFailure codes](#configurefailure-codes) below. ## Warning Informational message; synthesis continues and the connection is unaffected. Every warning carries a `code` and a human-readable `description`. ```json { "type": "Warning", "code": "NO_ACTIVE_SPEECH", "description": "There is no active turn. The request will be ignored." } ``` See the [warning codes](#warning-codes) below. Warnings are not rate-limited — every occurrence is emitted. ## Error A fatal, server-originated error. Unlike a `Warning`, an `Error` is always followed by a WebSocket close. ```json { "type": "Error", "code": "MESSAGE-0000", "description": "The message could not be parsed." } ``` See the [error codes](#error-codes) below. ## Warning codes All warning codes follow Deepgram's `SCREAMING_SNAKE_CASE` convention; the session stays open in every case. **Turn-scoped:** | Code | Trigger | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `NO_ACTIVE_SPEECH` | A `Flush` arrived with no active turn. The request is ignored. | | `NO_SYNTHESIZABLE_TEXT` | The turn's text was entirely whitespace or punctuation. No audio is produced; the turn completes with a zero-duration `SpeechMetadata`. | | `SYNTHESIS_RETRYING` | A synthesis request to the model failed and is being retried. The session continues; a fatal `Error` is sent only if retries are exhausted. | **Inline controls and markup** (the `BREAK*` codes are reserved: inline pause is batch-only, and a pause marker on the WebSocket fails the connection with `DATA-0002`): | Code | Trigger | | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `INPUT_MARKUP_STRIPPED` | Recognized SSML / competitor markup was stripped from `Speak.text` before synthesis. See [Markup handling](/docs/flux-tts/client-messages#markup-handling). | | `PRONUNCIATION_WARNINGS` | One or more inline pronunciation strings failed IPA validation. The overrides are still applied best-effort and counted in `pronunciations_applied`; check the output by ear. | | `PRONUNCIATION_TOO_LONG` | A pronunciation's IPA string exceeded the maximum length and was dropped. | | `PRONUNCIATIONS_LIMIT_EXCEEDED` | More than 500 pronunciation controls in one request. The offending text is dropped. | | `BREAKS_LIMIT_EXCEEDED` | More than 8 pause controls in one request, or two pauses with no intervening text. The offending text is dropped. | | `BREAK_TOKENS_OUT_OF_RANGE` | One or more pause tokens specified an out-of-range duration and were ignored. | | `BREAK_TOKENS_WITH_INVALID_INCREMENTS` | One or more pause tokens specified a duration off the `0.1s` increment and were ignored. | **Interrupt-scoped** (in every case the `Interrupt` is ignored and no `SpeechInterrupted` is sent): | Code | Trigger | | -------------------------- | ---------------------------------------------------------------------------------------------- | | `NO_AUDIO_GENERATED` | The `Interrupt` arrived before the session produced any audio — there is nothing to interrupt. | | `INTERRUPT_IN_PROGRESS` | An earlier `Interrupt` is still being processed. At most one is handled at a time. | | `INVALID_INTERRUPT_OFFSET` | The `playback_offset` did not advance past the position a prior interrupt established. | ## ConfigureFailure codes | Code | Trigger | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SPEED_OUT_OF_RANGE` | `speed` is outside the supported range (`0.5`–`1.5`). Prior config retained. | | `SPEED_INCREMENT_INVALID` | `speed` is inside the range but off the `0.05` increment. Prior config retained. | | `SPEED_NOT_SUPPORTED` | `speed` sent on a model/language that does not support runtime speed control. | | `CONTROL_COMBINATION_INVALID` | `speed` was set while a turn queued behind the active one carries a pronunciation control. Pronunciation and speed cannot be combined; flush that turn before setting `speed`. Pronunciations in the active turn do not block the change. Prior config retained. | ## Connection rejection codes An invalid `expressivity` value rejects the connection with an HTTP `400` before the WebSocket upgrade completes — no `Connected` message is sent. See [Expressivity](/docs/tts-expressivity). | Code | Trigger | | -------------------------------- | --------------------------------------------------- | | `EXPRESSIVITY_OUT_OF_RANGE` | `expressivity` was outside the `-2` to `2` range. | | `EXPRESSIVITY_INCREMENT_INVALID` | `expressivity` was not a whole number (e.g. `1.5`). | ## Error codes Every error is fatal and is followed by a WebSocket close frame. Codes use Deepgram's `DOMAIN-NNNN` convention. | Code | Error | Trigger | | -------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `MESSAGE-0000` | Unparseable client message | A client message could not be parsed. | | `DATA-0000` | Invalid command | A message contained an invalid command. | | `DATA-0002` | Invalid inline control | `Speak.text` carried a malformed inline control, an inline pause marker (pause is batch-only; use [batch](/docs/flux-tts/batch)), or a pronunciation control on a session opened with a `speed` other than `1.0`, or after a `Configure` that set one (the two cannot be combined). `description` names the rule. A pronunciation that is merely invalid or over a limit produces a `Warning` instead. | | `BIG-0000` | Message too large | A message was too large to process. | | `NET-0000` | Internal server error | The server hit an unexpected condition. | | `NET-0001` | Failed to receive message | The server failed to receive a message. | | `NET-0002` | Failed to send message | The server failed to send a message. In practice you won't observe this — if the server can't send messages, it can't deliver this error either. | | `NET-0003` | Time limit exceeded | The session time limit (1 hour) was exceeded. | | `NET-0004` | Inactive client | No client message arrived within the 60s inactivity window; the session was closed. Send a WebSocket Ping or Pong to keep an idle session alive. | ## Related resources * [Client Messages](/docs/flux-tts/client-messages) — the messages these respond to * [The Speech Lifecycle](/docs/flux-tts/state) — how these events sequence across a turn * [Cross-Turn Context](/docs/flux-tts/context) — voice consistency across turns --- > The messages Flux TTS sends back on /v2/speak — Connected, lifecycle and metadata events, interruption and configuration responses, warnings, errors, and the code reference.