# Stream audio as it is generated

Streaming delivers audio in chunks while synthesis is still running, cutting time to first sound. Set stream: true, or use the OpenAI-compatible endpoint where streaming is the default.

Streaming delivers audio in chunks while synthesis is still running. Set `stream: true` on [POST /v1/tts](https://paxalabs.com/docs/api/text-to-speech) and the first bytes arrive while the rest is being synthesized, which cuts time to first sound for real-time uses like voice agents and live narration. On the [OpenAI-compatible endpoint](https://paxalabs.com/docs/api/speech), streaming is the default. Streaming here is one-way delivery of one request; for a two-way connection that accepts text incrementally, see [Live streaming](https://paxalabs.com/docs/live-streaming).

curl:

```bash
curl -N -X POST https://api.paxalabs.com/v1/tts \
  -H "Authorization: Bearer $PAXA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "สวัสดีครับ ยินดีต้อนรับสู่ Paxa Labs", "voice": "khanomkrok", "model": "paxa-tts-flash-v2", "stream": true}' \
  --output speech.mp3
```

TypeScript:

```typescript
const response = await fetch("https://api.paxalabs.com/v1/tts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PAXA_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    text: "สวัสดีครับ ยินดีต้อนรับสู่ Paxa Labs",
    voice: "khanomkrok",
    model: "paxa-tts-flash-v2",
    stream: true,
  }),
});

if (!response.ok || !response.body) {
  throw new Error(`TTS failed: ${response.status}`);
}

// Chunks arrive while synthesis is still running.
for await (const chunk of response.body) {
  player.feed(chunk);
}
```

Python:

```python
import os
import requests

response = requests.post(
    "https://api.paxalabs.com/v1/tts",
    headers={"Authorization": f"Bearer {os.environ['PAXA_API_KEY']}"},
    json={"text": "สวัสดีครับ ยินดีต้อนรับสู่ Paxa Labs", "voice": "khanomkrok", "model": "paxa-tts-flash-v2", "stream": True},
    stream=True,
)
response.raise_for_status()

with open("speech.mp3", "wb") as f:
    for chunk in response.iter_content(chunk_size=None):
        f.write(chunk)
```

## Buffered and streamed delivery

|  | Buffered (`stream: false`) | Streamed (`stream: true`) |
| --- | --- | --- |
| Delivery | Complete body with a `content-length` | Chunked as synthesis proceeds |
| First byte | After synthesis finishes | As soon as the first chunk exists |
| Failure after start | Clean 502 with a problem body | The stream truncates; no trailing error body |

## Consume the stream

Feed chunks to the player or socket as they arrive. Buffer the whole body only when writing a file. The charge happens before synthesis and `x-credits-charged` is a response header. The exact cost is known from the first byte.

With [`timestamps`](https://paxalabs.com/docs/timestamps) set, a streamed response switches from raw MP3 chunks to NDJSON events that carry audio and timing together; the event shapes are in the [reference](https://paxalabs.com/docs/api/text-to-speech).

## Handle failures

The response commits with the first chunk. Any failure before the first byte answers a clean [502 provider_error](https://paxalabs.com/docs/errors#provider_error) and the charge is refunded. If synthesis fails mid-stream, the connection ends early: treat an unexpectedly closed stream as a failed request. The refund still runs automatically.

Cancelling the request from your side keeps the charge: the audio was being delivered as paid for. Prefer buffered delivery when you need all-or-nothing semantics, for example when writing files for later use.

> Pair streaming with an [Idempotency-Key](https://paxalabs.com/docs/idempotency). If the connection drops, a retry under the same key serves the request again without a second charge. If synthesis itself failed and was refunded, the retry answers [409 idempotency_refunded](https://paxalabs.com/docs/errors#idempotency_refunded) and needs a fresh key.
