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

# Ambient Streaming Wire Format

> Learn how to format and send audio chunks for Ambient and Form filling streaming: message format, order, example flow, and audio format and chunking

Use this guide to format and send messages over the **`GET /ws/stream`** WebSocket. **Ambient and Form filling use the same wire format** on this socket.

Send each message as a UTF-8 **JSON text frame** containing exactly one JSON object. Audio must be Base64-encoded and included in the JSON payload. **Do not send audio as a binary WebSocket frame.**

## Message format

Each outbound message is a JSON object with a **`type`** field. For messages that carry a payload, use the **`data`** field.

The **`data`** value must be:

* Standard Base64 ([RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648)).
* An encoding of the raw bytes you intend to send.
* Sent as a JSON string, regardless of the programming language you use.

<Warning>
  Do not use:

  * Hex encoding.
  * URL-safe Base64.
  * Raw binary inside JSON strings.
</Warning>

### Start the stream segment

Send one **`START_TIME`** message before the audio chunks for a stream segment:

```json theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
{ "type": "START_TIME", "data": "<base64>" }
```

* **`data`**: Base64 encoding of the UTF-8 bytes of an RFC 3339 timestamp.
* **Example timestamp**: **`2026-04-25T12:34:56Z`**.

### Send audio chunks

Send audio with **`type`** set to **`AUDIO`**:

```json theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
{ "type": "AUDIO", "data": "<base64>" }
```

* **`data`**: Base64 encoding of the raw PCM audio bytes.

### Send control events

Send control events with **`type`** set to **`EVENT`**:

```json theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
{ "type": "EVENT", "event": "PAUSE" }
```

Use the **`event`** field, not **`data`**. Supported values are **`PAUSE`**, **`RESUME`**, **`KEEP_ALIVE`**, **`CANCEL`**, and **`ABORT`**.

<Note>
  The ambient stream handler acts on **`PAUSE`**, **`RESUME`**, and **`CANCEL`**. While the stream is **paused**, send **`KEEP_ALIVE`** at least once every **five seconds** per the **idle-timeout guidance** (maximum pause **30 minutes**).
</Note>

**Common use cases:**

* Pause or resume audio.
* Keep the connection alive.
* Cancel or abort a stream.

You can send `EVENT` messages at any point in the stream where control is needed.

### End the stream segment

Send the final **`AUDIO`** message with **`data`** set to **`RU9G`**:

```json theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
{ "type": "AUDIO", "data": "RU9G" }
```

<Note>
  **`RU9G`** is Base64 for ASCII **`EOF`** (**`0x45 0x4F 0x46`**).
</Note>

<Warning>
  Do not:

  * Send **`EOF`** as plain text.
  * Use custom types like **`end_of_stream`**.
  * Use binary EOF signaling.
</Warning>

## Required message order

For each stream segment:

1. Send one **`START_TIME`** message.
2. Send one or more **`AUDIO`** messages with Base64 PCM audio.
3. Send any **`EVENT`** messages when control is needed.
4. Send a final **`AUDIO`** message with **`data`** set to **`RU9G`**.

## Example flow

This example shows the outbound message sequence for one ambient stream segment: one **`START_TIME`** message, multiple **`AUDIO`** chunks, an optional **`EVENT`**, and the final **`RU9G`** end marker.

```json theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
{ "type": "START_TIME", "data": "<base64(timestamp)>" }

{ "type": "AUDIO", "data": "<base64(pcm chunk 1)>" }
{ "type": "AUDIO", "data": "<base64(pcm chunk 2)>" }

{ "type": "EVENT", "event": "PAUSE" }

{ "type": "AUDIO", "data": "<base64(pcm chunk 3)>" }

{ "type": "AUDIO", "data": "RU9G" }
```

## Audio format and chunking

Use raw PCM audio chunks in each **`data`** message after Base64 decode. For more information, refer to:

<Icon icon="file-lines" iconType="solid" /> Read [Audio capture best practices](/documentation/how-to/audio-streaming/audio-capture-best-practices) for capture settings and PCM guidance.

### Audio streaming recommendations

<CardGroup cols={2}>
  <Card title="Sample Rate of 16 kHz" icon="waveform">
    Suki streams audio at **16 kHz**, which captures the full range of clinical speech.
  </Card>

  <Card title="Mono Channel" icon="microphone">
    Send a **single mono channel** of audio, not stereo or multi-channel.
  </Card>

  <Card title="LINEAR16 Encoding" icon="file-audio">
    Encode as **LINEAR16** (PCM signed 16-bit little-endian). Remove WAV headers or decode to raw PCM before you send.
  </Card>

  <Card title="Audio Chunk Size of 100 ms" icon="clock">
    Suki supports **100 ms** chunks to balance recognition quality, latency, and efficiency. At 16 kHz mono 16-bit, that is about **3200 bytes** of raw PCM per chunk.
  </Card>

  <Card title="Stream at Real-Time Speed" icon="gauge">
    Pace audio chunks to match their actual duration and stream at or near real time, rather than sending buffered audio as fast as possible.
  </Card>
</CardGroup>

### PCM vs WAV

Raw PCM is audio data without a file container. **`.wav`** is a container format and includes headers.

If your source is WAV, remove the **44-byte** RIFF header, or decode the file to raw PCM before sending. Sending WAV headers as PCM reduces recognition quality and makes debugging harder.

### Recommended audio format

* **Encoding**: LINEAR16, PCM signed 16-bit little-endian.
* **Channels**: Mono.
* **Sample rate**: **16 kHz**.

### Chunk size

Suki supports **100 ms audio chunks** to balance recognition quality, latency, and efficiency. At **16 kHz**, mono, **16-bit** audio, each chunk contains about **3200 bytes** of raw PCM before Base64 encoding.

* Send each chunk as one **`AUDIO`** JSON text frame.
* Keep chunks at **100 ms** throughout active streaming.
* Pace chunks at or near real time instead of sending buffered audio as quickly as possible.
* Send audio at least once every **25 seconds** while the stream is active, or Suki disconnects the stream. When the stream is paused, follow the [`KEEP_ALIVE` guidance](/api-reference/quickstart).

### Encode each chunk

For every **`AUDIO`** message:

1. Take raw PCM bytes.
2. Encode the bytes using standard Base64 ([RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648)).
3. Send the encoded string as **`data`**.

## Related topics

<CardGroup cols={2}>
  <Card title="Stream Ambient Audio" icon="waveform" href="/documentation/how-to/audio-streaming/ambient-audio-streaming" arrow={true}>
    How Ambient streaming works, JSON text rules, and when to use `/ws/stream`.
  </Card>

  <Card title="Dictation Wire Format" icon="code" href="/documentation/how-to/audio-streaming/websocket-streaming-wire-format-dictation" arrow={true}>
    Outbound message contract for Dictation **`GET /ws/transcribe`**.
  </Card>

  <Card title="Audio Capture Best Practices" icon="microphone" href="/documentation/how-to/audio-streaming/audio-capture-best-practices" arrow={true}>
    Capture format, environment, gain, and network readiness before you stream.
  </Card>

  <Card title="Complete the Session" icon="circle-check" href="/documentation/how-to/audio-streaming/websocket-streaming-complete-session" arrow={true}>
    Close the socket, end with REST, and troubleshoot common errors.
  </Card>
</CardGroup>

## Next steps

<Icon icon="file-lines" iconType="solid" /> Follow [Complete the session after streaming](/documentation/how-to/audio-streaming/websocket-streaming-complete-session) to close the socket, end the session, and troubleshoot errors.

<Icon icon="file-lines" iconType="solid" /> Copy the [Ambient WebSocket client code example](/documentation/tutorials/ambient-websocket-code-example) for a full client implementation.

<Icon icon="file-lines" iconType="solid" /> Review [Audio capture and streaming FAQs](/api-reference/faqs/audio-capture-streaming) for codec, chunk size, and reconnect guidance.
