> ## 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.

# Stream Ambient Audio - Overview

> Stream Ambient or Form filling visit audio on `GET /ws/stream` after session creation: PCM chunking, JSON control frames, REST completion, and result retrieval

<div className="quick-summary-wrapper">
  <div className="quick-summary-header">
    <span className="quick-summary-icon" aria-hidden="true" />

    <span className="quick-summary-title">Quick summary</span>
  </div>

  <div className="quick-summary-content">
    Streaming sends live visit audio to Suki over WebSocket after you create a session and seed context. The WebSocket carries JSON text frames for audio and control messages. Final notes, form fields, transcripts, and structured data come from the matching product REST APIs after you end the session.
  </div>

  <div className="quick-summary-footer">
    <span className="quick-summary-footer-icon" aria-hidden="true" />

    <span className="quick-summary-footer-text">Last updated:</span>
    <span className="quick-summary-footer-date">August 2026</span>
  </div>
</div>

This guide explains how to stream live audio to `GET /ws/stream` after you create an ambient session and seed context. The WebSocket is used only to send audio and exchange control messages. Use other Ambient REST APIs to end the session, check processing status, and retrieve transcripts, notes, and structured data.

Form filling sessions use the **same Partner WebSocket** and the same ambient message protocol. If you are building clinical documentation workflows, including note sections, review experiences, and EHR handoff, start with [Ambient clinical documentation](/documentation/concepts/ambient-clinical-notes/ambient-documentation).

Streaming audio with the Ambient APIs lets you:

* Capture audio from your own backend, mobile app, or custom client.
* Stream visit audio in small chunks while the encounter is in progress.
* Pause, resume, or keep a paused stream alive by sending `EVENT` messages.
* Complete the stream and retrieve results through the Ambient REST APIs.

## Before you begin

<Tip>
  If you do not have credentials yet:

  * Complete [Partner onboarding](/documentation/get-started/partner-onboarding).
  * Complete [Partner authentication](/documentation/how-to/partner-authentication) to get an **`sdp_suki_token`**.
  * Use the staging base URL for API examples: `https://sdp.suki-stage.com`, or ask your Suki representative for the staging base URL.
</Tip>

To stream audio successfully, your application must:

* Authenticate and obtain an `sdp_suki_token`.
* Create an ambient session and seed context before opening the WebSocket connection.
* Capture mono **LINEAR16 PCM** audio at **16 kHz**, split it into chunks, Base64 encode each chunk, and send each message as a JSON text frame.
* Close the WebSocket when streaming is complete, call [End ambient session](/api-reference/ambient-sessions/end), and retrieve transcripts, notes, and structured data using the Ambient REST APIs.

Read the following related guides and references:

<Accordion title="Click Each Button to Learn More" defaultOpen={true}>
  <div className="doc-guide-btn-row">
    <a href="/api-reference/ambient-sessions/audio-stream" className="doc-guide-btn">
      Audio Streaming API
    </a>

    <a href="/documentation/how-to/audio-streaming/audio-stream" className="doc-guide-btn">
      Streaming Comparison
    </a>

    <a href="/documentation/how-to/audio-streaming/streaming-architecture" className="doc-guide-btn">
      Architecture Diagram
    </a>

    <a href="/documentation/how-to/audio-streaming/websocket-streaming-wire-format-ambient" className="doc-guide-btn">
      Wire Format
    </a>

    <a href="/documentation/how-to/audio-streaming/audio-capture-best-practices" className="doc-guide-btn">
      Audio Capture Best Practices
    </a>

    <a href="/documentation/how-to/audio-streaming/websocket-streaming-complete-session" className="doc-guide-btn">
      Complete the Session
    </a>
  </div>
</Accordion>

<Note>
  * Both Form filling and [Ambient clinical note sessions](/api-reference/ambient-sessions/create) use the parameter name **`ambient_session_id`** in the API reference. Those identifiers refer to **different** sessions.

  * Use only the ID returned from Form filling [Create Form filling session](/form-filling-api-reference/form-filling-sessions/create) for Form filling REST calls and for **`/ws/stream`**. Refer to [Form filling basic usage](/documentation/how-to/form-filling/form-filling-basic-usage).
</Note>

### When to use Ambient or Form filling streaming vs Dictation streaming

Ambient or Form filling streaming and Dictation streaming solve different problems.

* Use **Ambient or Form filling streaming** (`GET /ws/stream`) when you want to push visit audio into an ambient or Form filling session for note generation or form output. You retrieve results mainly with REST after you end the session.
* Use **Dictation streaming** (`GET /ws/transcribe`) when the provider needs real-time transcript text in your application. Refer to [Stream Dictation audio](/documentation/how-to/audio-streaming/dictation-streaming).

## Decide before you build your streaming client

Before you implement production streaming, align on a few engineering decisions. Agree on who owns capture, what the WebSocket owns versus REST, when you may open or reconnect the socket, how you handle pause and keep-alive, and which audio format you will send. Those choices shape handshake behavior, reconnect logic, and how you retrieve results.

Use the following sections to align your team.

<AccordionGroup>
  <Accordion title="Who Owns Capture and the WebSocket Client" icon="code">
    This guide is for **Ambient APIs**. Your application owns the microphone or media pipeline, PCM chunking, Base64 encoding, the WebSocket client, and the session lifecycle on **`GET /ws/stream`**.

    If you do not want to own that streaming stack, choose a different capture path:

    * **Web SDK:** Suki provides browser capture and review UI. Your application supplies encounter context and handles note handoff after submit.
    * **Headless Web SDK:** Your React application owns recording controls, status, and review UI through SDK hooks, not the Partner WebSocket wire format in this guide.

    Refer to the capture path table in [Ambient clinical documentation](/documentation/concepts/ambient-clinical-notes/ambient-documentation#2-choose-your-audio-capture-strategy).

    For Ambient API streaming, use the **same base host** for REST and WebSocket in a given environment. Your partnership team confirms which host and credentials apply. Refer to [Streaming architecture](/documentation/how-to/audio-streaming/streaming-architecture).
  </Accordion>

  <Accordion title="What the WebSocket Owns vs What REST Owns" icon="diagram-project">
    **`/ws/stream`** streams audio and accepts control messages such as **`PAUSE`**, **`RESUME`**, **`CANCEL`**, and **`KEEP_ALIVE`**. It does not replace session create, end session, or result retrieval.

    After you send the ambient end marker and close the socket:

    * End the session with [End ambient session](/api-reference/ambient-sessions/end).
    * Poll status and fetch transcript, note content, and structured data with ambient content REST APIs.

    <Warning>
      Final transcripts and notes are not guaranteed to arrive over WebSocket. Treat REST APIs as the source of truth. Refer to [Complete the session after streaming](/documentation/how-to/audio-streaming/websocket-streaming-complete-session).
    </Warning>
  </Accordion>

  <Accordion title="Session Readiness and Reconnect Rules" icon="plug">
    Create the ambient session and seed context before you open **`/ws/stream`**.

    <Warning>
      Opening **`/ws/stream`** before the session and context are ready often leads to handshake failures or a broken stream. Refer to the [Audio streaming API](/api-reference/ambient-sessions/audio-stream) prerequisites.
    </Warning>

    You can reconnect to **`/ws/stream`** using the same **`ambient_session_id`** only while the session status is **`CREATED`**. Otherwise the handshake returns **`FailedPrecondition`**. Refer to [Audio capture best practices](/documentation/how-to/audio-streaming/audio-capture-best-practices) and [Complete the session after streaming](/documentation/how-to/audio-streaming/websocket-streaming-complete-session).
  </Accordion>

  <Accordion title="Pause, Keep-Alive, and Idle Timeouts" icon="clock">
    While audio is flowing, send audio at least once every **25 seconds**, or Suki disconnects the stream.

    While the stream is paused, send a **`KEEP_ALIVE`** event at least every **5 seconds**. An ambient session can remain paused for up to **30 minutes** when keep-alives are maintained.

    Refer to [Audio capture best practices](/documentation/how-to/audio-streaming/audio-capture-best-practices) and [Streaming architecture](/documentation/how-to/audio-streaming/streaming-architecture#network-and-connection-management).
  </Accordion>

  <Accordion title="Audio Format and Session Length" icon="waveform">
    Capture mono **LINEAR16** (PCM signed 16-bit little-endian) at **16 kHz**. Stream about **100 ms** chunks. At that rate, each chunk is about **3200 bytes** of raw PCM. Base64 encode the bytes in the **`data`** field and stream at or near real-time speed. Strip WAV headers before you encode.

    <Note>
      Sessions shorter than **1 minute** may not contain enough audio for note generation and can be marked as **`skipped`**.
    </Note>

    Refer to [Audio capture best practices](/documentation/how-to/audio-streaming/audio-capture-best-practices) and [WebSocket streaming wire format](/documentation/how-to/audio-streaming/websocket-streaming-wire-format-ambient).
  </Accordion>
</AccordionGroup>

## How Ambient or Form filling streaming works

```mermaid actions={false} theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
flowchart LR
    A[Create ambient session<br/>and seed context] --> B[Open GET /ws/stream]
    B --> C[START_TIME then<br/>AUDIO chunks]
    C --> D[Optional EVENT<br/>controls]
    D --> E[RU9G end marker<br/>and close socket]
    E --> F[End session and<br/>retrieve results with REST]

    style A fill:#FFF394,stroke:#333,color:#000
    style B fill:#FFF394,stroke:#333,color:#000
    style C fill:#FFF394,stroke:#333,color:#000
    style D fill:#FFF394,stroke:#333,color:#000
    style E fill:#FFF394,stroke:#333,color:#000
    style F fill:#FFF394,stroke:#333,color:#000
```

<Steps>
  <Step title="Create Session and Seed Context">
    Create an [Ambient session](/api-reference/ambient-sessions/create) and [Seed session context](/api-reference/ambient-sessions/context).
  </Step>

  <Step title="Open the WebSocket">
    Open a WebSocket connection to **`GET /ws/stream`**.
  </Step>

  <Step title="Send START_TIME">
    Send one **`START_TIME`** message for the stream segment.
  </Step>

  <Step title="Stream Audio Chunks">
    Send one JSON message per audio chunk.
  </Step>

  <Step title="Send Optional EVENT Messages">
    Optionally send **`EVENT`** messages, such as **`PAUSE`**, **`RESUME`**, or **`KEEP_ALIVE`**, when control is needed.
  </Step>

  <Step title="Send the Ambient End Marker">
    Send the ambient end marker as the final **`AUDIO`** message with **`data`**: **`RU9G`**.
  </Step>

  <Step title="Close the Socket and Finish with REST">
    Close the socket, then use [End ambient session](/api-reference/ambient-sessions/end) to end the session and retrieve results.
  </Step>
</Steps>

For handshake details, refer to the [Audio streaming API](/api-reference/ambient-sessions/audio-stream). For exact JSON shapes and required message order, refer to [WebSocket streaming wire format](/documentation/how-to/audio-streaming/websocket-streaming-wire-format-ambient).

<Warning>
  Ambient streaming uses JSON text frames. Do not send raw binary audio frames to this endpoint.
</Warning>

### Verify your first Ambient stream

Before you design the full production streaming client, confirm that your staging integration can complete the core path:

* Authenticate successfully and use the returned **`sdp_suki_token`** in follow-up REST and WebSocket requests.
* Create an ambient session, seed context, and store the returned **`ambient_session_id`**.
* Open **`/ws/stream`**, send **`START_TIME`**, stream mono PCM chunks, send **`RU9G`**, and close the socket.
* End the session and confirm that you can retrieve transcript or note content with REST.

After this path works end to end, continue with pause and resume, keep-alive handling, reconnect rules, and capture quality controls.

## Send JSON text frames

Every message you send on **`/ws/stream`** must be a **UTF-8 JSON text frame**.

* Each WebSocket frame must contain **exactly one JSON object**.
* Each client **`send`** should contain **one logical message**.
* Audio bytes go inside a **JSON string field**, not in a binary WebSocket frame.

<Warning>
  Do not:

  * Send binary WebSocket frames.
  * Send multiple JSON objects in one frame.
  * Stream raw audio over HTTP. Use the WebSocket with JSON text frames instead.

  <Note>
    If the server receives non-JSON payloads, it returns parsing errors, such as invalid character or null byte errors.
  </Note>
</Warning>

## 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>
</CardGroup>

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

For encoding steps and the message order, refer to [WebSocket streaming wire format](/documentation/how-to/audio-streaming/websocket-streaming-wire-format-ambient). For capture guidance before you open the socket, refer to [Audio capture best practices](/documentation/how-to/audio-streaming/audio-capture-best-practices).

## Common streaming mistakes to avoid

Use this table to troubleshoot common ambient streaming mistakes before you ship. It explains what each mistake means, why it occurs, and how to resolve it. For the full Ambient troubleshooting table, refer to [Complete the session after streaming](/documentation/how-to/audio-streaming/websocket-streaming-complete-session).

| Problem                                                                        | Why it happens                                                                                                                                   | How to fix it                                                                                                             |
| :----------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ |
| Server returns JSON parse errors (for example, invalid character or null byte) | The WebSocket received a binary frame, a non-JSON payload, or multiple JSON objects in a single frame.                                           | Send one UTF-8 JSON object per WebSocket text frame. Do not send binary frames or concatenate multiple JSON objects.      |
| Audio is ignored or transcription is incorrect                                 | Audio was not encoded as expected. Common causes include hexadecimal encoding, URL-safe Base64, or sending WAV headers instead of raw PCM audio. | Encode raw LINEAR16 PCM audio using standard Base64 before sending it in the **`data`** field.                            |
| The session never completes                                                    | The server never received the end-of-stream marker.                                                                                              | End the stream by sending a final **`AUDIO`** message with **`"data": "RU9G"`**.                                          |
| Control messages are ignored                                                   | A control action was sent in the **`data`** field instead of as an event message.                                                                | Send control actions as **`{"type":"EVENT","event":"<VALUE>"}`**. Do not place control values in **`data`**.              |
| No final note or transcript is available                                       | Closing the WebSocket does not end the ambient session or trigger note generation.                                                               | After streaming completes, call the ambient REST API to end the session, then retrieve the generated note and transcript. |
| The WebSocket disconnects during a long pause                                  | No messages were sent before the keep-alive timeout expired.                                                                                     | While audio is paused, send a **`KEEP_ALIVE`** event at least every **5 seconds**.                                        |
| Unable to reconnect after a disconnect                                         | The session has already moved beyond the **`CREATED`** state.                                                                                    | Reconnect to **`/ws/stream`** using the same **`ambient_session_id`** only while the session status is **`CREATED`**.     |

## Available cookbooks

<div className="hp-io-method-grid tut-hub-card-grid" data-cookbook-related-grid>
  <a className="hp-io-method-card tut-hub-method-card" href="/documentation/cookbooks/end-ambient-after-streaming">
    <div className="tut-hub-card-media" aria-hidden="true" />

    <div className="hp-io-method-card-body">
      <div className="tut-hub-card-badges">
        <span className="hp-wn-badge hp-wn-badge-new">Ambient</span>
        <span className="hp-wn-badge cookbook-hub-badge-surface cookbook-hub-badge-surface--api">API</span>
      </div>

      <h3 className="hp-io-method-card-title">End Ambient After Streaming</h3>

      <p className="hp-io-method-card-desc cookbook-hub-card-desc">
        Send RU9G, then end session.
      </p>

      <div className="hp-io-method-card-meta tut-hub-card-foot" aria-label="5 min">
        <div className="tut-hub-card-foot-meta">
          <span className="hp-io-method-card-meta-time">5 min</span>
        </div>
      </div>
    </div>
  </a>

  <a className="hp-io-method-card tut-hub-method-card" href="/documentation/cookbooks/browser-websocket-auth">
    <div className="tut-hub-card-media tut-hub-card-media--blue" aria-hidden="true" />

    <div className="hp-io-method-card-body">
      <div className="tut-hub-card-badges">
        <span className="hp-wn-badge hp-wn-badge-new">Ambient</span>
        <span className="hp-wn-badge cookbook-hub-badge-surface cookbook-hub-badge-surface--api">API</span>
      </div>

      <h3 className="hp-io-method-card-title">Authenticate Browser WebSocket Handshake</h3>

      <p className="hp-io-method-card-desc cookbook-hub-card-desc">
        Auth browser WebSocket with protocols.
      </p>

      <div className="hp-io-method-card-meta tut-hub-card-foot" aria-label="5 min">
        <div className="tut-hub-card-foot-meta">
          <span className="hp-io-method-card-meta-time">5 min</span>
        </div>
      </div>
    </div>
  </a>
</div>

## Available tutorials

<div className="hp-io-method-grid tut-hub-card-grid">
  <a className="hp-io-method-card tut-hub-method-card" href="/documentation/tutorials/ambient-websocket-code-example">
    <div className="tut-hub-card-media" aria-hidden="true" />

    <div className="hp-io-method-card-body">
      <span className="hp-wn-badge hp-wn-badge-new">Ambient</span>
      <h3 className="hp-io-method-card-title">Build an Ambient Streaming Client</h3>

      <p className="hp-io-method-card-desc">
        Authenticate, create a session, stream PCM audio over WebSocket, and retrieve clinical note results.
      </p>

      <div className="hp-io-method-card-meta tut-hub-card-foot" aria-label="20 min, Intermediate">
        <div className="tut-hub-card-foot-meta">
          <span className="hp-io-method-card-meta-time">20 min</span>
          <span className="tut-hub-level">Intermediate</span>
        </div>
      </div>
    </div>
  </a>
</div>

## Next steps

<Icon icon="file-lines" iconType="solid" /> Open [Ambient streaming wire format](/documentation/how-to/audio-streaming/websocket-streaming-wire-format-ambient)

<Icon icon="file-lines" iconType="solid" /> Follow [Complete the session after streaming](/documentation/how-to/audio-streaming/websocket-streaming-complete-session)

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