---
title: Streaming audio
description: Play streamed audio, save WAV files, and handle interruptions.
---

`POST /v1/audio/speech` streams audio bytes. Read the response incrementally; its total length is unknown when streaming begins.

## Text input in a voice agent

Each request accepts one complete `input` string. Streaming applies to the audio response; this endpoint does not accept an ongoing text stream over WebSocket.

When your LLM emits text incrementally, let your agent framework group it into suitable phrases before sending speech requests. Very short fragments can interrupt the rhythm of the spoken reply; waiting for the entire answer can delay it. Test that tradeoff with your own dialogue. The [LiveKit configuration](/docs/integrations#livekit-tts-configuration) uses the framework's existing text segmentation and playback.

If the caller interrupts, stop playback and cancel the active synthesis through your framework. Cancellation does not undo audio already delivered or its charge; see [interruptions and billing](#interruptions-and-billing).

## Audio formats

| `response_format` | Content type | Audio |
| --- | --- | --- |
| `pcm` (default) | `audio/pcm` | Signed 16-bit little-endian PCM, mono, without a header. |
| `wav` | `audio/wav` | The same PCM with a 44-byte WAV header. |

The default sample rate is 24,000 Hz; `sample_rate` accepts 8,000–48,000 Hz. Set `response_format` explicitly when using an SDK that defaults to another format.

## Response headers

| Header | Meaning |
| --- | --- |
| `Content-Type` | Audio format, or `application/json` for errors before streaming. |
| `X-Sample-Rate` | Effective sample rate in Hz. |
| `X-Channels` | `1` for mono. |
| `X-Request-Id` | ID for [operation status](/docs/operations) and support. |
| `Cache-Control` | `no-store`. |

`X-Audio-Sample-Rate` and `X-Audio-Channels` contain the same values as their shorter equivalents.

## Playing raw PCM

Configure your player for signed 16-bit little-endian mono audio at `X-Sample-Rate`. Network chunks can split a two-byte sample: keep any final odd byte and prepend it to the next chunk. A small playback buffer helps absorb network jitter.

## Playing streaming WAV (`audio/wav`)

The first 44 bytes are the WAV header; they may arrive across several network chunks. Both length fields use `0xFFFFFFFF` because the final size is not yet known. Use a streaming-aware player.

After [verifying completion](#interruptions-and-billing), finalize the file saved in the [Quickstart](/docs/quickstart) with FFmpeg:

```bash
ffmpeg -i speech.partial.wav -c:a copy speech-final.wav
```

This updates the header without re-encoding the audio. To finalize it in your own code:

1. Calculate `data_bytes = file_size - 44`.
2. Write `data_bytes + 36` at byte offset 4 as a 32-bit little-endian integer.
3. Write `data_bytes` at byte offset 40 in the same format.

For a playable file with completion checks, use the [Python quickstart](/docs/quickstart#python-save-a-playable-wav). The [agent skill](/docs/agent-skill#verify-the-recording) creates finalized WAV files; verify the recording before using it.

## Interruptions and billing

- Speech is charged for its full input text once audio is delivered, including partial streams and cancellations.
- Requests delivering no audio are not charged. A WAV header alone does not count as audio.
- Charges settle when the operation ends, which may be after a disconnect.

Failures before streaming return a JSON error. Failures after headers interrupt the stream, so HTTP `200` alone does not prove completion.

Save `X-Request-Id`, keep interrupted audio marked as partial, and check [operation status](/docs/operations) before retrying. A completed operation reports `completion_bytes`; compare it with received PCM bytes, excluding the WAV header. Save API audio in your application. The [saved-recording API](/docs/generation-history#saved-recording-api) can retrieve only recordings that were successfully saved; it cannot recover an unsaved recording, and an interrupted response does not guarantee that a complete recording exists.
