assistant-api reads them through the custom-tts transformer and maps LLM text packets and provider audio frames with WebSocket DSL rules.
Provider identifier: custom-tts
Where it is configured
1
Create the provider credential
In the Rapida dashboard, open Integrations > Models, choose Custom TTS, and create a credential.
The backend also accepts snake case keys:
api_compatibility and base_url.2
Select Custom TTS on the assistant
Open the assistant voice settings and select Custom TTS as the text-to-speech provider.
3
Fill the TTS arguments
Set the audio format, query parameters, request rules, and response rules. The argument reference below maps to the UI fields and the transformer option keys.
TTS arguments
The UI stores these keys with thespeaker. metadata prefix, but the transformer reads the option keys shown below. The current UI exposes the audio and DSL fields; speak.voice.id, speak.model, and speak.language are optional API/import metadata keys that request rules and query parameters can read when present.
Query parameters
Usespeak.ws.query_params when your provider expects configuration in the WebSocket URL.
Supported variables:
If your provider needs a voice, model, or language and those values are not present as metadata, set them as static values inside
speak.ws.request_rules.Request rules
Request rules are evaluated for normalized TTS packets produced by Rapida.
Supported outbound frame types are
binary, json, and text.
One-shot synthesis
Use this when the provider synthesizes each text packet immediately.Two-step synthesis with done
Use this when the provider expects text first and a final flush/done packet.Response rules
Response rules parse provider WebSocket frames into Rapida audio packets. Supported inbound frame types:
Supported emit keys:
Binary audio responses
JSON base64 audio responses
TTS DSL design
Custom TTS uses a JSON-template DSL with three sections: query parameters, request rules, and response rules. The DSL is intentionally small. It does not run scripts, call functions, concatenate strings, perform regex matching, or read environment variables.TTS frame support
Inbound parsing rules:
- WebSocket message type
2is treated asbinary. - Non-binary messages are parsed as JSON when they contain exactly one valid JSON value.
- Non-JSON messages are treated as
text, but TTS response rules do not support text response frames.
TTS operators
Every operator object must contain only that operator and its required field.
Unsupported TTS operators:
$frame: "text"and$frame: "json"are not supported for TTS emit rules.$decodesupports onlybase64.
Cast behavior
JSON path behavior
$path uses dot-separated paths.
- Keys containing a literal dot are not addressable.
- Request rules can only read from
configandpacket. - Response rules can use
$pathonly with JSON response frames. - A missing path in
when.pathmeans the rule does not match. - A missing path in
emitorsend.bodyis an error.
Query parameter rules
speak.ws.query_params must be a flat JSON object. Each value must resolve to a primitive value: string, number, boolean, or null. Nested objects and arrays are rejected unless the object is a DSL expression.
baseUrl. Existing query parameters in baseUrl are preserved unless the same key is rendered by speak.ws.query_params.
TTS request rules can read
packet.text, but text is not a supported query parameter variable.Request rule shape
speak.ws.request_rules is an ordered JSON array. Every matching rule is sent, so one packet can produce multiple WebSocket messages.
Body validation depends on
send.frame:
TTS request scope
The request scope is the data available to$path in request rules.
done and interrupt, packet.text is present but may be empty.
Response rule shape
speak.ws.response_rules is an ordered JSON array. The first matching rule is evaluated; later rules are skipped for that frame.
Match behavior:
TTS emit keys
Binary audio response
Use this for providers that stream raw audio as binary WebSocket frames.JSON base64 audio response
Use$decode when the provider returns base64-encoded audio inside JSON.
Text, done, and interrupt recipe
Use this pattern when the provider expects text payloads, an explicit final message, and an explicit cancel message.Runtime behavior
- The connection URL is built from
baseUrlandspeak.ws.query_params. - Headers are copied from the credential and are not templated.
- The transformer opens a connection per active message/context. A new context closes the previous connection.
textpackets open the WebSocket connection if needed.doneandinterruptrequest rules are optional. If no rule exists for that packet, nothing is sent.- On interruption, Rapida sends the optional
interruptrule first, then closes the connection. - Audio returned by the provider is interpreted as
speak.audio.encodingandspeak.audio.sample_rate, then resampled to Rapida’s internal audio format when needed. - If no response rule matches an inbound frame, the frame is ignored.
- If a response emits
error, Rapida emits a TTS error packet. - If a response emits
done, Rapida closes the connection and emits a TTS end packet.
Current TTS limits
- No regex, contains, starts-with, greater-than, or compound match conditions.
- No string interpolation or concatenation.
- No fallback values inside expressions.
- No dynamic headers or dynamic URL path segments.
- No text response handling for TTS.
- No
$frame: "json"selector in emit rules. $decodesupports only base64.
Backend mapping
assistant-api resolves custom-tts in api/assistant-api/internal/transformer/transformer.go, then dispatches to the WebSocket v1 implementation in api/assistant-api/internal/transformer/custom/tts_websocket_v1.
The WebSocket v1 transformer validates:
baseUrlis present in the credential.speak.audio.encodingis not empty.speak.audio.sample_rateis positive.speak.ws.request_rulescontains at least onetextpacket rule.speak.ws.response_rulescontains at least one rule.
Custom STT
Configure WebSocket speech-to-text.
TTS overview
Transformer interface and supported providers.