| name | elv |
| description | Use the elv CLI for ElevenLabs audio and voice work from the command line: generate speech (TTS), transcribe audio (STT), create sound effects or music, clone, convert, or isolate voices, dub video/audio, and manage ElevenLabs voices, models, conversational agents, history, and usage. elv also invokes ANY of the ~320 ElevenLabs API operations by id, plus raw REST and WebSocket calls. It is agent-first: every command is non-interactive and prints exactly one JSON envelope to stdout, with binary and large output written to disk. Reach for elv whenever the user wants ElevenLabs text-to-speech, speech-to-text, voice cloning, sound effects, music, dubbing, or any ElevenLabs REST/WebSocket call. |
elv
elv is an agent-first CLI over the ElevenLabs OpenAPI spec. Binary is elv
(or node dist/cli.js). Requires Node >= 22.
The contract
Every command writes exactly one JSON object to stdout: a success envelope
({"v":1,"ok":true,...}) or an error envelope ({"v":1,"ok":false,...}). No
prose, no spinners, no multiple JSON lines, no interactive prompts. Binary
results and large JSON go to disk; their paths appear in files[].
Branch on the exit code first. Parse the envelope only when you need detail.
| Code | Meaning |
|---|
| 0 | Success |
| 2 | Input / validation (bad params, local pre-flight) |
| 3 | Auth / permission |
| 4 | Confirmation required: add --yes |
| 5 | Budget ceiling: raise --max-credits or lower the op cost |
| 6 | Out of credits at provider |
| 7 | Transient / retryable, retries exhausted |
| 8 | Provider error (other 4xx/5xx) |
| 9 | Not found (404, unknown operation_id) |
A success envelope carries operation_id, http, cost, and either data
inline or files[] plus a data_summary. An error envelope carries
error.type, error.code, error.message, retry, and often a hints[]
entry with a suggested next command (e.g. elv config doctor on an auth failure,
elv voices list when a voice id is not found).
Three ways to act
- Aliases for the common workflows. Twelve thin commands that build the request
and call the same core runner as
call: tts, stt, music, sfx,
voice-change, voice-isolate, dubbing, voices, models, agents,
history, usage. Use these first when one fits.
elv call <operation_id> --json '{...}' for full coverage of every operation
in the spec. Use this when no alias fits.
- Escape hatches when the registry is not enough:
elv http <METHOD> <path>
for raw REST, elv ws <catalog|url> for scripted WebSocket sessions (requires
a --send NDJSON script; run elv ws --list for the catalog), and elv wait
to poll an operation until a status field resolves.
All three share the same auth, envelope, retries, redaction, budget guard, and
file output.
Parent alias commands need a subcommand. elv voices alone exits 2 and lists
the valid subcommands; run elv voices list. (elv ops, elv config, and
elv spec print their subcommands and exit 0.)
Discovery
When you do not know the operation id or its shape:
elv ops search "text to speech"
elv ops get text_to_speech_full
elv ops schema text_to_speech_full --example
--example prints a ready-to-run call with the input buckets filled in. Add
--raw to ops schema for the raw JSON Schema. elv <command> --help prints
that command's own flags and arguments.
Safety gates
No interactive prompts ever. Destructive operations (DELETE), outbound
calls/messages, API-key mutation, and member changes require --yes. GET reads
are never gated.
Without --yes, a gated op exits 4:
elv call delete_voice --path voice_id=VOICE_ID
elv call delete_voice --path voice_id=VOICE_ID --yes
--max-credits N (or ELV_MAX_CREDITS) blocks a credit-consuming op pre-flight,
before any network call, when the estimate exceeds the ceiling:
elv tts --voice-id VOICE --text "Long script..." --max-credits 5
Estimates are calibrated: TTS Flash/Turbo about 0.5 credits/char, standard TTS
about 1.0/char, STT about 27 credits/min.
--dry-run validates and returns a redacted request preview without touching the
network. It runs before the --yes and budget gates, so the preview tells you
in advance what would happen:
elv tts --voice-id VOICE --text "Hello" --dry-run
Do not --dry-run secret-create ops with real secret values; redaction keys on
field names and may echo a secret passed as a plain value.
Output handling
Binary output goes to a file. Pass --out <file-or-dir>; otherwise it lands in
the output directory (default ~/.cache/elv/out, override with ELV_OUTPUT_DIR
or a profile output_dir). An extensionless --out is treated as a directory.
The envelope's files[] gives each path, mime, byte size, and sha256.
Large JSON (long lists, big responses) also spills to disk: the envelope returns
files[] plus a data_summary (type, count, a short preview) and a hints[]
entry instead of flooding stdout. Inspect a spilled file WITHOUT loading it into
context with elv view:
elv view <path>
elv view <path> --path data.voices.0.name
elv view <path> --path voices --limit 5
elv view <path> --path 'voices[].name'
elv view reads the spilled JSON (or NDJSON), applies an optional --path
(dotted, with numeric array indices and a [] array wildcard) and --limit, and
returns the slice inline when small or a data_summary plus a narrow-further hint
when still large. voices[].name flattens to ["Bella ...", "Bill ...", ...];
voices[] returns the array itself.
Pagination works on the list aliases (voices list, history list,
agents list, dubbing list) and on call/http: --limit N sets the page
size and caps inlined items, --all walks every page and writes the full set to
the --save-json/--out target (one of which is required with --all),
--save-json <path> chooses the output path. A large single page spills to disk
but still returns the next page command inline so you can keep paging.
When you only need a couple of fields per row, skip the spill: the list aliases
take --fields <csv> and return the whole list projected and inline.
elv voices list --fields voice_id,name is a sub-KB envelope instead of a ~100 KB
spill — the fastest way to get an id/name table to pick from.
Auth
Set ELEVENLABS_API_KEY in the environment; elv sends it as the xi-api-key
header. Never pass the key as a CLI argument. It never appears in output, logs,
dry-run previews, or files. Check setup with elv config get and
elv config doctor.
Recipes
| Task | Command |
|---|
| Check balance / tier / usage | elv usage |
| Character usage over a range | elv usage --from 2026-06-01 --to 2026-06-25 |
| List models | elv models list |
| List voices | elv voices list |
| List voices (compact id/name) | elv voices list --fields voice_id,name |
| Search voices (name / labels) | elv voices list --search "narration" |
| Find a voice by name | elv voices find "Rachel" (matches exact name, else unique substring) |
| Get one voice | elv voices get VOICE_ID (or --voice-id VOICE_ID) |
| Text to speech | elv tts --voice-id VOICE_ID --text "Hello" --model eleven_flash_v2_5 --out out.mp3 |
| TTS by voice name | elv tts --voice "Rachel" --text "Hello" --out out.mp3 |
| TTS with timestamps | elv tts --voice-id VOICE_ID --text "Hello" --timestamps --out out.mp3 (writes out.mp3 plus a sidecar out.timestamps.json with the alignment data) |
| Speech to text | elv stt --file note.m4a --model scribe_v1 |
| Sound effect | elv sfx --prompt "thunderclap" --duration 5 --out sfx.mp3 |
| Music | elv music --prompt "lofi beat" --length-ms 30000 --out track.mp3 |
| Convert voice (speech to speech) | elv voice-change --voice-id VOICE_ID --file in.mp3 --out out.mp3 |
| Isolate voice from noise | elv voice-isolate --file in.mp3 --out clean.mp3 |
| Clone a voice (instant) | elv voices clone-instant --name "My Voice" --file sample.mp3 |
| Create a dub, wait for it | elv dubbing create --file in.mp4 --source en --target es --wait |
| Get dubbed audio | elv dubbing audio --id DUB_ID --language es --out dubbed.mp3 |
| List agents | elv agents list |
| Speech history | elv history list --limit 20 |
| Any operation by id | elv call <operation_id> --json '{"path":{...},"query":{...},"body":{...}}' |
| Inspect a spilled JSON result | elv view <path> --path data.voices.0 |
| Raw REST call | elv http GET /v1/user |
| Scripted WebSocket session | elv ws tts-realtime --query voice_id=VOICE --send script.ndjson --out ./session |
| Poll a long job | elv wait --operation get_dubbed_metadata --json '{"path":{"dubbing_id":"abc"}}' --status-path '$.data.status' --success 'dubbed' --failure 'failed' --interval-ms 2000 --timeout-ms 600000 (--failure is optional; success-only polling works) |
call input shape
call takes one --json object with path, query, body, and files
buckets. You can also build it from flags: --path key=value, --query key=value,
--file field=path, repeated as needed. --json-file <path> and --stdin-json
read the JSON from a file or stdin. --allow-unknown routes flat top-level keys
into the body.
elv call text_to_speech_full \
--json '{"path":{"voice_id":"21m00Tcm4TlvDq8ikWAM"},"body":{"text":"Hello","model_id":"eleven_v3"}}' \
--out ./out
Common flags
Every command accepts --dry-run, --yes, --max-credits <n>, --out <path>,
--base-url <url>, --profile <name>, --debug, and --retry-post. The list
aliases (voices list, history list, agents list, dubbing list) and
call/http take --limit <n> (page size + inline cap), --all (walk every
page to a file; requires --save-json/--out), and --save-json <path>; the
list aliases also take --fields <csv> to project rows to a chosen set of fields
inline. Every command's own flags are documented in elv <command> --help.