> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rime.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Engine protocols

> Call a standalone licensed Coda or Mist engine over HTTP, gRPC, or WebSocket.

Your engine image selects the model. Requests use that model over HTTP, gRPC, or WebSocket. These interfaces apply to the [standalone licensed deployment](/docs/on-prem/quickstart). The cloud API and legacy API container have their own contracts.

| Protocol  | Packaged engine address                       | Input                                    | Output                           |
| :-------- | :-------------------------------------------- | :--------------------------------------- | :------------------------------- |
| HTTP      | `http://localhost:8080/`                      | One JSON request with complete text      | Streamed audio bytes             |
| gRPC      | `localhost:7463`, service `rime.TextToSpeech` | Complete text or a stream of text chunks | Protobuf audio messages          |
| WebSocket | `ws://localhost:8080/ws`                      | One-shot or streaming contexts           | JSON or protobuf event envelopes |

The model image configures the ports above. Use those ports for the quickstart. A bare engine binary defaults to HTTP port `7464` and gRPC port `7463`.

## Caller authentication

Your gateway must authenticate callers and provide TLS for every exposed protocol. In offline-license mode, the engine authorizes synthesis through the mounted runtime license. HTTP and gRPC callers do not need to send the metering key or license with each request.

<Warning>
  WebSocket still requires an `Authorization` upgrade header or an initial `config.authorization` message within ten seconds, even in offline-license mode. The engine skips validation of this value when it has a verified offline license. The field is required, but it provides no access control in this mode. For a private engine with a verified offline license, set `config.authorization` to an empty string. Keep the metering API key on the engine. Your gateway must authenticate callers. An empty value does not authenticate clients in other deployment modes.
</Warning>

## HTTP

Send the complete text to `POST /`. You can also use its alias, `POST /invocations`. Set `Content-Type: application/json` and choose an audio format with `Accept`, such as `audio/wav` or `audio/mpeg`.

| JSON field        | Meaning                                                                        |
| :---------------- | :----------------------------------------------------------------------------- |
| `text`            | Required text to synthesize                                                    |
| `speaker`         | Voice supported by the image, for example `luna`                               |
| `language`        | BCP-47 language tag, for example `en`; `lang` is an alias                      |
| `samplingRate`    | Requested output sample rate in Hz, subject to model support                   |
| `timeScaleFactor` | Duration multiplier; `1` preserves the generated duration                      |
| `splitStrategy`   | `sentence` or `none`; incremental input is available through gRPC or WebSocket |

Check the response `Content-Type` before you decode the audio.

<Note>
  For compatibility with existing HTTP clients, the engine also accepts `audioFormat` in the request body. Valid short values, such as `wav` or `mp3`, override `Accept`. If the field is absent or invalid, the engine uses `Accept`. Without either a valid body format or an `Accept` header, the request fails. Use `Accept` for new clients.
</Note>

This request writes the WAV response to `output.wav`:

```bash theme={null}
curl --fail-with-body http://localhost:8080/ \
  -H 'Content-Type: application/json' \
  -H 'Accept: audio/wav' \
  -d '{"text":"Hello from Rime.","speaker":"luna","language":"en"}' \
  -o output.wav
```

Read the response as audio bytes. The engine does not use the legacy `audioContent.model_output` JSON wrapper. Before audio starts, a failure can return an HTTP error. After audio starts, a failure aborts the response body. HTTP 200 alone is not proof that synthesis completed.

Word timestamps are currently unavailable over all three standalone engine protocols.

## gRPC

The engine exposes `rime.TextToSpeech` without gRPC reflection, so you need the schema to call it. Ask Rime for `rime/text_to_speech.proto` and its imports that match your image. Client generation also requires `google/protobuf/duration.proto` and `google/rpc/status.proto`, including their imports.

| RPC                     | Behavior                                                                            |
| :---------------------- | :---------------------------------------------------------------------------------- |
| `Synthesize`            | Accepts one `SynthesisRequest`; streams `SynthesisResponseStream` messages          |
| `SynthesizeStreaming`   | Accepts a stream of `StreamingSynthesisRequest`; streams the same response messages |
| `NormalizeText`         | Returns `normalizedSentences`                                                       |
| `GetSupportedLanguages` | Returns the image's language tags                                                   |
| `GetSupportedSpeakers`  | Lists Coda speakers; Mist returns `UNIMPLEMENTED`                                   |

With [grpcurl](https://github.com/fullstorydev/grpcurl) installed and the schema bundle in `./rime-protos`, this command sends complete text and prints the streamed response messages:

```bash theme={null}
grpcurl -plaintext \
  -import-path ./rime-protos \
  -proto rime/text_to_speech.proto \
  -d '{"text":"Hello from Rime.","speaker":"luna","language":"en","audioParameters":{"audioFormat":"audio/wav"}}' \
  localhost:7463 rime.TextToSpeech/Synthesize
```

Keep `-plaintext` for the private local listener. Use TLS through your gateway. grpcurl prints JSON with base64 `audio` fields. A generated protobuf client receives those fields as bytes. Append the audio payloads in order to build the audio file. Writing whole protobuf messages would include data outside the audio payload.

For incremental input, send one `header` followed by `textChunk` messages. Close the request side when you have sent all the text, then keep reading until the response stream completes. This example sends two complete sentences:

```bash theme={null}
grpcurl -plaintext \
  -import-path ./rime-protos \
  -proto rime/text_to_speech.proto -d @ \
  localhost:7463 rime.TextToSpeech/SynthesizeStreaming <<'JSON'
{"header":{"speaker":"luna","language":"en","audioParameters":{"audioFormat":"audio/wav"}}}
{"textChunk":"Hello from Rime. "}
{"textChunk":"This is the second sentence."}
JSON
```

If your input arrives a token at a time, set `header.splitStrategy` to `SPLIT_STRATEGY_ACCUMULATE_SENTENCE`. The engine collects text into sentences before synthesis. The selected image still determines model-specific synthesis behavior.

Check the final RPC status. Anything other than OK means synthesis is incomplete. Cancel the RPC to stop generation. The schema defines timestamp fields and a response trailer, but the standalone engine currently sends only audio messages. Setting `timestamps.enable` has no effect.

## WebSocket

Open `/ws` on the engine's HTTP port. Offer `rime.v1.json` for JSON text responses or `rime.v1.binary` for protobuf binary responses. The engine defaults to JSON if you omit the subprotocol. Both encodings use `WebSocketRequest` and `WebSocketResponse` from the gRPC schema bundle.

Each frame contains one message envelope. JSON uses camelCase fields and base64 `audio` values. Binary frames contain protobuf envelopes. With either encoding, extract the `audio` payload before you write it to an audio file.

| Client message                | Action                                                                |
| :---------------------------- | :-------------------------------------------------------------------- |
| `config`                      | Set connection credentials or defaults once, before the first `start` |
| `start` with non-empty `text` | Synthesize the complete text; no `end` needed                         |
| `start` with empty `text`     | Open a streaming context                                              |
| `text`                        | Append input to that streaming context                                |
| `end: {}`                     | Finish input; continue reading until `done`                           |
| `cancel: {}`                  | Cancel the context; expect `cancelled`                                |

Wait for `ready` before you send `start`. For each context, the server sends `started`, zero or more `audio` messages, then `done`, `cancelled`, or `error`. Match responses by `contextId`. An error with an empty context ID applies to the connection. You can keep the connection open for later contexts.

Save this example as `stream.mjs` and run it with Node.js 22 or later against a private engine with a verified offline license. It sends an empty authorization value and needs no metering API key. Running `node stream.mjs` writes `output.wav` only after the engine sends `done`:

```javascript theme={null}
import { writeFileSync } from "node:fs";

const socket = new WebSocket("ws://localhost:8080/ws", "rime.v1.json");
const chunks = [];
let completed = false;
let errorReported = false;
const send = (message) => socket.send(JSON.stringify(message));

socket.addEventListener("open", () => {
  // A verified offline license permits an empty authorization value.
  send({ config: { authorization: "" } });
});

socket.addEventListener("message", ({ data }) => {
  const message = JSON.parse(data);
  if (message.error) {
    console.error(message.error);
    errorReported = true;
    process.exitCode = 1;
    socket.close();
    return;
  }
  if (message.ready) {
    send({ contextId: "example", start: {
      text: "", speaker: "luna", language: "en",
      audioParameters: { audioFormat: "audio/wav" },
    } });
    send({ contextId: "example", text: "Hello from Rime. " });
    send({ contextId: "example", text: "This is the second sentence." });
    send({ contextId: "example", end: {} });
  }
  if (message.contextId !== "example") return;
  if (message.audio) chunks.push(Buffer.from(message.audio, "base64"));
  if (message.done) {
    writeFileSync("output.wav", Buffer.concat(chunks));
    completed = true;
    socket.close();
  }
  if (message.cancelled) socket.close();
});

socket.addEventListener("error", () => { process.exitCode = 1; });
socket.addEventListener("close", () => {
  if (!completed) {
    if (!errorReported) console.error("Synthesis ended before done");
    process.exitCode = 1;
  }
});
```

If your input arrives a token at a time, set `start.splitStrategy` to `SPLIT_STRATEGY_ACCUMULATE_SENTENCE`. Wait for `done` to confirm that all audio has arrived. This event does not currently include timestamp spans.

Migration from the API container's WebSocket ports requires changes to your client's messages. Move synthesis parameters from the old query parameters into `start`. Replace `flush`, `clear`, and `eos` with the context lifecycle above. Use `end` to finish input and `cancel` to stop a context. Decode the `audio` payload from each envelope.

For language support, read the languages in `ready` and test the voices in your image. The old API container's port-specific restrictions do not define this protocol.

## Routing, health, and failures

Route traffic using [HTTP or gRPC health checks](/docs/on-prem/metrics). Your gateway must support HTTP/2 gRPC and WebSocket upgrades. Set its timeouts to allow streams to stay open for the duration your application needs.

All three protocols share one inference capacity limit. At that limit, or during request drain, the engine refuses new synthesis. It returns gRPC `UNAVAILABLE`, HTTP 503, or a WebSocket `error` with kind `unavailable`.

Before you retry, check whether the client already received audio. Retry with backoff only if your application can avoid playing that audio again. See [Load balancing](/docs/on-prem/load-balancing) for stream routing and shutdown.
