> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sabi-tts-app.dev.neuralace.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Stream speech

> Low-latency, sentence-by-sentence audio over WebSocket.

```
wss://sabi-tts.dev.neuralace.co/v1/audio/speech/stream
```

The streaming endpoint synthesizes audio **one sentence at a time** and sends each
sentence as a binary WebSocket frame as soon as it is ready. Begin playback on the first
frame for the lowest perceived latency — ideal for conversational agents.

## Protocol

<Steps>
  <Step title="Connect">
    Open a WebSocket to `/v1/audio/speech/stream` with your key in the header:
    `Authorization: Bearer sk_…`.
  </Step>

  <Step title="Start a session">
    Send a `start` control frame (JSON text frame):

    ```json theme={null}
    { "type": "start", "voice": "capella", "response_format": "wav" }
    ```

    The server replies with `{ "type": "ready" }` once the session is open.
  </Step>

  <Step title="Send text">
    After `ready`, send one or more `text` frames, then a `done` frame:

    ```json theme={null}
    { "type": "text", "text": "Hello. This streams sentence by sentence." }
    ```

    ```json theme={null}
    { "type": "done" }
    ```
  </Step>

  <Step title="Receive audio">
    The server sends **binary frames** — one complete audio buffer per sentence — followed
    by `{ "type": "session.done" }` when synthesis is finished.
  </Step>
</Steps>

## Frame reference

### Client → server

| Frame                                            | Purpose                                     |
| ------------------------------------------------ | ------------------------------------------- |
| `{"type":"start","voice":…,"response_format":…}` | Opens the session. Must be the first frame. |
| `{"type":"text","text":…}`                       | Text to synthesize (metered per character). |
| `{"type":"done"}`                                | No more text; finish and close the session. |

### Server → client

| Frame                          | Purpose                                                   |
| ------------------------------ | --------------------------------------------------------- |
| `{"type":"ready"}`             | Session open — safe to send `text` frames.                |
| binary frame                   | One sentence of audio in the requested `response_format`. |
| `{"type":"session.done"}`      | All audio delivered.                                      |
| `{"type":"error","message":…}` | Something went wrong (bad frame, quota, upstream error).  |

<Note>
  Send the `start` frame before anything else — `text` frames sent first are rejected with
  an `error` frame. Quota and rate limits apply per `text` frame, the same as HTTP requests.
</Note>

## Example

```python Python theme={null}
# pip install websockets
import asyncio, json, os, websockets

async def main():
    url = "wss://sabi-tts.dev.neuralace.co/v1/audio/speech/stream"
    headers = [("Authorization", "Bearer " + os.environ["SABI_API_KEY"])]
    async with websockets.connect(url, additional_headers=headers, max_size=64 * 1024 * 1024) as ws:
        await ws.send(json.dumps({"type": "start", "voice": "capella", "response_format": "wav"}))
        idx = 0
        async for msg in ws:
            if isinstance(msg, (bytes, bytearray)):       # per-sentence audio frame
                open(f"sentence_{idx}.wav", "wb").write(msg); idx += 1
                continue
            evt = json.loads(msg)
            if evt["type"] == "ready":
                await ws.send(json.dumps({"type": "text", "text": "Hello. This streams sentence by sentence."}))
                await ws.send(json.dumps({"type": "done"}))
            elif evt["type"] == "session.done":
                break

asyncio.run(main())
```
