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

# Connection

> Endpoint, JWT authentication via query parameter, the handshake sequence, and close codes

## Endpoint

```
wss://speech.medisync.me/api/v2/ws/transcribe?token=<JWT>
```

Connections are **WSS only** (TLS). The JWT is supplied as the `token` **query parameter** and is verified during the WebSocket handshake — before the connection is accepted.

<Warning>
  Because the JWT is passed in the URL, always use `wss://` (never `ws://`) so the query string is encrypted in transit. Treat the URL as a secret and never log it.
</Warning>

## Authentication

Authentication uses the same JWT you obtain from the MediSync login flow. See [Authentication](/authentication) for how to get a token.

<ParamField query="token" type="string" required>
  A valid MediSync JWT. It must identify a user, and — if the token carries a `permissions` list — that list must include `"transcribe"`.
</ParamField>

The server checks, in order:

<Steps>
  <Step title="Token is valid">
    The JWT signature and expiry are verified. An invalid, malformed, or expired token is rejected.
  </Step>

  <Step title="Token identifies a user">
    The token must resolve to a user identity. A token with no user identity is rejected.
  </Step>

  <Step title="Permission (if scoped)">
    If the token includes a `permissions` array, it must contain `"transcribe"`. Tokens without a `permissions` array are accepted (no scoping applied).
  </Step>
</Steps>

<Note>
  If **any** of these checks fails, the socket is closed with code **`1008`** during the handshake — before the connection is accepted. No `session_ready` and no JSON `error` frame are delivered in this case; the close code is the signal.
</Note>

## Handshake sequence

<Steps>
  <Step title="Open the socket">
    Connect to the endpoint with the `token` query parameter set.

    ```javascript theme={null}
    const ws = new WebSocket(
      `wss://speech.medisync.me/api/v2/ws/transcribe?token=${token}`
    );
    ws.binaryType = "arraybuffer";
    ```
  </Step>

  <Step title="Send the config frame (first message)">
    Your first message **must** be a single JSON `config` text frame. Send it as soon as the socket opens. The server waits about **10 seconds** for it; if none arrives it closes with a `config_timeout` error.

    ```javascript theme={null}
    ws.onopen = () => ws.send(JSON.stringify({
      type: "config",
      audio: { encoding: "pcm_s16le", sample_rate: 16000, channels: 1 },
      language: "de",
      chunk: { strategy: "balanced" },
      diarization: { enabled: false }
    }));
    ```
  </Step>

  <Step title="Receive session_ready">
    The server replies with a `session_ready` frame containing your `session_id` and the **effective** config. Only now should you begin streaming audio.

    ```json theme={null}
    {
      "type": "session_ready",
      "session_id": "f0c1a9b2c3d4e5f6a7b8c9d0e1f2a3b4",
      "effective": {
        "language": "de",
        "diarization": false,
        "chunk": { "strategy": "balanced", "target_seconds": 5.0, "max_seconds": 20.0 },
        "timeline_offset_seconds": 0
      },
      "server": { "max_segment_seconds": 25.0, "protocol_version": "2.0" },
      "warnings": []
    }
    ```
  </Step>

  <Step title="Stream & finish">
    Send binary audio frames, then flush with `end_utterance` and end with `close`. See [Messages](/websocket/messages).
  </Step>
</Steps>

## Connect (full example)

<CodeGroup>
  ```javascript JavaScript (browser) theme={null}
  const token = "<YOUR_JWT>";
  const ws = new WebSocket(
    `wss://speech.medisync.me/api/v2/ws/transcribe?token=${token}`
  );
  ws.binaryType = "arraybuffer";

  ws.onopen = () => {
    ws.send(JSON.stringify({
      type: "config",
      audio: { encoding: "pcm_s16le", sample_rate: 16000, channels: 1 },
      language: "de",
      chunk: { strategy: "balanced" },
      diarization: { enabled: false }
    }));
  };

  ws.onmessage = (event) => {
    const msg = JSON.parse(event.data);
    switch (msg.type) {
      case "session_ready":
        console.log("session:", msg.session_id);
        startMicrophone(ws);        // begin sending PCM binary frames
        break;
      case "transcript":
        render(msg.text, msg.segment);
        break;
      case "error":
        console.error(msg.code, msg.message);
        if (msg.fatal) ws.close();
        break;
    }
  };

  ws.onclose = (e) => {
    if (e.code === 1008) console.error("Authentication failed");
  };
  ```

  ```python Python (asyncio) theme={null}
  import asyncio, json, websockets

  TOKEN = "<YOUR_JWT>"
  URL = f"wss://speech.medisync.me/api/v2/ws/transcribe?token={TOKEN}"

  async def main():
      # max_size=None allows large inbound frames; audio is sent as bytes.
      async with websockets.connect(URL, max_size=None) as ws:
          await ws.send(json.dumps({
              "type": "config",
              "audio": {"encoding": "pcm_s16le", "sample_rate": 16000, "channels": 1},
              "language": "de",
              "chunk": {"strategy": "balanced"},
              "diarization": {"enabled": False},
          }))
          ready = json.loads(await ws.recv())
          assert ready["type"] == "session_ready", ready
          print("session:", ready["session_id"])
          # ... stream audio and read transcripts (see Messages) ...

  asyncio.run(main())
  ```
</CodeGroup>

## Close codes

| Code            | Meaning                                      | When                                                                                                                       |
| --------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `1008`          | Policy violation — **authentication failed** | Invalid/expired token, no user identity, or missing `transcribe` permission. Sent during the handshake, before acceptance. |
| `1000` / `1005` | Normal closure                               | The session ended cleanly — you sent `close`, or the server closed after delivering a fatal `error` frame.                 |
| `1011`          | Server error                                 | An unexpected server-side condition ended the session. Usually preceded by an `internal_error` frame.                      |

<Note>
  For a fatal application error **after** the socket is accepted (for example `session_conflict` or `overloaded`), the server first sends a JSON `error` frame with `"fatal": true` and then closes the socket. Branch on the error `code`, not only on the close code. See [Errors](/websocket/errors).
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Session config" icon="sliders" href="/websocket/session-config">
    Every config field, defaults, and the `session_ready` echo.
  </Card>

  <Card title="Messages" icon="messages" href="/websocket/messages">
    Audio frames, control frames, and the transcript schema.
  </Card>
</CardGroup>
