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://mp-staging.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-events.md
Real-Time Event Reference
Complete reference for events in real-time speech-to-text streaming.
Sent Events (Client → Server)
input_audio_chunk
Send audio data for transcription.
{
"message_type": "input_audio_chunk",
"audio_base_64": "<base64-encoded-pcm-audio>",
"commit": false,
"sample_rate": 16000
}| Field | Type | Required | Description |
|---|---|---|---|
message_type | string | Yes | Always "input_audio_chunk" |
audio_base_64 | string | Yes | Base64-encoded PCM audio data |
commit | boolean | Yes | Whether to commit after this chunk |
sample_rate | number | No | Sample rate in Hz (8000-48000) |
previous_text | string | No | Context from prior transcript (first chunk only, max 50 chars) |
commit
Finalize the current transcript segment.
{
"message_type": "commit"
}Received Events (Server → Client)
All received events use message_type as the discriminator field.
session_started
Connection established successfully.
{
"message_type": "session_started",
"session_id": "0b0a72b57fd743ebbed6555d44836cf2",
"config": {
"sample_rate": 16000,
"audio_format": "pcm_16000",
"language_code": "en",
"model_id": "scribe_v2_realtime",
"commit_strategy": "manual",
"enable_logging": true,
"include_timestamps": true
}
}partial_transcript
Interim transcription results, updates frequently as audio is processed.
{
"message_type": "partial_transcript",
"text": "Hello, how are"
}| Field | Type | Description |
|---|---|---|
message_type | string | "partial_transcript" |
text | string | Current partial transcription |
final_transcript
A stable result for a segment after speech settles but before the segment is committed.
{
"message_type": "final_transcript",
"text": "Hello, how are you today?"
}final_transcript_with_timestamps
A delayed final result sent when timestamps or language detection are enabled.
{
"message_type": "final_transcript_with_timestamps",
"text": "Hello, how are you today?",
"language_code": "en",
"words": [
{"text": "Hello", "start": 0.0, "end": 0.32, "type": "word", "logprob": -0.03}
]
}language_code and words are optional and depend on the enabled session options.
committed_transcript
Final transcription after commit.
{
"message_type": "committed_transcript",
"text": "Hello, how are you today?"
}| Field | Type | Description |
|---|---|---|
message_type | string | "committed_transcript" |
text | string | Finalized transcription |
committed_transcript_with_timestamps
Final transcription with word-level timing. Sent after committed_transcript when include_timestamps=true.
{
"message_type": "committed_transcript_with_timestamps",
"text": "Hello, how are you today?",
"language_code": "en",
"words": [
{"text": "Hello", "start": 0.0, "end": 0.32, "type": "word"},
{"text": " ", "start": 0.32, "end": 0.35, "type": "spacing"},
{"text": "how", "start": 0.40, "end": 0.55, "type": "word"}
]
}| Field | Type | Description |
|---|---|---|
message_type | string | "committed_transcript_with_timestamps" |
text | string | Full transcription text |
language_code | string | Detected language code |
words | array | Word-level timing data |
words[].text | string | The word or token |
words[].start | number | Start time in seconds |
words[].end | number | End time in seconds |
words[].type | string | "word", "spacing", or "audio_event" |
words[].speaker_id | string | Speaker identifier (if diarization enabled) |
edited_transcript
Edited version of a committed segment. Sent when the connection includes transcript_edit.
{
"message_type": "edited_transcript",
"text": "our next meeting is on the twelfth of July twenty twenty-six",
"edited_text": "our next meeting is on 2026-07-12"
}| Field | Type | Description |
|---|---|---|
message_type | string | "edited_transcript" |
text | string | Committed transcript text that the instruction was applied to |
edited_text | string | Edited text; identical to text when no edits were made |
committed_transcript_entities
Entities detected in a committed segment when the connection includes entity_detection.
{
"message_type": "committed_transcript_entities",
"text": "My name is Alice.",
"entities": [
{
"text": "Alice",
"entity_type": "name_given",
"start_char": 11,
"end_char": 16
}
]
}| Field | Type | Description |
|---|---|---|
message_type | string | "committed_transcript_entities" |
text | string | Committed transcript segment scanned for entities |
entities | array | Detected entities; empty when none are found |
entities[].text | string | Text identified as an entity |
entities[].entity_type | string | Detected entity type |
entities[].start_char | integer | Start character offset in text |
entities[].end_char | integer | End character offset in text |
redacted_transcript
Redacted version of a committed segment. Sent when the connection includes entity_redaction.
{
"message_type": "redacted_transcript",
"text": "My name is Alice.",
"redacted_text": "My name is {NAME_0}."
}| Field | Type | Description |
|---|---|---|
message_type | string | "redacted_transcript" |
text | string | Committed transcript text that entities were redacted from |
redacted_text | string | Committed text with each redacted entity replaced by a marker |
Error Events
error
Sent when an error occurs.
{
"message_type": "error",
"error": "input_error"
}Error Codes
| Code | Description |
|---|---|
auth_error | Invalid API key or token |
quota_exceeded | Usage limit reached |
input_error | Unsupported audio format or invalid input |
rate_limited | Too many requests |
commit_throttled | Commits sent too frequently |
invalid_request | Connection parameters were rejected; the session closes |
session_time_limit_exceeded | Session exceeded max duration |
unaccepted_terms | Terms not accepted in dashboard |
resource_exhausted | Server capacity reached |
queue_overflow | Server queue capacity reached |
chunk_size_exceeded | Audio chunk too large |
insufficient_audio_activity | Not enough speech detected |
transcriber_error | Internal processing error |
Connection Events
open
WebSocket connection established (standard WebSocket event, not a JSON message).
close
WebSocket connection closed (standard WebSocket close frame with code and reason).
Event Handling Examples
Python
The Python SDK uses RealtimeEvents members matching the message_type values. Each callback receives the event payload as a dict. ERROR also fires for every specific error event, so register it to catch all failures:
from elevenlabs import RealtimeEvents
def on_committed_with_timestamps(data):
for word in data.get("words", []):
print(f" {word['text']}: {word['start']}s - {word['end']}s")
connection.on(RealtimeEvents.SESSION_STARTED, lambda data: print(f"Session: {data.get('session_id')}"))
connection.on(RealtimeEvents.PARTIAL_TRANSCRIPT, lambda data: print(f"Partial: {data.get('text', '')}"))
connection.on(RealtimeEvents.FINAL_TRANSCRIPT, lambda data: print(f"Stable segment: {data.get('text', '')}"))
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT, lambda data: print(f"Final: {data.get('text', '')}"))
connection.on(RealtimeEvents.COMMITTED_TRANSCRIPT_WITH_TIMESTAMPS, on_committed_with_timestamps)
connection.on(RealtimeEvents.EDITED_TRANSCRIPT, lambda data: print(f"Edited: {data.get('edited_text', '')}"))
connection.on(RealtimeEvents.INVALID_REQUEST, lambda data: print(f"Connection rejected: {data.get('error')}"))
connection.on(RealtimeEvents.ERROR, lambda data: print(f"Error: {data.get('error')}"))
connection.on(RealtimeEvents.CLOSE, lambda: print("Connection closed"))JavaScript
The JavaScript SDK uses event names matching the message_type values:
connection.on("session_started", (data) => {
console.log("Session:", data.sessionId);
});
connection.on("partial_transcript", (data) => {
console.log("Partial:", data.text);
});
connection.on("final_transcript", (data) => {
console.log("Stable segment:", data.text);
});
connection.on("committed_transcript", (data) => {
console.log("Final:", data.text);
});
connection.on("committed_transcript_with_timestamps", (data) => {
for (const word of data.words) {
console.log(` ${word.text}: ${word.start}s - ${word.end}s`);
}
});
connection.on("edited_transcript", (data) => {
console.log("Edited:", data.edited_text);
});
connection.on("error", (error) => {
console.error("Error:", error);
});
connection.on("invalid_request", (error) => {
console.error("Connection rejected:", error.error);
});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.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download speech-to-text.zip