Skip to main content
Connect to wss://api.rime.ai/coda/ws to send text and receive audio on one persistent connection. Each synthesis has a contextId. JSON and Protobuf support the same parameters, audio formats, and lifecycle. Neither returns word timestamps. For runnable clients, use the JSON quickstart, Protobuf quickstart, or LiveKit integration.

Connect and authenticate

Your server sends Authorization: Bearer YOUR_API_KEY in the WebSocket upgrade request. Create a key on the API Tokens page. Keep it on your server. Browser applications must connect through your backend. Select one subprotocol in the upgrade request and check the negotiated value: Wait for ready and check that ready.protocol is 1 before sending start. ready.languages lists supported language tags. ready.defaultLanguage, when present, gives the deployment default. Readiness reports engine availability; it does not replace authentication.

Client messages

Each message contains one payload field and an optional contextId. Do not send a type discriminator or a payload wrapper. JSON uses camelCase names; generated Python Protobuf objects use snake_case, such as audio_parameters.sampling_rate. Use the same contextId for start, text, end, and cancel in one turn. A repeated end, or one for a complete-text or unknown context, has no effect. This message appends a sentence to an open streaming context:

Run a synthesis context

The value of start.text selects the input mode: This sequence streams two sentences. The client can send text while it receives audio:

Complete text in one request

This start supplies the complete utterance. The server finishes it without an end message:

Stream text as it becomes available

  • Buffer LLM tokens locally, then send complete sentences or stable clauses. The server normalizes each message separately, so splitting a number, abbreviation, or word can change its pronunciation.
  • Preserve spaces at chunk boundaries, for example, "Hello. " followed by "How can I help? ".
  • An input pause needs no message. Send end only when the turn is complete. It leaves the socket open.
splitStrategy splits text within each message. It does not buffer incomplete sentences across messages.

Identify and reuse contexts

  • Use a new contextId per turn to help reject stale audio. An omitted or empty ID selects default, which the server returns as "contextId":"default".
  • Responses from concurrent contexts can arrive between each other. Route them by contextId from one receive loop, with a separate decoder and playback queue per context.
  • You can reuse an ID after its terminal event. The engine supports up to 16 open contexts.
  • Log started.requestId with your contextId for error reports. A request can fail or be cancelled before started, so handle terminal events as soon as you send start.

Stop speech on interruption

  1. Stop local playback and discard the turn’s queued audio.
  2. Send cancel with that turn’s contextId.
  3. Keep reading until cancelled, or done if synthesis completed first. A context-scoped error also ends the run. Discard audio for the interrupted turn that was already in transit.
This message cancels only turn-1:
Cancellation is idempotent. An unknown or finished context still gets a cancelled acknowledgement. Cancelling a context leaves the connection open; closing the socket cancels all its active contexts.

Connection configuration

Send at most one config message before the first start. It has no acknowledgement. Defaults merge at the top level. start.audioParameters replaces the whole default object, so include every audio setting you want to retain. The same rule applies to codaParameters. This configuration uses header authentication and sets PCM output for later turns:
If a later start.audioParameters contains only {"samplingRate":8000}, it discards the default audioFormat. Include "audioFormat":"audio/pcm" to retain PCM.

Synthesis parameters

Set these fields in start or config.defaults. The URL selects Coda; there is no modelId field. Coda does not accept temperature, topP, topK, repetitionPenalty, maxTokens, or seed. Unknown fields can be ignored, so a successful request does not prove an unsupported setting took effect.

Audio parameters

Both protocols support these audio formats. Decode the message envelope first: JSON carries base64 audio; Protobuf carries audio bytes. Message boundaries are transport chunks, not separate files or guaranteed decoder frames. Keep one decoder alive for each context. Raw PCM consumers must retain any incomplete sample between chunks.
Direct engine requests also accept audio/mp3, audio/ogg, audio/webm, audio/x-mulaw, and audio/l16. The audio/l16 alias returns little-endian PCM. Use audio/pcm to avoid confusion with network-byte-order L16. Unknown formats fall back to WAV; an empty format is invalid. LiveKit accepts only the six canonical MIME types above.

Text splitting and lookahead

Set codaParameters.textLookaheadTokens to a non-negative integer, default 0. More leading text tokens can improve the start of an utterance but delay first audio. The engine caps lookahead at the available tokens and ignores it for complete-text input.

Server events

These examples show separate server messages. The language list is illustrative, and error.requestId is optional:
Proto3 JSON omits default-valued fields. Test payload presence with "done" in event; the empty object {} is false in Python. For Python Protobuf, use response.WhichOneof("payload"). Allow unknown fields for compatible schema additions.

Handle completion and errors

An active context finishes with one terminal event: done, cancelled, or a context-scoped error. No more audio belongs to that run after its terminal event. Duplicate start, text outside an open streaming input, and too many open contexts cause connection-level errors. They do not terminate an existing context. Malformed envelopes and failed authentication can close the socket. There is no resume operation. Retrying text whose audio already played can repeat speech. Choose retries at a turn or sentence boundary.

Error kinds

error.kind is a string, not an HTTP status code. Authentication can also fail with HTTP 401 during the upgrade. An undecodable envelope produces invalid_input and close code 1007. Invalid or conflicting message credentials produce close code 1008. Treat an unexpected close before a terminal event as incomplete synthesis, even if some audio arrived.

Limits and connection health

A hosted gateway can apply additional limits. Use a WebSocket library that answers ping frames. Keep one reader active, await sends to respect backpressure, and bound your audio queue to limit memory use with a slow player.

Advanced connection options

Subprotocol negotiation

If you offer both supported subprotocols, the server selects the first in your list. An offer with only unknown values gets no selected subprotocol, which can cause the client to reject the handshake. The server decodes incoming messages by their WebSocket type, text or binary. The selected subprotocol fixes the response encoding for the connection. Use the same encoding in both directions unless you need a mixed client.

Message-based authentication

Clients that cannot send upgrade headers can send config.authorization, such as Bearer YOUR_API_KEY, in their first message. This does not make an API key safe to put in a browser. On a direct engine connection, send credentials within 10 seconds. Do not send credentials in both the upgrade headers and config; the engine closes the connection. Deployments with license authentication accept config.license as a JSON string.