> ## 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 Audio To Dictation Session

> Opens a WebSocket to stream audio for real-time transcription. Browser clients authenticate with the `Sec-WebSocket-Protocol` header; non-browser clients send `sdp_suki_token` and `transcription_session_id` as HTTP headers on the upgrade request.

Use this WebSocket endpoint to stream audio to an active <Tooltip tip="The process of speaking aloud to create written text, often used by clinicians to create medical documentation using voice input." cta="View in Glossary" href="/Glossary/d">dictation</Tooltip> session for **real-time transcription**.

For the complete workflow, **usage guidelines**, **wire format**, **message order**, and **error handling**, refer to the [Audio dictation](/documentation/dictation) and [Dictation streaming](/documentation/dictation-streaming) guides.

<Tip>
  * For a single guide that compares **`/ws/stream`** and **`/ws/transcribe`** (handshake, proxy behavior, and message shapes), refer to [Audio streaming](/documentation/audio-stream) guide.
  * Stream audio in **chunks** for the best latency and throughput.
  * For partial and final inbound transcript frames, **`EOF`**, and session state rules, refer to [Dictation streaming](/documentation/dictation-streaming).
</Tip>

<Warning>
  If the dictation session is **`RUNNING`**, **`COMPLETED`**, or in another state, the WebSocket handshake fails with **`FailedPrecondition`** (for example **transcript session is not accepting new speech sessions**).
</Warning>

## Inbound transcript messages

The server sends **JSON text frames** that include `transcript`, `is_final`, and `transcript_id`. Use `is_final` to identify whether the
transcript is a partial result or a final result. After the audio stream ends, the server sends `{ "transcript": { "transcript": "EOF" } }` and then closes the WebSocket connection.

Refer to [Dictation streaming](/documentation/dictation-streaming) for frame examples, **`words`** and speaker IDs on finals, and client-side filtering rules.

## Authentication

Authentication is applied during the WebSocket handshake. The method depends on your client type. Use `Sec-WebSocket-Protocol` header for browser clients, and `sdp_suki_token` and `transcription_session_id` headers for non-browser clients.

### Browser clients

If you are connecting from a browser, you must use the `Sec-WebSocket-Protocol` header during the WebSocket handshake.

The header must specify the `SukiAmbientAuth` protocol, followed by the **token** and the **transcription session ID** in the following format.

```bash theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
Sec-WebSocket-Protocol: SukiAmbientAuth,<sdp_suki_token>,<transcription_session_id>
```

### Non-browser clients

If you are connecting from a non-browser client, such as a mobile or server-side application, you must provide the **token** and **session ID** as separate HTTP headers in the initial WebSocket upgrade request.

* `sdp_suki_token`: Session token from login.
* `sdp_provider_id`: Provider identifier. Optional for standard partners; **Required** for Single Auth Token authentication.
* `transcription_session_id`: The ID for the current session.

<Warning>
  Important:

  * All messages must be sent as **JSON text** frames over the WebSocket connection.
  * Do not send raw binary data or use HTTP endpoints for streaming audio.
</Warning>

## Code examples

<Tabs>
  <Tab title="Python">
    ```python theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    # pip install websocket-client
    import base64
    import json
    import websocket

    # Replace with values from Create dictation session and your authentication flow.
    transcription_session_id = "<transcription_session_id>"
    sdp_suki_token = "<sdp_suki_token>"
    sdp_provider_id = "<sdp_provider_id>"  # Optional; Required for Single Auth Token authentication

    # Staging WebSocket URL
    ws_url = "wss://sdp.suki-stage.com/ws/transcribe"

    ws = websocket.create_connection(
        ws_url,
        header=[
            f"sdp_suki_token: {sdp_suki_token}",
            f"sdp_provider_id: {sdp_provider_id}",
            f"transcription_session_id: {transcription_session_id}",
        ],
    )

    # If the file is WAV, skip the 44-byte header so payloads are PCM_S16LE only.
    WAV_HEADER_BYTES = 44
    CHUNK_BYTES = 3200  # Example chunk size; size your chunks to your capture pipeline.

    try:
        with open("audio.wav", "rb") as audio_file:
            if WAV_HEADER_BYTES:
                audio_file.read(WAV_HEADER_BYTES)
            chunk = audio_file.read(CHUNK_BYTES)
            while chunk:
                msg = {
                    "type": "AUDIO",
                    "audioData": base64.b64encode(chunk).decode("ascii"),
                }
                ws.send(json.dumps(msg))
                chunk = audio_file.read(CHUNK_BYTES)
        ws.send(json.dumps({"type": "EVENT", "event": "AUDIO_END"}))
    finally:
        ws.close()
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    // Browser example: same IDs as Python tab; add <input type="file" id="audioFile" /> in your HTML.

    const transcriptionSessionId = '<transcription_session_id>';
    const sdpSukiToken = '<sdp_suki_token>';

    // Staging WebSocket URL
    const ws = new WebSocket('wss://sdp.suki-stage.com/ws/transcribe', [
      `SukiAmbientAuth,${sdpSukiToken},${transcriptionSessionId}`,
    ]);

    function uint8ToBase64(bytes: Uint8Array): string {
      let binary = '';
      for (let i = 0; i < bytes.byteLength; i += 1) {
        binary += String.fromCharCode(bytes[i]!);
      }
      return btoa(binary);
    }

    ws.onopen = () => {
      const input = document.getElementById('audioFile') as HTMLInputElement | null;
      const file = input?.files?.[0];
      if (!file) {
        console.error('Select a file for #audioFile');
        ws.close();
        return;
      }
      const reader = new FileReader();
      reader.onload = () => {
        const buffer = reader.result as ArrayBuffer;
        const wavHeaderBytes = 44; // Set to 0 if the file is already raw PCM.
        const pcm = new Uint8Array(buffer, wavHeaderBytes);
        const chunkSize = 3200;
        for (let offset = 0; offset < pcm.byteLength; offset += chunkSize) {
          const end = Math.min(offset + chunkSize, pcm.byteLength);
          const chunk = pcm.subarray(offset, end);
          ws.send(
            JSON.stringify({
              type: 'AUDIO',
              audioData: uint8ToBase64(chunk),
            }),
          );
        }
        ws.send(JSON.stringify({ type: 'EVENT', event: 'AUDIO_END' }));
      };
      reader.readAsArrayBuffer(file);
    };

    ws.onmessage = (event) => {
      const payload = JSON.parse(event.data as string);
      if (payload.transcript?.transcript === 'EOF') {
        console.log('End of transcription stream');
        return;
      }
      console.log('Partial:', !payload.is_final, payload.transcript?.transcript);
    };
    ```
  </Tab>
</Tabs>


## OpenAPI

````yaml GET /ws/transcribe
openapi: 3.0.1
info:
  title: Suki Developer Platform
  description: >-
    REST and WebSocket APIs for the Suki Developer Platform. Authenticate with
    Login or Register to obtain a Suki access token, then integrate ambient
    clinical documentation, form filling, transcription, and reference metadata
    endpoints.
  contact: {}
  version: '1.0'
servers:
  - url: https://sdp.suki.ai
    description: >-
      Production base URL for Suki Developer Platform REST APIs. WebSocket
      endpoints use the same host with `wss://`.
security:
  - SukiTokenAuth: []
paths:
  /ws/transcribe:
    get:
      tags:
        - /ws
      summary: Stream audio for transcription
      description: >-
        Opens a WebSocket to stream audio for real-time transcription. Browser
        clients authenticate with the `Sec-WebSocket-Protocol` header;
        non-browser clients send `sdp_suki_token` and `transcription_session_id`
        as HTTP headers on the upgrade request.
      parameters:
        - name: Sec-WebSocket-Protocol
          in: header
          description: >-
            Required FOR BROWSER CLIENTS ONLY. Sent during WebSocket handshake.
            Browsers must use the same subprotocol the grpc-wsproxy maps to
            Authorization:
            'SukiAmbientAuth,<sdp_suki_token>,<transcription_session_id>'
            (comma-separated; token second, transcription session id third).
            Other names (e.g. SukiTranscriptionAuth) are not mapped and
            typically yield 401.
          schema:
            type: string
        - name: transcription_session_id
          in: header
          description: >-
            Required for non-browser clients only. UUID from Create
            Transcription Session.
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/ProviderIdHeader'
      responses:
        '101':
          description: Switching Protocols - Indicates successful WebSocket handshake.
          content:
            application/json:
              schema:
                type: string
        '200':
          description: >-
            OK - May indicate successful stream processing or completion (e.g.,
            sending 'EOF' message). Specific meaning depends on implementation
            after handshake.
          content:
            application/json:
              schema:
                type: string
        '400':
          description: Bad Request - Invalid parameters, headers, or request format.
          content:
            application/json:
              schema:
                type: string
        '401':
          description: >-
            Unauthorized - Authentication failed (invalid token/session,
            incorrect protocol/headers).
          content:
            application/json:
              schema:
                type: string
        '403':
          description: >-
            Forbidden - Client is authenticated but not authorized for this
            action.
          content:
            application/json:
              schema:
                type: string
        '500':
          description: Internal Server Error - Abnormal closure or server-side issue.
          content:
            application/json:
              schema:
                type: string
      security:
        - SukiTokenAuth: []
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl --request GET \
              --url https://sdp.suki.ai/ws/transcribe \
              --header 'sdp_suki_token: <sdp_suki_token>' \
              --header 'sdp_provider_id: <sdp_provider_id>' \
              --header 'transcription_session_id: <transcription_session_id>'
components:
  parameters:
    ProviderIdHeader:
      name: sdp_provider_id
      in: header
      description: >-
        **Optional** - Stable identifier for the active provider. Omit for
        standard partners whose `partner_token` identifies the user.
        **Required** for Bearer partners and Single Auth Token authentication
        where multiple providers share one `partner_token`. Use the same
        `provider_id` you sent on Login or Register.
      required: false
      schema:
        type: string
        example: provider-123
  securitySchemes:
    SukiTokenAuth:
      type: apiKey
      in: header
      name: sdp_suki_token
      description: >-
        Suki access token for the authenticated provider. Obtain this by calling
        Login or Register with a valid `partner_token`. Pass the `suki_token`
        value from the JSON response as the `sdp_suki_token` header on REST
        requests and non-browser WebSocket upgrades. Browser WebSocket clients
        pass the token in `Sec-WebSocket-Protocol` instead. Tokens expire after
        one hour; call Login again to refresh.

````