ElevenLabs Client
Experimental
The ElevenLabs client is experimental: its interface and typed payloads may change between minor releases without a deprecation cycle.
ElevenLabs is an audio provider: text-to-speech, speech-to-text, speech-to-speech, sound effects and music, no chat
models. ElevenLabsClient is standalone, like the TypeSafe client: it is not an LLMClientBase
subclass, LLMClient does not route to it, and the OpenTelemetry instrumentation does not cover it.
Configuration
Set ELEVENLABS_API_KEY in your environment or pass api_key= explicitly. base_url defaults to
https://api.elevenlabs.io/v1/; point it at a data-residency host (e.g. https://api.eu.residency.elevenlabs.io/v1/) if
your account needs one.
Requests retry up to three times on connection errors and 5xx, respecting Retry-After, as the chat clients do. A
rate limit (429, or a realtime rate_limited/commit_throttled/queue_overflow frame) raises TooManyRequestsError,
an exhausted credit quota raises QuotaExceededError (not an LLMError), and other API errors raise LLMError with
provider="elevenlabs". Bad arguments (no voice, no or two audio sources) raise ValueError.
The realtime clients need the realtime extra (niquests WebSocket support):
Text-to-speech
from pathlib import Path
from padwan_ai import ElevenLabsClient
async with ElevenLabsClient(voice_id="JBFqnCBsd6RMkjVDRZzb") as client:
speech = await client.generate_speech(
"Hello from Padwan.",
model="eleven_v3", # default: eleven_multilingual_v2
output_format="mp3_44100_128",
voice_settings={"stability": 0.4, "speed": 1.1},
)
Path("hello.mp3").write_bytes(speech.audio)
generate_speech returns an ElevenLabsAudio with the raw audio bytes, its mime_type and the ElevenLabs
request_id. To keep prosody consistent across chunks of a long text, pass earlier IDs as previous_request_ids (or
the surrounding text as previous_text / next_text). Voice IDs come from your ElevenLabs voice library; pass one
per call or set a client default with voice_id=.
Streaming
stream_speech takes the same arguments and yields audio chunks as they are generated, so playback can start before
the whole text is synthesized:
async with ElevenLabsClient(voice_id="JBFqnCBsd6RMkjVDRZzb") as client:
with Path("hello.mp3").open("wb") as f:
async for chunk in client.stream_speech("Hello from Padwan."):
f.write(chunk)
Realtime input (WebSocket)
When the text itself arrives in pieces (e.g. from an LLM stream), ElevenLabsRealtimeTTSClient sends it over a
WebSocket as it comes and yields audio back. stream() accepts a sync or async iterable of text:
from padwan_ai import ElevenLabsRealtimeTTSClient
async def llm_tokens():
yield "Hello there, "
yield "this is realtime speech."
async with ElevenLabsRealtimeTTSClient(
voice_id="JBFqnCBsd6RMkjVDRZzb",
model="eleven_flash_v2_5", # default
output_format="pcm_16000",
) as tts:
async for audio in tts.stream(llm_tokens()):
... # play or buffer the PCM chunk
For finer control, call send_text(text, flush=False) and end_input() yourself and iterate the connection: each
message's audio_bytes() holds audio and is_final() marks the end. chunk_length_schedule and auto_mode tune the
latency/quality trade-off.
Speech-to-text
async with ElevenLabsClient() as client:
result = await client.transcribe(
file="meeting.wav", # path or raw bytes (then pass filename=); or source_url="https://..."
language="en", # optional ISO code, as on the OpenAI and Mistral clients
diarize=True,
keyterms=["Padwan", "Polarsen"],
)
print(result["text"])
for word in result["words"]:
print(word["start"], word.get("speaker_id"), word["text"])
transcribe uses scribe_v2 by default and returns the API's ElevenLabsTranscription (text, language_code,
language_probability, and words with timestamps, types and speaker IDs). See the
speech-to-text reference for each option.
Realtime transcription (WebSocket)
ElevenLabsRealtimeSTTClient streams microphone audio to Scribe (scribe_v2_realtime) and returns partial and
committed transcripts. Send audio in the client's audio_format (default mono PCM16 at 16 kHz). With the default
manual strategy you commit() each segment; commit_strategy="vad" lets the server commit on silence.
from padwan_ai import ElevenLabsRealtimeSTTClient
async with ElevenLabsRealtimeSTTClient(language_code="en") as stt:
for chunk in pcm16_chunks:
await stt.append_audio(chunk)
await stt.commit()
async for message in stt:
print(message["message_type"], stt.transcript(message))
if stt.is_committed(message):
break
Error frames (auth, quota, rate limits, bad input) raise LLMError (or QuotaExceededError) while iterating.
Speech-to-speech
speech_to_speech re-voices a recording with another voice, keeping its delivery, emotion and timing (the ElevenLabs
voice changer). It takes a path or raw bytes and returns an ElevenLabsAudio; stream_speech_to_speech yields the
audio in chunks instead:
async with ElevenLabsClient(voice_id="JBFqnCBsd6RMkjVDRZzb") as client:
speech = await client.speech_to_speech(
"my_recording.mp3",
model="eleven_multilingual_sts_v2", # default; or eleven_english_sts_v2
remove_background_noise=True,
)
Pass file_format="pcm_s16le_16" for raw 16 kHz mono PCM input to skip decoding and lower latency.
Sound effects and music
Both return an ElevenLabsAudio (MP3 unless output_format says otherwise):
async with ElevenLabsClient() as client:
knock = await client.generate_sound_effect("a door knock", duration_seconds=1.5)
jingle = await client.compose_music(
"calm piano jingle", music_length_ms=10_000, force_instrumental=True
)
compose_music takes either a text prompt or a structured composition_plan (see the
music reference).
Voices
async with ElevenLabsClient() as client:
voices = await client.list_voices()
voice = await client.get_voice("JBFqnCBsd6RMkjVDRZzb")
voice_id = await client.clone_voice("Narrator", ["sample1.mp3", "sample2.mp3"])
await client.delete_voice(voice_id)
clone_voice creates an instant voice clone (paid plans only) and returns its ID.
Not covered yet
Dubbing, voice design, professional voice cloning and the Agents platform (conversational speech-to-speech) are not implemented.