Skip to main content
Connect to wss://coda.api.rime.ai/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. Either encoding can carry word timestamps. For runnable clients, use the JSON quickstart, the Protobuf quickstart, or the LiveKit integration.

Connect and authenticate

Your server sends Authorization: Bearer YOUR_API_KEY in the WebSocket upgrade request. The API accepts credentials only in this header. Create a key on the API Tokens page. Keep it on your server, because anyone who loads browser code can read a key embedded in it. 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 you send start. The same event lists the supported language tags in ready.languages, and the deployment default in ready.defaultLanguage when one is set. ready means that the engine is available. It does not mean that the connection is authenticated.

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 an end 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

Set splitStrategy on the streaming start: With SPLIT_STRATEGY_ACCUMULATE_SENTENCE, send tokens unchanged, spaces included. "Hello" followed by "world." is spoken as Helloworld. With SPLIT_STRATEGY_NONE, the Coda default, a message that ends mid-sentence is spoken as a fragment. Send end only at the end of the turn. A pause in your input needs no message.

Identify and reuse contexts

  • Use a new contextId for every turn: the server answers a cancel for a finished context with cancelled. It also applies a late cancel to any open context with the same ID. With a new ID per turn, a late cancel or cancelled cannot end the next turn, and you can reject stale audio.
  • Default ID: an omitted or empty ID selects default, which the server returns as "contextId":"default".
  • Route by ID: responses from concurrent contexts can arrive between each other. Route them by contextId from one receive loop, with a separate decoder and playback queue for each context. The engine supports up to 16 open contexts.
  • Log the request ID: 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:
You can send cancel more than once, and an unknown or finished context still gets cancelled. Cancelling a context leaves the connection open, and closing the socket cancels all of its active contexts.

Connection configuration

Send at most one config message before the first start. The server does not acknowledge it. Defaults merge at the top level. start.audioParameters replaces the whole default object, so include every audio setting that you want to keep. 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, and the context fails with invalid_input. Include "audioFormat":"audio/pcm" to keep PCM.

Synthesis parameters

Set these fields in start or config.defaults. The host selects Coda, so there is no modelId field. Coda does not accept temperature, topP, topK, repetitionPenalty, maxTokens, or seed. The server can ignore unknown fields, so a successful request does not prove that an unsupported setting took effect.

Audio parameters

Both protocols support these audio formats. Decode the message envelope first: JSON carries base64 audio, and 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 keep any incomplete sample between chunks.

Text lookahead

Set codaParameters.textLookaheadTokens to control streaming text lookahead:

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, not truthiness, because the empty object {} is falsy 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. There is no resume operation. Retrying text whose audio already played can repeat speech, so 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 or 503 during the upgrade.

Word timestamps

Set timestamps.enable to true in start to get the time of each word Coda speaks. The times arrive in the done event, after the last audio. Asking for them does not change how the audio streams. Coda returns them for de, en, es, fr, it, and pt. This start asks for the timestamps of a complete-text context:
Its done event has one span for each word of the text:
Each span is a word of your text as you sent it, split at whitespace, so $42.50 keeps its spelling although Coda says “forty-two dollars and fifty cents.” start and end are offsets from the start of the context’s audio as you receive it, in seconds, with timeScaleFactor applied.
  • Unspoken words: a word Coda does not speak, such as punctuation on its own, has no span.
  • Streaming input: spans follow the text as the server joins it. "Hello" followed by "world." gives one span, Helloworld.
An empty status means that Coda aligned every word. Otherwise, status has a code, one of the gRPC status codes, and a message that says why, and spans is empty. The audio is unaffected either way. A context in a language Coda does not align, such as ja, or with more than 180 seconds of audio gets no spans.

Limits and connection health

A hosted gateway can apply additional limits. Keep the connection healthy with these practices:
  • Answer pings: use a WebSocket library that answers ping frames, because the pongs reset the read inactivity timeout.
  • Keep one reader: keep one receive loop active for the whole connection.
  • Respect backpressure: await each send.
  • Bound the audio queue: a bounded queue limits memory use when the player is slow.

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.

Direct engine connections

These details apply when you connect to a Coda engine directly, for example in an on-premises deployment:
  • Credentials: a client that cannot send upgrade headers can send config.authorization in its first message, with the value the engine’s caller authentication expects. Send it within 10 seconds. If you send credentials in both the upgrade headers and config, the engine closes the connection. This does not make an API key safe to put in a browser.
  • License authentication: deployments that use license authentication accept config.license. This license field holds the license JSON as a string.
  • Audio aliases: the engine also accepts audio/mp3, audio/ogg, audio/webm, audio/x-mulaw, and audio/l16. The audio/l16 alias returns little-endian PCM, so use audio/pcm to avoid confusion with network-byte-order L16. Unknown formats fall back to WAV, and an empty format is invalid. LiveKit accepts only the six canonical MIME types above.