---
title: "Stream live transcription from Speak AI over WebSocket"
description: "Connect to the Speak AI live transcription WebSocket at wss://listen.speakai.co, authenticate the handshake, stream audio, and read transcript events."
---

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

# Stream live transcription from Speak AI over WebSocket

You need a Speak AI API key and a client that can hold a WebSocket connection open. Create a key at [app.speakai.co/developers/apikeys](https://app.speakai.co/developers/apikeys).

## What is Speak AI live transcription?

Speak AI live transcription is a streaming service that accepts audio over a WebSocket connection and pushes recognized words back to you one at a time, while the audio is still being captured. It runs on its own host, separate from the REST API, and it creates a media record in your workspace as the session starts, so the finished recording, transcript, and insights are available through the [Speak AI API](/api/) once the stream ends.

This is a written guide rather than generated reference because a WebSocket surface has no request and response pair to describe. OpenAPI models one request against one path, and a live session is one long connection carrying many messages in both directions, so the message flow is the thing that has to be documented.

## Which host do you connect to?

Connect to `wss://listen.speakai.co`. Speak AI stores the host as an `https://` origin in configuration and swaps the scheme when opening the socket, so use the `wss://` form in your own client.

```text
wss://listen.speakai.co
```

## Which path do you use?

The service exposes two paths, and they are not interchangeable: `/v1/live` for your own clients, and `/v1/live-bot` for the Speak AI meeting assistant.

| Path | Protocol | Who uses it |
|---|---|---|
| `/v1/live` | Socket.IO namespace | Browser, mobile, and server clients that capture and send their own audio |
| `/v1/live-bot` | Raw WebSocket | The meeting assistant bot, which streams mixed meeting audio |

> **Note**
>
> `/v1/live` is a Socket.IO namespace, not a raw WebSocket endpoint. A plain `new WebSocket("wss://listen.speakai.co/v1/live")` will not connect. Use a Socket.IO client, which handshakes on the default Socket.IO path and then joins the `/v1/live` namespace.

## How do you authenticate a live transcription connection?

Authentication happens entirely in the handshake query string, before the first audio chunk. The service reads the query parameters, exchanges them for a short-lived access token against the REST API, and refuses to accept audio on any connection that has not completed that exchange.

Which parameters are required depends on the `source` parameter:

| `source` | Required parameters |
|---|---|
| `speak-client` (the default when `source` is absent) | `speak-api-key` |
| `speak-recorder` | `userId` and `recorderToken` |
| `meeting-assistant` | `companyId` and `userId` |

The `speak-api-key` parameter accepts two kinds of credential. One is a developer API key from your workspace. The other is a short-lived live transcription token, a JWT with a `live-transcription` audience claim, which you mint from the REST API so a browser never holds a long-lived key. A token minted this way is rejected everywhere else in the API, and a normal session token is rejected here.

Every other handshake parameter is optional and shapes the session:

| Parameter | Effect |
|---|---|
| `sourceLanguage` | Language of the audio, for example `en-US`. Defaults to `en-US`. |
| `folderId` | Folder the new media record is created in. |
| `mediaType` | `audio` or `video`. `video` extracts an audio track before transcribing. |
| `encoding` | Audio encoding hint passed through to the transcription engine. |
| `sample-rate` | Sample rate hint passed through to the transcription engine. |
| `mode` | `transcribe`, the default, or `s3-only`, which stores the stream without transcribing it. |

## How do you open a connection and stream audio?

Opening a session takes three steps: connect with your credentials in the handshake query, emit `start-live-stream`, then emit `audio-data` for every chunk your recorder produces.

```js
import { io } from "socket.io-client";

const socket = io("wss://listen.speakai.co/v1/live", {
  transports: ["websocket"],
  query: {
"speak-api-key": "sk_test_speak_0000000000000000",
sourceLanguage: "en-US",
mediaType: "audio",
  },
});

socket.on("connect", () => {
  socket.emit("start-live-stream");
});

socket.on("metadata", (message) => {
  console.log("media id:", message.mediaId);
});

socket.on("transcript", (message) => {
  console.log(message.word.text);
});

socket.on("error", (message) => {
  console.error(message);
});

socket.on("close", () => {
  console.log("stream closed");
});
```

Send audio as a binary chunk on the `audio-data` event, then close the session with `stop-transcription`:

```js
socket.emit("audio-data", audioChunk);

socket.emit("stop-transcription");
```

These are the four events the server listens for on a producing connection:

| Event you emit | Payload | What it does |
|---|---|---|
| `start-live-stream` | none | Runs the credential exchange and prepares the session for audio |
| `audio-data` | binary audio chunk | Sends one chunk of captured audio |
| `stop-transcription` | none | Ends transcription and finalizes the recording |
| `subscribe-to-media` | `{ mediaId, targetLanguage }` | Joins an existing session as a listener |

`start-live-stream` takes no payload. The Speak AI web client sends an object with the API key in it, but the server ignores anything passed here and reads credentials from the handshake query only.

Audio sent before `start-live-stream` completes is rejected. The server replies on the `error` event with `{ "type": "authentication-required", "message": "Authentication required before sending audio data" }` and drops the chunk.

## What messages does the server send back?

The server sends five kinds of message, and the message type is also the event name you listen on, so `socket.on("transcript", ...)` receives every message whose `type` is `transcript`.

| Event and `type` | When it arrives |
|---|---|
| `metadata` | The media record was created, or a subscription was accepted |
| `transcript` | A new word was recognized |
| `translated-transcript` | A translated sentence is ready, for subscribers that asked for a target language |
| `error` | Authentication, subscription, or message format failed |
| `close` | The live stream was disconnected |

A `metadata` message carries the identifiers you need to find the recording later:

```json
{
  "type": "metadata",
  "mediaId": "med_0000000000",
  "folderId": "fld_0000000000",
  "userId": "usr_0000000000",
  "name": "Customer interview",
  "timestamp": "2026-07-24T14:02:11.482Z",
  "message": "Live transcript media created successfully"
}
```

A `transcript` message carries exactly one word, with its speaker, its confidence, and its position in the recording:

```json
{
  "type": "transcript",
  "mediaId": "med_0000000000",
  "timestamp": "2026-07-24T14:02:13.905Z",
  "word": {
"id": 1,
"text": "Hello",
"confidence": 0.9938,
"language": "en-US",
"speakerId": "0",
"instances": {
  "startInSec": 0.32,
  "endInSec": 0.58
}
  },
  "message": "New word received",
  "isFinal": true
}
```

Words arrive one per message, so build the sentence on your side by appending them in the order they arrive. Every `transcript` message the service emits carries `isFinal: true`, and there is no separate interim result stream.

An `error` message names the failure in both `message` and `error`:

```json
{
  "type": "error",
  "mediaId": null,
  "timestamp": "2026-07-24T14:02:09.117Z",
  "message": "Invalid API key or failed to get access token",
  "error": "Invalid API key or failed to get access token"
}
```

The service emits these error messages: `API key required for live streaming`, `Invalid API key or failed to get access token`, `Failed to create live transcript media`, `Media ID required for subscription`, and `Invalid message format`. Setup failures arrive in a different shape on the same event, as `{ "type": "authentication-failed", "message": "Failed to setup live streaming session" }`, so check for a `type` of `error` before assuming the `error` field is present.

## How do you listen to a session you are not recording?

A second client can follow a live session by subscribing to its media id instead of sending audio. Connect to the same namespace, then emit `subscribe-to-media` with the `mediaId` that the producing client received in its `metadata` message.

```js
import { io } from "socket.io-client";

const listener = io("wss://listen.speakai.co/v1/live", {
  transports: ["websocket"],
  query: { "speak-api-key": "sk_test_speak_0000000000000000" },
});

listener.on("connect", () => {
  listener.emit("subscribe-to-media", {
mediaId: "med_0000000000",
targetLanguage: "es",
  });
});

listener.on("transcript", (message) => {
  console.log(message.word.text);
});

listener.on("translated-transcript", (message) => {
  console.log(message.translatedText);
});

listener.emit("unsubscribe-from-media", { mediaId: "med_0000000000" });
```

`targetLanguage` is optional. Include it and the subscriber also receives `translated-transcript` messages, which carry the translated sentence in `translatedText` alongside the `originalWords` it came from, plus `sourceLanguage` and `targetLanguage`. Leave it out and the subscriber receives the original words only.

## How does the meeting assistant path differ?

The `/v1/live-bot` path is a raw WebSocket endpoint that accepts the meeting assistant's mixed audio, and it is one way only. Speak AI configures it for you when you turn live transcription on for the meeting assistant, so you do not open this connection yourself.

The connection carries three query parameters, `companyId`, `userId`, and `source`, where `source` is `meeting-assistant`:

```text
wss://listen.speakai.co/v1/live-bot?companyId=COMPANY_ID&userId=USER_ID&source=meeting-assistant
```

Messages on this path are JSON envelopes rather than binary frames. Each one has an `event` of `audio_mixed_raw.data` and a `data` object holding base64 audio in `data.data.buffer`, a `data.data.timestamp` with `relative` and `absolute` fields, and `bot`, `recording`, and `realtime_endpoint` objects that each carry an `id`. The first message doubles as the handshake, and the connection has 30 seconds to authenticate before it is closed. Audio that arrives during that window is buffered, up to 50 chunks, and replayed once authentication succeeds.

Nothing is sent back on this path. The service holds the `/v1/live-bot` socket open to receive audio and publishes the resulting words to `/v1/live` subscribers instead, so a client that wants to display a live meeting transcript subscribes to the media id on `/v1/live`.

## What happens when a session ends?

A session ends when the client emits `stop-transcription`, when the socket disconnects, or when the service closes the stream, and all three run the same cleanup: the transcription engine is closed, the audio is finalized to storage, and the media record moves on to full analysis.

When the service closes the stream, it sends a `close` message first:

```json
{
  "type": "close",
  "timestamp": "2026-07-24T14:31:44.006Z",
  "message": "Live stream disconnected"
}
```

Speak AI defines no custom WebSocket close codes for live transcription. The one code the service sets explicitly is the standard `1000`, used when it disconnects a `/v1/live` client after sending `close`. Treat any other code you observe as coming from the transport or the network, not from Speak AI.

## How does this relate to the /v1/live-transcription REST routes?

Three REST routes sit behind live transcription, all under `https://api.speakai.co/v1/live-transcription`, and the streaming service calls two of them on your behalf during a session. You do not need to call `create` or `update` yourself when you stream through `/v1/live`, because the service creates the media record when the session starts and writes words and the finished audio back as it runs.

| Route | Who calls it |
|---|---|
| `POST /v1/live-transcription/create` | The streaming service, when your session starts |
| `POST /v1/live-transcription/update/{mediaId}` | The streaming service, as words and audio are finalized |
| `POST /v1/live-transcription/token` | You, to mint a short-lived live transcription token for a browser |

The `token` route is the one worth calling directly. It returns a token and its lifetime, and it is rate limited to 5 requests per minute per IP address, so mint one per session rather than per connection attempt. Full request and response detail for all three lives in the [Speak AI API reference](/api/), and the header and token model they share is described in [API authentication](/api/authentication/).

Once a live session finishes, the recording behaves like any other file in your workspace. Read its transcript, speakers, and insights through the [media endpoints](/api/media/), or through the [Speak AI MCP server](/mcp) if an agent is doing the reading.

## Related guides

- [Speak AI MCP server, connect Claude, ChatGPT, and more](/mcp)
- [Speak AI MCP server authentication and rate limits](/mcp/authentication)
- [Speak AI API reference for audio, video, and text data](/api/)
- [Upload audio and video to Speak AI and read insights](/api/media/)

Source: https://docs.speakai.co/mcp/live-transcription/index.mdx
