Last updated 2026-05-17
ElevenLabs Provider
The elevenlabs provider implements both
Speaker
(text-to-speech) and
Transcriber
(speech-to-text via the Scribe family). It does not implement
Provider — ElevenLabs has no chat endpoint. Pair it
with one of the chat providers
(OpenAI,
Anthropic, or any
other) for end-to-end voice pipelines.
Import path
import ( "github.com/elloloop/llmrouter" "github.com/elloloop/llmrouter/providers/elevenlabs")Default endpoint
With no WithBaseURL override, the provider targets
https://api.elevenlabs.io/v1. TTS requests go to
{BaseURL}/text-to-speech/{voice_id} (or
/text-to-speech/{voice_id}/stream when
SpeechRequest.Stream is true). STT requests go to
{BaseURL}/speech-to-text.
Construction
p, err := elevenlabs.New( llmrouter.WithAPIKey(os.Getenv("ELEVENLABS_API_KEY")),)if err != nil { log.Fatal(err)}
Authentication is via the xi-api-key header — the
provider sets it automatically from the API key. There is no Bearer
token form for ElevenLabs.
Text-to-speech
Models
-
eleven_turbo_v2_5— lowest latency, recommended for conversational and real-time use cases. Multilingual (32 languages). -
eleven_multilingual_v2— highest quality, 29 languages. Higher latency thanturbo. -
eleven_flash_v2_5— ultra-low latency (~75 ms), English-only. -
eleven_monolingual_v1— legacy, English-only.
Voice IDs
ElevenLabs voices are referenced by opaque IDs (often 20-character
alphanumeric strings) — set SpeechRequest.Voice to the
ID, not the display name. Find IDs in your account at
elevenlabs.io/app/voice-library
or via the ElevenLabs /v1/voices endpoint. A handful of
public voice IDs (subject to change):
| Voice | ID | Notes |
|---|---|---|
| Rachel | 21m00Tcm4TlvDq8ikWAM | default female |
| Adam | pNInz6obpgDQGcFmaJgB | default male |
| Bella | EXAVITQu4vr4xnSDxMaL | soft female |
Antoni | ErXwobaYiN019PkySvjV | narrator male |
Output format mapping
ElevenLabs uses compound format codes
({codec}_{sample_rate}_{bitrate}). The library
maps the canonical Format field as follows:
Library Format | ElevenLabs output_format |
|---|---|
mp3 (default) | mp3_44100_128 |
pcm | pcm_22050 |
ulaw | ulaw_8000 |
For non-default sample rates or bitrates, set
Format="" and pass output_format on
Raw.
Streaming TTS example
package main
import ( "context" "log" "os"
"github.com/elloloop/llmrouter" "github.com/elloloop/llmrouter/providers/elevenlabs")
func main() { p, err := elevenlabs.New(llmrouter.WithAPIKey(os.Getenv("ELEVENLABS_API_KEY"))) if err != nil { log.Fatal(err) }
stream, err := p.Speak(context.Background(), llmrouter.SpeechRequest{ Model: "eleven_turbo_v2_5", Input: "Welcome to the future of voice synthesis.", Voice: "21m00Tcm4TlvDq8ikWAM", // Rachel Format: "mp3", Stream: true, }) if err != nil { log.Fatal(err) }
out, _ := os.Create("welcome.mp3") defer out.Close() for chunk := range stream.Chunks() { out.Write(chunk.Data) } if err := stream.Err(); err != nil { log.Fatal(err) }}Voice stability and style
ElevenLabs' voice_settings object (stability,
similarity_boost, style,
use_speaker_boost) is not on the typed surface. Pass
it via Raw:
raw := []byte(`{ "voice_settings": { "stability": 0.65, "similarity_boost": 0.85, "style": 0.2, "use_speaker_boost": true }}`)
stream, err := p.Speak(ctx, llmrouter.SpeechRequest{ Model: "eleven_multilingual_v2", Input: "Hello world.", Voice: "21m00Tcm4TlvDq8ikWAM", Raw: raw,})Speech-to-text (Scribe)
Models
-
scribe_v1— current production model. 99 languages, word-level timing, speaker diarization, and (optionally) profanity filtering.
Language detection
Scribe auto-detects language by default. Set
TranscribeRequest.Language to an ISO-639-1 code to
skip detection and improve accuracy on short clips.
STT example
f, err := os.Open("interview.m4a")if err != nil { log.Fatal(err)}defer f.Close()
stream, err := p.Transcribe(ctx, llmrouter.TranscribeRequest{ Model: "scribe_v1", Audio: f, AudioFormat: "audio/m4a", Filename: "interview.m4a", Language: "en", ResponseFormat: "verbose_json",})if err != nil { log.Fatal(err)}
for seg := range stream.Segments() { fmt.Printf("[%s] %s\n", seg.Start, seg.Text) for _, w := range seg.Words { fmt.Printf(" %s @ %s\n", w.Word, w.Start) }}if err := stream.Err(); err != nil { log.Fatal(err)}Speaker diarization
Diarization is enabled via Scribe's
diarize field, which is not on the typed surface. Pass
it via Raw:
raw := []byte(`{"diarize": true, "num_speakers": 2}`)
stream, err := p.Transcribe(ctx, llmrouter.TranscribeRequest{ Model: "scribe_v1", Audio: f, AudioFormat: "audio/m4a", Raw: raw,})
// Each segment's Raw json contains "speaker_id" when diarize is on.for seg := range stream.Segments() { fmt.Printf("%s\n", string(seg.Raw))}Error handling
Non-2xx responses surface as
*llmrouter.ErrUpstream
with Provider == "elevenlabs". Common cases:
401— bad or missing API key.402— quota exhausted (insufficient characters).422— invalid voice ID or model id.429— concurrent-request limit hit.
Caveats
- No chat. ElevenLabs does not offer text
generation. Calling
CompletionStreamon this provider is a compile error — the type doesn't have the method. - Streaming TTS uses HTTP chunked transfer, not SSE. The library wraps the stream identically to other providers, so consumer code is unchanged.
- STT uses multipart upload. The
Audio io.Readeris consumed once; for retries, the caller must rewind.
See also
- Audio concept — Speaker/Transcriber semantics.
- Cartesia provider — TTS-only, optimised for real-time.
- Deepgram provider — STT-only, Nova-3.
- OpenAI provider — TTS + Whisper STT.