import asyncio
import websockets
import os
from dotenv import load_dotenv
load_dotenv()
api_key = os.environ.get('RIME_API_KEY')
if not api_key:
raise ValueError("RIME_API_KEY environment variable is not set")
FILE_PATH = "coda_ws.wav"
class RimeClient:
def __init__(self, speaker, api_key):
self.url = f"wss://users-ws.rime.ai/ws?speaker={speaker}&modelId=coda&audioFormat=wav"
self.auth_headers = {
"Authorization": f"Bearer {api_key}"
}
self.audio_data = b''
async def send_tokens(self, websocket, message):
for token in message:
await websocket.send(token)
async def handle_audio(self, websocket):
while True:
try:
audio = await websocket.recv()
except websockets.exceptions.ConnectionClosedOK:
break
self.audio_data += audio
async def run(self, message):
async with websockets.connect(self.url, additional_headers=self.auth_headers) as websocket:
await asyncio.gather(
self.send_tokens(websocket, message),
self.handle_audio(websocket),
)
def save_audio(self, file_path):
with open(file_path, 'wb') as f:
f.write(self.audio_data)
message = [
"This ",
"is ",
"a ",
"test ",
"of ",
"the ",
"coda ",
"model ",
"using ",
"websockets ",
"and ",
"python.",
"<EOS>",
]
client = RimeClient("astra", api_key=api_key)
asyncio.run(client.run(message))
print(f"Saving audio to {FILE_PATH}")
client.save_audio(FILE_PATH)
Legacy WebSockets
Legacy WebSocket (/ws)
Coda plain-text WebSocket (/ws): send text, receive raw audio bytes.
import asyncio
import websockets
import os
from dotenv import load_dotenv
load_dotenv()
api_key = os.environ.get('RIME_API_KEY')
if not api_key:
raise ValueError("RIME_API_KEY environment variable is not set")
FILE_PATH = "coda_ws.wav"
class RimeClient:
def __init__(self, speaker, api_key):
self.url = f"wss://users-ws.rime.ai/ws?speaker={speaker}&modelId=coda&audioFormat=wav"
self.auth_headers = {
"Authorization": f"Bearer {api_key}"
}
self.audio_data = b''
async def send_tokens(self, websocket, message):
for token in message:
await websocket.send(token)
async def handle_audio(self, websocket):
while True:
try:
audio = await websocket.recv()
except websockets.exceptions.ConnectionClosedOK:
break
self.audio_data += audio
async def run(self, message):
async with websockets.connect(self.url, additional_headers=self.auth_headers) as websocket:
await asyncio.gather(
self.send_tokens(websocket, message),
self.handle_audio(websocket),
)
def save_audio(self, file_path):
with open(file_path, 'wb') as f:
f.write(self.audio_data)
message = [
"This ",
"is ",
"a ",
"test ",
"of ",
"the ",
"coda ",
"model ",
"using ",
"websockets ",
"and ",
"python.",
"<EOS>",
]
client = RimeClient("astra", api_key=api_key)
asyncio.run(client.run(message))
print(f"Saving audio to {FILE_PATH}")
client.save_audio(FILE_PATH)
The Rime API authenticates every request with a bearer token in the
Discards the buffered text. Use it when the user interrupts.
Synthesizes whatever text is buffered and sends the audio.
Synthesizes whatever text is buffered, sends the audio, then closes the connection.
Authorization header: Authorization: Bearer YOUR_API_KEY. See API authentication for how to create a key.
This page describes the legacy
/ws endpoint, which keeps working for existing integrations. For a new Coda integration, use Coda WebSocket v1.Include
modelId=coda in the connection query. If you omit it, the server routes the request to Mist v3 and speakers outside the Mist v3 catalog can fail with Speaker not found.Overview
This endpoint accepts bare text and responds with raw audio bytes in the selected format. All synthesis arguments are query parameters on the connection URL.Messages
Send
Send bare text, not JSON.This will be converted to audio via websockets
Receive
The server sends raw audio bytes in theaudioFormat set on the connection. With pcm, a message can end mid-sample, so keep whole samples.
<FF>^@^@^@9LAME3.100^AP^@^@^@^@^@^@^@^@^T<A0>$^D>"^@^@<A0>^@^@<A8><C0><BA><9D>G^N^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
^@^@^@^@^@^@^@^@^@^@
Commands
Send these strings as a whole message to act on the server’s text buffer.<CLEAR>
Discards the buffered text. Use it when the user interrupts.
<FLUSH>
Synthesizes whatever text is buffered and sends the audio.
<EOS>
Synthesizes whatever text is buffered, sends the audio, then closes the connection.
Variable parameters
string
required
Must be a
coda voice from the Rime voice catalog.string
required
The text you’d like spoken. Character limit per request is 1,000 via the API and in the dashboard UI.
string
default:"mistv3"
Set this to
coda. It is not strictly required, but if you omit it the server defaults to the Mist v3 backend, and speakers outside the Mist v3 catalog fail with a “Speaker not found” error.string
One of
wav, mp3 (or mpeg), ogg (Opus in OGG), webm (Opus in WebM), pcm (or l16), or mulaw (or pcmu). Unrecognized values fall back to wav.string
default:"eng"
If provided, the language must match the language spoken by the provided speaker. Both the 2-letter ISO 639-1 and the 3-letter ISO 639-2/3 form are accepted:
See the voices documentation for which speakers support each language.
| 639-1 | 639-2/3 | Language |
|---|---|---|
en | eng | English |
es | spa | Spanish |
fr | fra | French |
pt | por | Portuguese |
de | ger | German |
ja | jpn | Japanese |
ar | ara | Arabic |
hi | hin | Hindi |
it | ita | Italian |
int
default:"24000"
The sampling rate in Hz. Any positive integer is accepted, and values above the model’s native rate are upsampled. Common choices are 8000 (telephony), 16000, 22050, 24000, and 44100.
float
default:"1.0"
The time scaling factor. Accepted range is 0.4 to 2.5. Values outside this range are clamped without an error. Values above 1.0 slow the audio down, and values below 1.0 speed it up.
string
default:"bySentence"
Controls how text is segmented for synthesis. Available options:
- “immediate” - Synthesizes text immediately without waiting for complete sentences
- “never” - Never segments the text, waits for explicit flush or EOS
- “bySentence” (default) - Waits for complete sentences before synthesis
immediate=true in query params is equivalent to segment=immediate. If a null value is provided, it will default to “bySentence”.import asyncio
import websockets
import os
from dotenv import load_dotenv
load_dotenv()
api_key = os.environ.get('RIME_API_KEY')
if not api_key:
raise ValueError("RIME_API_KEY environment variable is not set")
FILE_PATH = "coda_ws.wav"
class RimeClient:
def __init__(self, speaker, api_key):
self.url = f"wss://users-ws.rime.ai/ws?speaker={speaker}&modelId=coda&audioFormat=wav"
self.auth_headers = {
"Authorization": f"Bearer {api_key}"
}
self.audio_data = b''
async def send_tokens(self, websocket, message):
for token in message:
await websocket.send(token)
async def handle_audio(self, websocket):
while True:
try:
audio = await websocket.recv()
except websockets.exceptions.ConnectionClosedOK:
break
self.audio_data += audio
async def run(self, message):
async with websockets.connect(self.url, additional_headers=self.auth_headers) as websocket:
await asyncio.gather(
self.send_tokens(websocket, message),
self.handle_audio(websocket),
)
def save_audio(self, file_path):
with open(file_path, 'wb') as f:
f.write(self.audio_data)
message = [
"This ",
"is ",
"a ",
"test ",
"of ",
"the ",
"coda ",
"model ",
"using ",
"websockets ",
"and ",
"python.",
"<EOS>",
]
client = RimeClient("astra", api_key=api_key)
asyncio.run(client.run(message))
print(f"Saving audio to {FILE_PATH}")
client.save_audio(FILE_PATH)

