JavaScript SDK

Scribe: real-time speech-to-text transcription in JavaScript

For an overview of Scribe and its capabilities, see the Speech to Text overview. For step-by-step usage guides, see Client-side streaming.

Installation

npm install @elevenlabs/client
# or
yarn add @elevenlabs/client
# or
pnpm install @elevenlabs/client

Use the ElevenLabs speech-to-text skill to transcribe audio from your AI coding assistant:

npx skills add elevenlabs/skills --skill speech-to-text

This library can be used in any JavaScript-based project. If you are using React, consider the useScribe hook which provides built-in state management and lifecycle handling.

Usage

Here is a minimal working example that connects to Scribe and logs transcription results:

import { Scribe, RealtimeEvents } from "@elevenlabs/client";
const token = await fetchTokenFromServer();
const connection = Scribe.connect({
token,
modelId: "scribe_v2_realtime",
microphone: {
echoCancellation: true,
noiseSuppression: true,
},
});
connection.on(RealtimeEvents.PARTIAL_TRANSCRIPT, (data) => {
console.log("Partial:", data.text);
});
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, (data) => {
console.log("Committed:", data.text);
});
// Later, close the connection
connection.close();

Getting a token

Scribe requires a single-use token for authentication. Create an API endpoint on your server:

// Node.js server
app.get("/scribe-token", yourAuthMiddleware, async (req, res) => {
const response = await fetch("https://api.elevenlabs.io/v1/single-use-token/realtime_scribe", {
method: "POST",
headers: {
"xi-api-key": process.env.ELEVENLABS_API_KEY,
},
});
const data = await response.json();
res.json({ token: data.token });
});

Your ElevenLabs API key is sensitive. Never expose it to the client. Always generate the token on the server.

// Client
const fetchToken = async () => {
const response = await fetch("/scribe-token");
const { token } = await response.json();
return token;
};

Connection options

Scribe.connect() accepts either microphone options or manual audio options. Both share a common set of base options.

Base options

PropertyTypeDefaultDescription
tokenstringSingle-use token for WebSocket authentication.
modelIdstringModel ID (e.g., "scribe_v2_realtime").
baseUristring"wss://api.elevenlabs.io"Custom WebSocket base URI.
commitStrategyCommitStrategy"manual""manual" or "vad".
vadSilenceThresholdSecsnumber1.5Seconds of silence before VAD commits (0.3-3.0).
vadThresholdnumber0.4VAD sensitivity (0.1-0.9, lower is more sensitive).
minSpeechDurationMsnumber100Minimum speech duration in ms (50-2000).
minSilenceDurationMsnumber100Minimum silence duration in ms (50-2000).
languageCodestringISO-639-1 or ISO-639-3 language code. Leave empty for auto-detection.
includeTimestampsbooleanfalseReceive word-level timestamps via the COMMITTED_TRANSCRIPT_WITH_TIMESTAMPS event.

Microphone options

Pass a microphone object to stream audio directly from the user’s microphone. The connection handles getUserMedia and audio encoding automatically.

const connection = Scribe.connect({
token,
modelId: "scribe_v2_realtime",
microphone: {
deviceId: "optional-device-id",
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
});
PropertyTypeDescription
deviceIdstringSpecific microphone device ID.
echoCancellationbooleanEnable echo cancellation.
noiseSuppressionbooleanEnable noise suppression.
autoGainControlbooleanEnable automatic gain control.

Manual audio options

Pass audioFormat and sampleRate to send audio data manually via connection.send().

import { AudioFormat } from "@elevenlabs/client";
const connection = Scribe.connect({
token,
modelId: "scribe_v2_realtime",
audioFormat: AudioFormat.PCM_16000,
sampleRate: 16000,
});
PropertyTypeDescription
audioFormatAudioFormatAudio encoding format (e.g., AudioFormat.PCM_16000).
sampleRatenumberSample rate in Hz. Must match audioFormat.

AudioFormat enum

enum AudioFormat {
PCM_8000 = "pcm_8000",
PCM_16000 = "pcm_16000",
PCM_22050 = "pcm_22050",
PCM_24000 = "pcm_24000",
PCM_44100 = "pcm_44100",
PCM_48000 = "pcm_48000",
ULAW_8000 = "ulaw_8000",
}

Microphone mode

Stream audio directly from the user’s microphone:

import { Scribe, RealtimeEvents } from "@elevenlabs/client";
async function transcribeFromMicrophone() {
const token = await fetchToken();
const connection = Scribe.connect({
token,
modelId: "scribe_v2_realtime",
microphone: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
});
connection.on(RealtimeEvents.PARTIAL_TRANSCRIPT, (data) => {
document.getElementById("live").textContent = data.text;
});
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, (data) => {
const el = document.createElement("p");
el.textContent = data.text;
document.getElementById("transcripts").appendChild(el);
document.getElementById("live").textContent = "";
});
document.getElementById("stop").addEventListener("click", () => {
connection.close();
});
}

Manual audio mode (file transcription)

Transcribe pre-recorded audio files by sending audio data manually:

import { Scribe, RealtimeEvents, AudioFormat } from "@elevenlabs/client";
async function transcribeFile(file) {
const token = await fetchToken();
const connection = Scribe.connect({
token,
modelId: "scribe_v2_realtime",
audioFormat: AudioFormat.PCM_16000,
sampleRate: 16000,
});
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, (data) => {
console.log("Transcript:", data.text);
});
// Decode audio file
const arrayBuffer = await file.arrayBuffer();
const audioContext = new AudioContext({ sampleRate: 16000 });
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
// Convert to PCM16
const channelData = audioBuffer.getChannelData(0);
const pcmData = new Int16Array(channelData.length);
for (let i = 0; i < channelData.length; i++) {
const sample = Math.max(-1, Math.min(1, channelData[i]));
pcmData[i] = sample < 0 ? sample * 32768 : sample * 32767;
}
// Send in chunks
const chunkSize = 4096;
for (let offset = 0; offset < pcmData.length; offset += chunkSize) {
const chunk = pcmData.slice(offset, offset + chunkSize);
const bytes = new Uint8Array(chunk.buffer);
const base64 = btoa(String.fromCharCode(...bytes));
connection.send({ audioBase64: base64 });
await new Promise((resolve) => setTimeout(resolve, 50));
}
// Commit and close
connection.commit();
}

RealtimeConnection

Scribe.connect() returns a RealtimeConnection instance with the following methods.

on(event, listener)

Register an event listener. See Events for available event types.

connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, (data) => {
console.log("Committed:", data.text);
});

off(event, listener)

Remove a previously registered event listener.

const handler = (data) => console.log(data.text);
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, handler);
// Later
connection.off(RealtimeEvents.COMMITTED_TRANSCRIPT, handler);

send(data)

Send audio data to Scribe (manual audio mode only).

connection.send({
audioBase64: base64AudioChunk,
commit: false, // Optional: commit immediately
sampleRate: 16000, // Optional: override sample rate
previousText: "Previous transcription text", // Optional: context from a previous transcription
});

The previousText field can only be sent in the first audio chunk of a session. Sending it in subsequent chunks results in an error.

commit()

Manually commit the current transcription. Only needed when using CommitStrategy.MANUAL.

connection.commit();

close()

Close the WebSocket connection and clean up resources (microphone stream, audio context).

connection.close();

Events

Register event listeners using connection.on(event, listener). All events are available as constants on the RealtimeEvents enum.

Transcription events

EventDataDescription
SESSION_STARTED{ session_id: string }Scribe session started.
PARTIAL_TRANSCRIPT{ text: string }Interim transcription result.
COMMITTED_TRANSCRIPT{ text: string }Finalized transcription result.
COMMITTED_TRANSCRIPT_WITH_TIMESTAMPS{ text: string; language_code?: string; words?: WordsItem[] }Finalized result with word-level timing.

The WordsItem type contains word-level timing information:

interface WordsItem {
text?: string; // Word text
start?: number; // Start time in seconds
end?: number; // End time in seconds
type?: "word" | "spacing"; // Token type
speaker_id?: string; // Speaker identifier
}

Connection events

EventDataDescription
OPENEventWebSocket connection opened.
CLOSEEventWebSocket connection closed.
ERRORError | EventGeneric error.

Error events

All error events receive { error: string }.

EventDescription
AUTH_ERRORAuthentication error.
QUOTA_EXCEEDEDUsage quota exceeded.
COMMIT_THROTTLEDCommit request throttled.
TRANSCRIBER_ERRORTranscription engine error.
UNACCEPTED_TERMSTerms of service not accepted.
RATE_LIMITEDRate limited.
INPUT_ERRORInvalid input format.
QUEUE_OVERFLOWProcessing queue full.
RESOURCE_EXHAUSTEDServer resources at capacity.
SESSION_TIME_LIMIT_EXCEEDEDMaximum session time reached.
CHUNK_SIZE_EXCEEDEDAudio chunk too large.
INSUFFICIENT_AUDIO_ACTIVITYNot enough audio activity to maintain the connection.

Commit strategies

Control when transcriptions are committed:

import { Scribe, CommitStrategy } from '@elevenlabs/client';
// Manual (default): you control when to commit
const connection = Scribe.connect({
token,
modelId: 'scribe_v2_realtime',
audioFormat: AudioFormat.PCM_16000,
sampleRate: 16000,
commitStrategy: CommitStrategy.MANUAL,
});
// Send audio, then commit when ready
connection.send({ audioBase64: chunk });
connection.commit();
// Voice Activity Detection: Scribe detects silences and commits automatically
const connection = Scribe.connect({
token,
modelId: 'scribe_v2_realtime',
microphone: { echoCancellation: true },
commitStrategy: CommitStrategy.VAD,
});

For more details, see Transcripts and commit strategies.

Complete example

Here is a complete example that transcribes microphone audio with VAD-based commit strategy:

import { Scribe, RealtimeEvents, CommitStrategy } from "@elevenlabs/client";
async function startTranscription() {
const token = await fetchToken();
const connection = Scribe.connect({
token,
modelId: "scribe_v2_realtime",
commitStrategy: CommitStrategy.VAD,
microphone: {
echoCancellation: true,
noiseSuppression: true,
},
});
connection.on(RealtimeEvents.SESSION_STARTED, (data) => {
console.log("Session started:", data.session_id);
});
connection.on(RealtimeEvents.PARTIAL_TRANSCRIPT, (data) => {
document.getElementById("live").textContent = data.text;
});
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, (data) => {
const el = document.createElement("p");
el.textContent = data.text;
document.getElementById("transcripts").appendChild(el);
document.getElementById("live").textContent = "";
});
connection.on(RealtimeEvents.ERROR, (error) => {
console.error("Scribe error:", error);
});
// Stop button
document.getElementById("stop").addEventListener("click", () => {
connection.close();
});
}
document.getElementById("start").addEventListener("click", startTranscription);