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

# Form Filling Audio Streaming

> WebSocket endpoint for streaming visit audio during Form filling sessions

Use this API to stream visit audio over the Partner WebSocket (`GET /ws/stream`) for Form filling sessions. For more information on how to handle the handshake, wire format, message order, and error handling,
and poll for structured data when the session is complete, refer to the [Aaudio streaming](/documentation/ambient-audio-streaming) guide.

<Note>
  Use the `ambient_session_id` from [Create Form Filling session](/form-filling-api-reference/form-filling-sessions/create) while streaming audio.
  Do not use the `ambient_session_id` from an [Create Ambient session](/api-reference/ambient-sessions/create), even though both fields use the name `ambient_session_id`.
</Note>

## Prerequisites

Complete these steps before you open the WebSocket.

<Warning>
  Opening `/ws/stream` before the session and context are ready can cause handshake failures or a broken stream.
</Warning>

* **Authenticate** and obtain `sdp_suki_token`
* **Create a Form filling session** with <Badge color="blue" size="sm">POST</Badge> [`/api/v1/form-filling/session/create`](/form-filling-api-reference/form-filling-sessions/create). Save the `ambient_session_id` from the **201 Created** response
* **Seed session context** (recommended) with <Badge color="blue" size="sm">POST</Badge> [`/api/v1/form-filling/session/{ambient_session_id}/context`](/form-filling-api-reference/form-filling-sessions/context). Include `form_template_id` values when you send context
* **Open the WebSocket** at `wss://sdp.suki-stage.com/ws/stream` (staging) or `wss://sdp.suki.ai/ws/stream` (production). Use your Form filling `ambient_session_id` in the handshake

## Browser clients

Send `Sec-WebSocket-Protocol` during the handshake as one comma-separated string, in this order:

* Subprotocol name
* Form filling session ID (`ambient_session_id`)
* `sdp_suki_token`

<Note>
  Do not add spaces between values unless your client library requires them.
</Note>

Example:

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

The server negotiates this subprotocol to establish the connection.

## Non-browser clients

For mobile apps, backend services, or testing tools, pass headers on the WebSocket upgrade request. Do not use `Sec-WebSocket-Protocol`.

* `sdp_suki_token` - Session token from login
* `sdp_provider_id` - Provider identifier. Optional for standard partners; required for Single Auth Token authentication
* `ambient_session_id` - Form filling session ID from [Create Form Filling session](/form-filling-api-reference/form-filling-sessions/create)

<Warning>
  Sending non-JSON payloads where the server expects JSON can cause parse errors (for example invalid character or null byte errors).
</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
    from datetime import datetime, timezone

    import websocket

    # Form filling ambient_session_id from POST /api/v1/form-filling/session/create
    form_filling_session_id = "<form_filling_ambient_session_id>"
    token = "<sdp_suki_token>"
    sdp_provider_id = "<sdp_provider_id>"  # Optional; Required for Single Auth Token authentication
    CHUNK_BYTES = 3200
    WAV_HEADER_BYTES = 44

    ws_url = "wss://sdp.suki-stage.com/ws/stream"
    ws = websocket.create_connection(
        ws_url,
        header=[
            f"sdp_suki_token: {token}",
            f"sdp_provider_id: {sdp_provider_id}",
            f"ambient_session_id: {form_filling_session_id}",
        ],
    )


    def b64(data: bytes) -> str:
        return base64.b64encode(data).decode("ascii")


    try:
        rfc3339 = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
        ws.send(json.dumps({"type": "START_TIME", "data": b64(rfc3339.encode("utf-8"))}))

        with open("audio.wav", "rb") as f:
            raw = f.read()
        if len(raw) >= 12 and raw[:4] == b"RIFF" and raw[8:12] == b"WAVE":
            pcm = raw[WAV_HEADER_BYTES:]
        else:
            pcm = raw

        for i in range(0, len(pcm), CHUNK_BYTES):
            chunk = pcm[i : i + CHUNK_BYTES]
            ws.send(json.dumps({"type": "AUDIO", "data": b64(chunk)}))

        ws.send(json.dumps({"type": "AUDIO", "data": b64(b"EOF")}))

        while True:
            try:
                print(ws.recv())
            except websocket.WebSocketConnectionClosedException:
                break
    finally:
        ws.close()
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    // Browser example: use Form filling ambient_session_id from POST /api/v1/form-filling/session/create

    const formFillingSessionId = '<form_filling_ambient_session_id>';
    const token = '<sdp_suki_token>';
    const CHUNK_BYTES = 3200;
    const WAV_HEADER_BYTES = 44;

    function utf8ToBase64(s: string): string {
      const bytes = new TextEncoder().encode(s);
      let binary = '';
      for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!);
      return btoa(binary);
    }

    function bytesToBase64(u8: Uint8Array): string {
      let binary = '';
      for (let i = 0; i < u8.length; i++) binary += String.fromCharCode(u8[i]!);
      return btoa(binary);
    }

    const ws = new WebSocket(`wss://sdp.suki-stage.com/ws/stream`, [
      `SukiAmbientAuth,${formFillingSessionId},${token}`,
    ]);

    ws.binaryType = 'arraybuffer';

    ws.onopen = () => {
      const rfc3339 = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
      ws.send(JSON.stringify({ type: 'START_TIME', data: utf8ToBase64(rfc3339) }));

      const input = document.getElementById('audioFile') as HTMLInputElement;
      const file = input.files?.[0];
      if (!file) return;

      const reader = new FileReader();
      reader.onload = () => {
        const buf = new Uint8Array(reader.result as ArrayBuffer);
        let pcm = buf;
        if (
          buf.length >= 12 &&
          buf[0] === 0x52 &&
          buf[1] === 0x49 &&
          buf[2] === 0x46 &&
          buf[3] === 0x46 &&
          buf[8] === 0x57 &&
          buf[9] === 0x41 &&
          buf[10] === 0x56 &&
          buf[11] === 0x45
        ) {
          pcm = buf.subarray(WAV_HEADER_BYTES);
        }
        for (let i = 0; i < pcm.length; i += CHUNK_BYTES) {
          const chunk = pcm.subarray(i, i + CHUNK_BYTES);
          ws.send(JSON.stringify({ type: 'AUDIO', data: bytesToBase64(chunk) }));
        }
        ws.send(
          JSON.stringify({
            type: 'AUDIO',
            data: bytesToBase64(new Uint8Array([0x45, 0x4f, 0x46])),
          }),
        );
      };
      reader.readAsArrayBuffer(file);
    };

    ws.onmessage = (ev) => console.log('Received:', ev.data);
    ws.onerror = (e) => console.error(e);
    ws.onclose = () => console.log('closed');
    ```
  </Tab>
</Tabs>


## OpenAPI

````yaml GET /ws/stream
openapi: 3.0.1
info:
  title: Suki Developer Platform
  description: This is the API Gateway for Suki Developer Platform
  contact: {}
  version: '1.0'
servers:
  - url: https://sdp.suki.ai
    description: Suki Developer Platform - Production
security: []
paths:
  /ws/stream:
    get:
      tags:
        - /ws
      summary: Streams audio to the speech service via WebSocket.
      description: Streams audio to the speech service via WebSocket connection
      parameters:
        - name: Sec-WebSocket-Protocol
          in: header
          description: >-
            Required FOR BROWSER CLIENTS ONLY. Sent during WebSocket handshake.
            Format: 'SukiAmbientAuth,<ambient_session_id>,<sdp_suki_token>'
            (comma-separated, no spaces required between parts).
          schema:
            type: string
        - name: sdp_suki_token
          in: header
          description: >-
            Required FOR NON-BROWSER CLIENTS ONLY: The Suki access token. Sent
            as a standard header with the initial upgrade request.
          required: true
          schema:
            type: string
        - name: ambient_session_id
          in: header
          description: >-
            Required FOR NON-BROWSER CLIENTS ONLY: The ambient session ID. Sent
            as a standard header with the initial upgrade request.
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/ProviderIdHeader'
      responses:
        '101':
          description: >-
            Switching Protocols - Indicates successful WebSocket handshake." //
            Standard successful WS upgrade response
          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." // Can represent successful stream interaction
            after connection
          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." // Added potential 403
          content:
            application/json:
              schema:
                type: string
        '500':
          description: Internal Server Error - Abnormal closure or server-side issue.
          content:
            application/json:
              schema:
                type: string
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl --request GET \
              --url https://sdp.suki.ai/ws/stream \
              --header 'sdp_suki_token: <sdp_suki_token>' \
              --header 'sdp_provider_id: <sdp_provider_id>' \
              --header 'ambient_session_id: <ambient_session_id>'
components:
  parameters:
    ProviderIdHeader:
      name: sdp_provider_id
      in: header
      description: >-
        Unique identifier for the provider. Optional for standard partners.
        **Required** for Single Auth Token authentication, where multiple
        providers share the same `partner_token`.
      required: false
      schema:
        type: string
        example: provider-123

````