Real-time Speech-to-Text
Transcribe speech to text in real-time using a persistent Socket.IO connection. Audio is streamed from the client to the server, and transcription results are returned as they become available.
Recognition runs on cantonese.ai's own Cantonese speech recognition model and outputs Traditional Chinese. The server detects pauses itself: an utterance is committed after about 0.7 seconds of silence (or after 20 seconds of continuous speech), so there is nothing to configure. Usage is billed at 2 credits per second of audio sent (minimum 3 seconds per session).
How It Works
- Connect to the Socket.IO endpoint at
/api/stt-realtime-socketwith your API key. - Emit a
start-sessionevent to begin a transcription session. - Stream audio by emitting
audio-chunkevents with base64-encoded PCM audio data. - Receive transcription results via the
transcript-messageevent. - Emit
stop-sessionto end the session.
Connection
Connect using a Socket.IO client with the following configuration:
| Parameter | Value |
|---|---|
| path | /api/stt-realtime-socket |
| extraHeaders.x-api-key | Your API key |
Client Events (Client → Server)
| Event | Payload | Description |
|---|---|---|
| start-session | { sampleRate: number, language?: string, endSilenceS?: number } | Start a new transcription session. Sample rate should match your audio source (e.g. 16000); any rate is accepted and resampled server-side. language is accepted for compatibility and ignored (the model recognises Cantonese). endSilenceS (optional, 0.2 to 5, default 2) is how many seconds of silence end an utterance: shorter gives faster committed_transcript messages but splits speech at brief pauses, longer keeps sentences with pauses together. A value outside the range is rejected with session-error. |
| audio-chunk | { audio_base_64: string, sample_rate: number } | Send a chunk of audio data. Audio must be base64-encoded 16-bit PCM. |
| stop-session | (none) | Stop sending audio and end the session. Utterances still in progress are committed before session-closed is emitted. |
Server Events (Server → Client)
| Event | Payload | Description |
|---|---|---|
| session-started | (none) | The transcription session is ready. You can start sending audio chunks. |
| transcript-message | object (see below) | A transcription result. |
| session-error | { error: string } | An error occurred during the session. |
| session-closed | { code: number, reason: string, audio_seconds: number } | The session has ended and every pending utterance has been committed. Code 1000 indicates normal closure; audio_seconds is the amount of audio processed. |
Transcript Message Types
The transcript-message event payload contains a message_type field indicating the type of transcription result:
| message_type | Description |
|---|---|
| session_started | The transcription session has started (sent right after session-started). Contains a session_id. |
| partial_transcript | An in-progress transcription of the current utterance that may change; each one replaces the previous partial. Contains text and segment (the utterance index). |
| committed_transcript | A finalized utterance, delivered in order. Contains text, segment, and start_s / end_s (seconds from the start of the session). |
Audio Format
Audio must be sent as base64-encoded 16-bit PCM (signed integer, little-endian). The recommended sample rate is 16000 Hz, mono channel. If your audio source uses a different sample rate, pass the actual sample rate in the start-session and audio-chunk events.
Example
const { io } = require("socket.io-client");
const API_KEY = "YOUR_API_KEY";
const BASE_URL = "https://cantonese.ai";
// Connect to the real-time STT socket
const socket = io(BASE_URL, {
path: "/api/stt-realtime-socket",
extraHeaders: {
"x-api-key": API_KEY,
},
});
socket.on("connect", () => {
console.log("Connected to real-time STT");
// Start a transcription session
// endSilenceS (optional, 0.2-5, default 2): seconds of silence that end an utterance
socket.emit("start-session", { sampleRate: 16000, endSilenceS: 1 });
});
socket.on("session-started", () => {
console.log("Session started, ready to receive audio");
// Send audio chunks (base64-encoded PCM)
// socket.emit("audio-chunk", {
// audio_base_64: "<base64-encoded-pcm-audio>",
// sample_rate: 16000,
// });
});
socket.on("transcript-message", (message) => {
switch (message.message_type) {
case "partial_transcript":
process.stdout.write(`\rPartial: ${message.text}`);
break;
case "committed_transcript":
console.log(`\nFinal [${message.start_s}s - ${message.end_s}s]: ${message.text}`);
break;
}
});
socket.on("session-error", (data) => {
console.error("Session error:", data.error);
});
socket.on("session-closed", (data) => {
console.log("Session closed:", data.code, data.reason, `${data.audio_seconds}s of audio`);
});
// To stop the session (pending utterances are committed, then session-closed arrives):
// socket.emit("stop-session");
// ...and call socket.disconnect() in the session-closed handler.
Status Codes
A Socket.IO connection without a valid API key (or signed-in browser session) is refused with aconnect_error whose message is Unauthorized.
The Socket.IO handshake endpoint returns standard HTTP status codes:
| Status Code | Description |
|---|---|
| 200 | Success - Socket connection established |
| 401 | Unauthorized - Invalid or missing API key |
| 500 | Internal Server Error - Server encountered an unexpected condition |