Skip to content

Stream live transcription from Speak AI over WebSocket

Connect to the Speak AI live transcription WebSocket at wss://listen.speakai.co, authenticate the handshake, stream audio, and read transcript events.

Updated View as MarkdownAsk ClaudeOpen in ChatGPTllms.txt

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.

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

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

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.

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:

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:

{
  "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:

{
  "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:

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

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:

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:

{
  "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, and the header and token model they share is described in 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, or through the Speak AI MCP server if an agent is doing the reading.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close