> ## Documentation Index
> Fetch the complete documentation index at: https://rimelabs-docs-coda-websocket-reference.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Protobuf quickstart

> Use rime-api Protobuf bindings with a standard WebSocket client and save Coda audio chunks as they arrive.

This Python example sends Protobuf messages with `rime.v1.binary` and writes PCM audio chunks to a file as they arrive. It needs no audio device. The endpoint, synthesis parameters, and lifecycle are the same as in the [JSON quickstart](/api-reference/coda/websockets-json).

## Prepare the client

Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and Python 3.11 or later. Create a [Rime API key](https://app.rime.ai/tokens) and set `RIME_API_KEY` in your server's environment.

Create a project and install the transport client and message definitions:

```bash theme={null}
uv init --python 3.11 coda-binary
cd coda-binary
uv add 'websockets>=15,<16' 'rime-api==0.0.1'
```

[`rime-api`](https://pypi.org/project/rime-api/0.0.1/) supplies generated Protobuf classes and schema files. Your application still uses a standard WebSocket client to connect, authenticate, send messages, and manage retries. The LiveKit plugin uses these same message classes.

## Stream audio to a file

Save this as `stream_binary.py`. It overwrites `output.pcm` in the current directory. Decode each server message as `WebSocketResponse`, then read its `audio` field. The full WebSocket message includes Protobuf metadata and cannot go directly to a player.

```python theme={null}
import asyncio
import os

from rime_api import text_to_speech_pb2 as proto
from websockets.asyncio.client import connect

URL = "wss://api.rime.ai/coda/ws"
CONTEXT_ID = "turn-1"


async def receive_event(connection):
    raw = await connection.recv()
    if not isinstance(raw, bytes):
        raise RuntimeError("Expected a Protobuf binary message")
    event = proto.WebSocketResponse.FromString(raw)
    if event.WhichOneof("payload") == "error":
        raise RuntimeError(f"Rime error: {event.error}")
    return event


async def send_sentences(connection):
    for sentence in ["Hello from Coda. ", "This audio uses the binary protocol. "]:
        request = proto.WebSocketRequest(context_id=CONTEXT_ID, text=sentence)
        await connection.send(request.SerializeToString())
    request = proto.WebSocketRequest(context_id=CONTEXT_ID)
    request.end.SetInParent()
    await connection.send(request.SerializeToString())


async def receive_audio(connection, output):
    while True:
        event = await receive_event(connection)
        if event.context_id != CONTEXT_ID:
            raise RuntimeError("Unexpected context ID")
        payload = event.WhichOneof("payload")
        if payload == "started":
            print("Request ID:", event.started.request_id)
        elif payload == "audio":
            output.write(event.audio)
        elif payload == "done":
            return
        elif payload == "cancelled":
            raise RuntimeError("Synthesis was cancelled")


async def main():
    headers = {"Authorization": f"Bearer {os.environ['RIME_API_KEY']}"}
    async with asyncio.timeout(120):
        async with connect(
            URL, additional_headers=headers, subprotocols=["rime.v1.binary"]
        ) as connection:
            if connection.subprotocol != "rime.v1.binary":
                raise RuntimeError("Binary subprotocol was not selected")
            event = await receive_event(connection)
            if event.WhichOneof("payload") != "ready" or event.ready.protocol != 1:
                raise RuntimeError("Expected WebSocket protocol version 1")
            request = proto.WebSocketRequest(
                context_id=CONTEXT_ID,
                start=proto.SynthesisRequest(
                    speaker="lyra",
                    language="en",
                    audio_parameters=proto.AudioParameters(
                        audio_format="audio/pcm", sampling_rate=24000
                    ),
                ),
            )
            await connection.send(request.SerializeToString())
            with open("output.pcm", "wb", buffering=0) as output:
                async with asyncio.TaskGroup() as tasks:
                    tasks.create_task(send_sentences(connection))
                    tasks.create_task(receive_audio(connection, output))
    print("Saved output.pcm")


if __name__ == "__main__":
    asyncio.run(main())
```

Run the script after setting your API key:

```bash theme={null}
uv run stream_binary.py
```

A successful run prints a request ID and `Saved output.pcm`. The file contains raw mono, signed 16-bit little-endian PCM at 24 kHz, with no WAV header. A failed run can leave a partial file.

To stream input over time, replace the fixed list in `send_sentences` with complete sentences from your application. To route audio to a browser, phone system, or player, replace `output.write(...)` with your audio handler.

Empty Protobuf messages still need presence. Call `request.end.SetInParent()` or `request.cancel.SetInParent()` to select these payloads. Constructing a request with only a `context_id` sends no operation.

## Run the audio demo

[Download the audio demos](/files/coda-audio-demos.zip), extract the archive, and open its `coda-audio-demos` directory. With `RIME_API_KEY` set and a local audio output device available, run:

```bash theme={null}
uv run stream_binary.py
```

This version plays PCM audio as it arrives and prints `Playback complete` after the last samples play. The archive includes the playback helper and a README with audio-device setup and troubleshooting.

## Generate bindings for another language

The installed package includes `schema/rime/text_to_speech.proto` and `schema/text_to_speech.asyncapi.yaml`. Use the Protobuf source to generate messages for your language. The AsyncAPI file describes the JSON encoding.

This command copies the installed Protobuf schema to your project:

```bash theme={null}
uv run python - <<'PY'
from importlib.resources import files
from pathlib import Path

schema = files("rime_api").joinpath("schema/rime/text_to_speech.proto")
Path("text_to_speech.proto").write_bytes(schema.read_bytes())
PY
```

Generate `rime.WebSocketRequest` and `rime.WebSocketResponse` with your language's Protobuf compiler. Each WebSocket message carries one serialized message. Do not add a gRPC header, length prefix, base64 layer, or custom framing. The schema defines gRPC services too, but this WebSocket client only needs the message types.

Use the [Coda WebSocket API reference](/api-reference/coda/websockets) for message fields, connection reuse, cancellation, and errors.
