Skip to main content

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

Authentication

Authentication uses the same JWT you obtain from the MediSync login flow. See Authentication for how to get a token.
token
string
required
A valid MediSync JWT. It must identify a user, and — if the token carries a permissions list — that list must include "transcribe".
The server checks, in order:
1

Token is valid

The JWT signature and expiry are verified. An invalid, malformed, or expired token is rejected.
2

Token identifies a user

The token must resolve to a user identity. A token with no user identity is rejected.
3

Permission (if scoped)

If the token includes a permissions array, it must contain "transcribe". Tokens without a permissions array are accepted (no scoping applied).
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.

Handshake sequence

1

Open the socket

Connect to the endpoint with the token query parameter set.
const ws = new WebSocket(
  `wss://speech.medisync.me/api/v2/ws/transcribe?token=${token}`
);
ws.binaryType = "arraybuffer";
2

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.
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 }
}));
3

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.
{
  "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": []
}
4

Stream & finish

Send binary audio frames, then flush with end_utterance and end with close. See Messages.

Connect (full example)

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");
};
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())

Close codes

CodeMeaningWhen
1008Policy violation — authentication failedInvalid/expired token, no user identity, or missing transcribe permission. Sent during the handshake, before acceptance.
1000 / 1005Normal closureThe session ended cleanly — you sent close, or the server closed after delivering a fatal error frame.
1011Server errorAn unexpected server-side condition ended the session. Usually preceded by an internal_error frame.
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.

Next steps

Session config

Every config field, defaults, and the session_ready echo.

Messages

Audio frames, control frames, and the transcript schema.