Speech To Text
ElevenLabsOfficialPublished by ElevenLabs
- Skill
- Tool integration
- Automation and integrations
- MIT
- Medium risk
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a comprehensive interface for the ElevenLabs Scribe v2 speech-to-text engine. It supports batch processing of audio and video files, as well as low-latency real-time streaming for live applications. The tool includes advanced features such as speaker diarization, word-level timestamps, multi-channel audio processing, and keyterm boosting to improve recognition accuracy for specialized terminology.
What it does
- Transcribes audio and video files into text with high accuracy.
- Supports real-time streaming transcription with ultra-low latency.
- Identifies individual speakers in multi-speaker audio recordings.
- Provides word-level timestamps for precise timing and synchronization.
- Allows keyterm boosting to improve recognition of specific jargon or names.
- Supports automatic language detection across 90+ languages.
When to use it
- When building applications that require live voice-to-text feedback.
- When processing large volumes of meeting recordings or interviews.
- When you need to generate accurate subtitles for video content.
- When working with multi-channel audio where speakers are isolated.
- When you need specialized transcription for medical or clinical audio.
Advantages
- State-of-the-art transcription accuracy across 90+ languages.
- Flexible integration options via Python, JavaScript, or CLI.
- Robust real-time capabilities with configurable commit strategies.
- Advanced features like speaker role detection and speaker library matching.
- Comprehensive support for various audio and video file formats.
Usage examples
- “Transcribe this audio file using the Scribe v2 model.”
- The agent uses the ElevenLabs client to process the file and returns the full text transcription.
- “Identify who is speaking in this meeting recording.”
- The agent processes the audio with diarization enabled, returning a transcript labeled with speaker IDs.
- “Set up a real-time transcription stream for a live microphone.”
- The agent initializes a real-time connection using the VAD commit strategy to provide live text updates.
- “Help the model recognize technical terms like 'ElevenLabs' and 'Scribe'.”
- The agent includes the specified keyterms in the transcription request to improve accuracy for those specific words.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/elevenlabs/speech-to-text/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip speech-to-text.zip -d .agents/skills
Documentation by ElevenLabs
ElevenLabs Speech-to-Text
Transcribe audio to text with Scribe v2 - supports 90+ languages, speaker diarization, and word-level timestamps.
Setup, before writing any code: if
ELEVENLABS_API_KEYisn't set for this project, runnpx -y @elevenlabs/cli@latest onboard initfrom the app's folder first and follow what it prints: it signs the user in and stores a key for the project, so nobody copies one by hand. Otherwise see the Installation Guide. For JavaScript, use@elevenlabs/*packages only.
Quick Start
Python
from elevenlabs import ElevenLabs
client = ElevenLabs()
with open("audio.mp3", "rb") as audio_file:
result = client.speech_to_text.convert(file=audio_file, model_id="scribe_v2")
print(result.text)JavaScript
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import { createReadStream } from "fs";
const client = new ElevenLabsClient();
const result = await client.speechToText.convert({
file: createReadStream("audio.mp3"),
modelId: "scribe_v2",
});
console.log(result.text);CLI
elevenlabs speech-to-text convert --file audio.mp3 --model-id scribe_v2Models
| Model ID | Description | Best For |
|---|---|---|
scribe_v2 | State-of-the-art accuracy, 90+ languages | Batch transcription, subtitles, long-form audio |
scribe_v2_medical | Specialized recognition for medical and clinical audio, 90+ languages | Clinical documentation, medical dictation, patient calls |
scribe_v2_realtime | Low latency (~150ms) | Live transcription, voice agents |
scribe_v2_realtime_turbo | Realtime transcription variant | Live transcription |
scribe_v2_realtime_lite | Realtime transcription variant | Live transcription |
Transcription with Timestamps
Word-level timestamps include type classification and speaker identification:
result = client.speech_to_text.convert(
file=audio_file, model_id="scribe_v2", timestamps_granularity="word"
)
for word in result.words:
print(f"{word.text}: {word.start}s - {word.end}s (type: {word.type})")
Speaker Diarization
Identify WHO said WHAT - the model labels each word with a speaker ID, useful for meetings, interviews, or any multi-speaker audio:
result = client.speech_to_text.convert(
file=audio_file,
model_id="scribe_v2",
diarize=True
)
for word in result.words:
print(f"[{word.speaker_id}] {word.text}")For call recordings, the batch API can label diarized speakers as agent and customer by setting detect_speaker_roles=true alongside diarize=true. This option is not compatible with use_multi_channel=true.
If your workspace has registered speaker profiles, set use_speaker_library=true with diarize=true to match detected speakers against the speaker library.
elevenlabs speech-to-text convert \
--file call.mp3 \
--model-id scribe_v2 \
--diarize true \
--detect-speaker-roles true \
--use-speaker-library trueMultichannel Audio
Use use_multi_channel=true when each speaker is isolated on a separate audio channel. By default, the API returns one transcript per channel under transcripts; set multichannel_output_style="combined" to receive one transcript merged by timestamp, with channel_index on each word.
result = client.speech_to_text.convert(
file=audio_file,
model_id="scribe_v2",
use_multi_channel=True,
multichannel_output_style="combined",
)Keyterm Prompting
Help the model recognize specific words it might otherwise mishear - product names, technical jargon, or unusual spellings (up to 100 terms):
result = client.speech_to_text.convert(
file=audio_file,
model_id="scribe_v2",
keyterms=["ElevenLabs", "Scribe", "API"]
)Language Detection
Automatic detection with optional language hint:
result = client.speech_to_text.convert(
file=audio_file,
model_id="scribe_v2",
language_code="eng" # ISO 639-1 or ISO 639-3 code
)
print(f"Detected: {result.language_code} ({result.language_probability:.0%})")Supported Formats
Audio: MP3, WAV, M4A, FLAC, OGG, WebM, AAC, AIFF, Opus Video: MP4, AVI, MKV, MOV, WMV, FLV, WebM, MPEG, 3GPP
Limits: Up to 5.0GB file size, 10 hours duration
Response Format
{
"text": "The full transcription text",
"language_code": "eng",
"language_probability": 0.98,
"words": [
{"text": "The", "start": 0.0, "end": 0.15, "type": "word", "speaker_id": "speaker_0"},
{"text": " ", "start": 0.15, "end": 0.16, "type": "spacing", "speaker_id": "speaker_0"}
]
}Word types:
word- An actual spoken wordspacing- Whitespace between words (useful for precise timing)audio_event- Non-speech sounds the model detected (laughter, applause, music, etc.)
Error Handling
try:
result = client.speech_to_text.convert(file=audio_file, model_id="scribe_v2")
except Exception as e:
print(f"Transcription failed: {e}")Common errors:
401: Invalid API key
422: Invalid parameters
429: Rate limit exceeded
Tracking Costs
Monitor usage via request-id response header:
response = client.speech_to_text.with_raw_response.convert(file=audio_file, model_id="scribe_v2")
result = response.data
print(f"Request ID: {response.headers.get('request-id')}")Real-Time Streaming
For live transcription with ultra-low latency (~150ms), use the real-time API. The real-time API produces two types of transcripts:
Partial transcripts: Interim results that update frequently as audio is processed - use these for live feedback (e.g., showing text as the user speaks)
Committed transcripts: Final, stable results after you "commit" - use these as the source of truth for your application
A "commit" tells the model to finalize the current segment. You can commit manually (e.g., when the user pauses) or use Voice Activity Detection (VAD) to auto-commit on silence.
Python (Server-Side)
import asyncio
from elevenlabs import CommitStrategy, ElevenLabs, RealtimeEvents, RealtimeUrlOptions
client = ElevenLabs()
async def transcribe_realtime():
done = asyncio.Event()
connection = await client.speech_to_text.realtime.connect(RealtimeUrlOptions(
model_id="scribe_v2_realtime",
url="https://npr-ice.streamguys1.com/live.mp3",
commit_strategy=CommitStrategy.VAD, # URL streams never commit on their own
include_timestamps=True,
keyterms=["ElevenLabs", "Scribe"],
no_verbatim=True,
))
def on_error(error):
print(f"Error: {error}")
done.set()
connection.on(RealtimeEvents.PARTIAL_TRANSCRIPT, lambda data: print(f"Partial: {data.get('text', '')}"))
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, lambda data: print(f"Final: {data.get('text', '')}"))
connection.on(RealtimeEvents.ERROR, on_error)
connection.on(RealtimeEvents.CLOSE, done.set)
try:
await done.wait()
finally:
await connection.close()
asyncio.run(transcribe_realtime())JavaScript (Client-Side with React)
import { useScribe, CommitStrategy } from "@elevenlabs/react";
function TranscriptionComponent() {
const [transcript, setTranscript] = useState("");
const scribe = useScribe({
modelId: "scribe_v2_realtime",
commitStrategy: CommitStrategy.VAD, // Auto-commit on silence for mic input
keyterms: ["ElevenLabs", "Scribe"],
noVerbatim: true,
includeLanguageDetection: true,
onPartialTranscript: (data) => console.log("Partial:", data.text),
onCommittedTranscript: (data) => setTranscript((prev) => prev + data.text),
});
const start = async () => {
// Get token from your backend (never expose API key to client)
const { token } = await fetch("/scribe-token").then((r) => r.json());
await scribe.connect({
token,
microphone: { echoCancellation: true, noiseSuppression: true },
});
};
return <button onClick={start}>Start Recording</button>;
}Commit Strategies
| Strategy | Description |
|---|---|
| Manual | You call commit() when ready - use for file processing or when you control the audio segments |
| VAD | Voice Activity Detection auto-commits when silence is detected - use for live microphone input |
Set includeLanguageDetection: true to receive the detected language code in delayed final
transcript events.
// React: set commitStrategy on the hook (recommended for mic input)
import { useScribe, CommitStrategy } from "@elevenlabs/react";
const scribe = useScribe({
modelId: "scribe_v2_realtime",
commitStrategy: CommitStrategy.VAD,
keyterms: ["ElevenLabs", "Scribe"],
noVerbatim: true,
// Optional VAD tuning:
vadSilenceThresholdSecs: 1.5,
vadThreshold: 0.4,
});// JavaScript client: pass vad config on connect
const connection = await client.speechToText.realtime.connect({
modelId: "scribe_v2_realtime",
keyterms: ["ElevenLabs", "Scribe"],
noVerbatim: true,
vad: {
silenceThresholdSecs: 1.5,
threshold: 0.4,
},
});Event Types
| Event | Description |
|---|---|
partial_transcript | Live interim results |
final_transcript | Stable segment result sent before the segment is committed |
final_transcript_with_timestamps | Delayed final result with timestamps and/or detected language |
committed_transcript | Final results after commit |
committed_transcript_with_timestamps | Final with word timing |
committed_transcript_entities | Entities detected in a committed segment |
edited_transcript | Original and edited text for a committed segment when transcript editing is enabled |
invalid_request | Connection parameters were rejected and the session closes |
error | Error occurred |
See real-time references for complete documentation.
References
Files
- ATTRIBUTION.mdAdded by AutoGineer812 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md10.6 KiB
- references/installation.md2.9 KiB
- references/realtime-client-side.md7.3 KiB
- references/realtime-commit-strategies.md4.6 KiB
- references/realtime-events.md9.0 KiB
- references/realtime-server-side.md11.5 KiB
- references/transcription-options.md10.7 KiB
references/realtime-server-side.md
Server-Side Real-Time Streaming
Transcribe audio streams in real-time from your server with ultra-low latency.
Installation
# Python
pip install --upgrade elevenlabs python-dotenv pydub
# JavaScript
npm install @elevenlabs/elevenlabs-js@latest dotenvWarning: Do not use
npm install elevenlabs- that's an outdated v1.x package. Always use@elevenlabs/elevenlabs-js.
Configuration
Store your API key in a .env file:
ELEVENLABS_API_KEY=<your_api_key_here>Realtime Session Options
| Purpose | Python / WebSocket | JavaScript |
|---|---|---|
| Limit language detection to expected additional languages | secondary_languages | secondaryLanguages |
| Return detected language in delayed final events | include_language_detection | includeLanguageDetection |
| Detect entities in committed segments | entity_detection | entityDetection |
| Redact entities in committed segments | entity_redaction, entity_redaction_mode | entityRedaction, entityRedactionMode |
| Apply a natural-language edit instruction to committed segments | transcript_edit | transcriptEdit |
| Reduce false activation from background speech and noise | filter_background_audio | filterBackgroundAudio |
| Disable history and logging for an enterprise zero-retention session | enable_logging=False | enableLogging: false |
secondary_languages accepts additional ISO-639-1 or ISO-639-3 codes. Entity detection accepts
all, a supported entity type or category, or a list of them; entity redaction accepts the same
values. Background audio filtering cannot be combined with timestamp output. A transcript edit
instruction can contain up to 2,000 characters and cannot be combined with entity detection or
entity redaction. It adds a 30% premium, billed for at least 10 seconds of audio per committed
transcript.
Stream from URL
Python
from dotenv import load_dotenv
import os
import asyncio
from elevenlabs import ElevenLabs
from elevenlabs import CommitStrategy, RealtimeEvents, RealtimeUrlOptions
load_dotenv()
async def main():
elevenlabs = ElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))
stop_event = asyncio.Event()
connection = await elevenlabs.speech_to_text.realtime.connect(RealtimeUrlOptions(
model_id="scribe_v2_realtime",
url="https://npr-ice.streamguys1.com/live.mp3",
commit_strategy=CommitStrategy.VAD, # URL streams never commit on their own
include_timestamps=True,
keyterms=["ElevenLabs", "Scribe"],
no_verbatim=True,
))
def on_partial_transcript(data):
print(f"Partial: {data.get('text', '')}")
def on_committed_transcript(data):
print(f"Committed: {data.get('text', '')}")
def on_error(error):
print(f"Error: {error}")
stop_event.set()
def on_close():
print("Connection closed")
connection.on(RealtimeEvents.PARTIAL_TRANSCRIPT, on_partial_transcript)
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, on_committed_transcript)
connection.on(RealtimeEvents.ERROR, on_error)
connection.on(RealtimeEvents.CLOSE, on_close)
try:
await stop_event.wait()
except KeyboardInterrupt:
print("\nStopping transcription...")
finally:
await connection.close()
if __name__ == "__main__":
asyncio.run(main())JavaScript
import "dotenv/config";
import { ElevenLabsClient, RealtimeEvents } from "@elevenlabs/elevenlabs-js";
const elevenlabs = new ElevenLabsClient();
const connection = await elevenlabs.speechToText.realtime.connect({
modelId: "scribe_v2_realtime",
url: "https://npr-ice.streamguys1.com/live.mp3",
includeTimestamps: true,
keyterms: ["ElevenLabs", "Scribe"],
noVerbatim: true,
});
connection.on(RealtimeEvents.PARTIAL_TRANSCRIPT, (transcript) => {
console.log("Partial transcript", transcript);
});
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, (transcript) => {
console.log("Committed transcript", transcript);
});
connection.on(RealtimeEvents.ERROR, (error) => {
console.log("Error", error);
});
connection.on(RealtimeEvents.CLOSE, () => {
console.log("Connection closed");
});Manual Audio Chunking
For local files or custom audio streams, convert to PCM format and send in chunks.
Python
import asyncio
import base64
import os
from dotenv import load_dotenv
from pathlib import Path
from elevenlabs import ElevenLabs
from elevenlabs import AudioFormat, CommitStrategy, RealtimeEvents, RealtimeAudioOptions
from pydub import AudioSegment
load_dotenv()
def load_and_convert_audio(audio_path: str | Path, target_sample_rate: int = 16000) -> bytes:
if str(audio_path).lower().endswith('.pcm'):
with open(audio_path, 'rb') as f:
return f.read()
audio = AudioSegment.from_file(audio_path)
if audio.channels > 1:
audio = audio.set_channels(1)
if audio.frame_rate != target_sample_rate:
audio = audio.set_frame_rate(target_sample_rate)
audio = audio.set_sample_width(2)
return audio.raw_data
async def main():
elevenlabs = ElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))
transcription_complete = asyncio.Event()
connection = await elevenlabs.speech_to_text.realtime.connect(RealtimeAudioOptions(
model_id="scribe_v2_realtime",
audio_format=AudioFormat.PCM_16000,
sample_rate=16000,
commit_strategy=CommitStrategy.MANUAL,
include_timestamps=True,
))
def on_session_started(data):
print(f"Session started: {data}")
asyncio.create_task(send_audio())
def on_partial_transcript(data):
transcript = data.get('text', '')
if transcript:
print(f"Partial: {transcript}")
def on_committed_transcript(data):
transcript = data.get('text', '')
print(f"\nCommitted transcript: {transcript}")
def on_committed_transcript_with_timestamps(data):
print(f"Timestamps: {data.get('words', '')}")
transcription_complete.set()
def on_error(error):
print(f"Error: {error}")
transcription_complete.set()
def on_close():
print("Connection closed")
transcription_complete.set()
connection.on(RealtimeEvents.SESSION_STARTED, on_session_started)
connection.on(RealtimeEvents.PARTIAL_TRANSCRIPT, on_partial_transcript)
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, on_committed_transcript)
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT_WITH_TIMESTAMPS, on_committed_transcript_with_timestamps)
connection.on(RealtimeEvents.ERROR, on_error)
connection.on(RealtimeEvents.CLOSE, on_close)
async def send_audio():
audio_file_path = Path("audio.mp3")
audio_data = load_and_convert_audio(audio_file_path)
chunk_size = 32000 # 1 second of audio at 16kHz
chunks = [audio_data[i:i + chunk_size] for i in range(0, len(audio_data), chunk_size)]
for i, chunk in enumerate(chunks):
chunk_base64 = base64.b64encode(chunk).decode('utf-8')
await connection.send({"audio_base_64": chunk_base64, "sample_rate": 16000})
if i < len(chunks) - 1:
await asyncio.sleep(1)
await asyncio.sleep(0.5)
await connection.commit()
try:
await transcription_complete.wait()
except KeyboardInterrupt:
print("\nStopping...")
finally:
await connection.close()
if __name__ == "__main__":
asyncio.run(main())JavaScript
import "dotenv/config";
import * as fs from "node:fs";
import { ElevenLabsClient, RealtimeEvents, AudioFormat } from "@elevenlabs/elevenlabs-js";
const elevenlabs = new ElevenLabsClient();
const connection = await elevenlabs.speechToText.realtime.connect({
modelId: "scribe_v2_realtime",
audioFormat: AudioFormat.PCM_16000,
sampleRate: 16000,
includeTimestamps: true,
});
connection.on(RealtimeEvents.SESSION_STARTED, (data) => {
console.log("Session started", data);
sendAudio();
});
connection.on(RealtimeEvents.PARTIAL_TRANSCRIPT, (transcript) => {
console.log("Partial transcript", transcript);
});
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, (transcript) => {
console.log("Committed transcript", transcript);
});
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT_WITH_TIMESTAMPS, (transcript) => {
console.log("Committed with timestamps", transcript);
});
connection.on(RealtimeEvents.ERROR, (error) => {
console.log("Error", error);
});
connection.on(RealtimeEvents.CLOSE, () => {
console.log("Connection closed");
});
async function sendAudio() {
const pcmFilePath = "audio.pcm";
const chunkSize = 32000;
const audioBuffer = fs.readFileSync(pcmFilePath);
const chunks: Buffer[] = [];
for (let i = 0; i < audioBuffer.length; i += chunkSize) {
const chunk = audioBuffer.subarray(i, i + chunkSize);
chunks.push(chunk);
}
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const chunkBase64 = chunk.toString("base64");
connection.send({
audioBase64: chunkBase64,
sampleRate: 16000,
});
if (i < chunks.length - 1) {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
await new Promise((resolve) => setTimeout(resolve, 500));
connection.commit();
}Direct WebSocket Connection
For cases where the SDK cannot be used:
wss://api.elevenlabs.io/v1/speech-to-text/realtime?model_id=scribe_v2_realtimeAdd repeated keyterms query parameters to bias recognition toward specific terms, and set
no_verbatim=true to remove filler words, false starts, and disfluencies. Add repeated
secondary_languages parameters with ISO-639-1 or ISO-639-3 codes when the audio may contain
additional languages:
wss://api.elevenlabs.io/v1/speech-to-text/realtime?model_id=scribe_v2_realtime&keyterms=ElevenLabs&keyterms=Scribe&secondary_languages=es&secondary_languages=fr&no_verbatim=trueEntity Detection
Add entity_detection to detect entities in committed segments. It accepts all, one entity type
or category, or a comma-separated list. Categories include pii, phi, pci, other, and
offensive_language:
wss://api.elevenlabs.io/v1/speech-to-text/realtime?model_id=scribe_v2_realtime&entity_detection=pii,pciEach committed segment is followed by a committed_transcript_entities event containing the
detected entity text, type, and character offsets. See the
entity detection guide
for specific entity types.
Entity Redaction
Add entity_redaction to redact entities from committed segments. It accepts the same values as
entity_detection. entity_redaction_mode sets the marker format: redacted ({REDACTED}),
entity_type ({ENTITY_TYPE}), or enumerated_entity_type ({ENTITY_TYPE_N}, the default):
wss://api.elevenlabs.io/v1/speech-to-text/realtime?model_id=scribe_v2_realtime&entity_redaction=pii&entity_redaction_mode=entity_typeEach committed segment is followed by a redacted_transcript event containing the original text
and the redacted_text. Other transcript events stay unredacted.
Message Format
{
"message_type": "input_audio_chunk",
"audio_base_64": "<base64-encoded-audio>",
"commit": false,
"sample_rate": 16000
}Commit Message
{
"message_type": "commit"
}Audio Requirements
| Parameter | Value |
|---|---|
| Format | PCM 16-bit |
| Sample Rate | 16000 Hz (recommended) |
| Channels | Mono |
| Chunk Size | 32,000 bytes = 1 second |
Supported sample rates: 8kHz to 48kHz
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an API key, Needs Python, Needs Node.js
The author says: Requires internet access and an ElevenLabs API key (ELEVENLABS_API_KEY).
Security
What our scanner noticed· Medium risk
- Instructions that use the network
- Downloads piped into a shell
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- ElevenLabs
- Publisher
- ElevenLabs (Official)
- Source
- elevenlabs/skills · speech-to-text · commit 9763c39
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by ElevenLabs or ElevenLabs.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download speech-to-text.zip