ElevenLabs audio API in Nebula — text-to-speech, sound-effects-from-text, speech-to-speech voice changing, audio isolation (denoise), translated dubbing, and speech-to-text transcription, all as audio-gen nodes. Activate when the user configures any elevenlabs-tts, elevenlabs-sfx, elevenlabs-sts, elevenlabs-isolation, elevenlabs-dubbing, or elevenlabs-stt node, or asks about ElevenLabs in Nebula. Sourced from the official ElevenLabs API reference (elevenlabs.io/docs/api-reference) cross-checked against backend/handlers/elevenlabs.py + backend/data/node_definitions.json, and the Nebula audit guide docs/api-guides/elevenlabs.md, on 2026-06-05.
Installation
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
ElevenLabs audio API in Nebula — text-to-speech, sound-effects-from-text, speech-to-speech voice changing, audio isolation (denoise), translated dubbing, and speech-to-text transcription, all as audio-gen nodes. Activate when the user configures any elevenlabs-tts, elevenlabs-sfx, elevenlabs-sts, elevenlabs-isolation, elevenlabs-dubbing, or elevenlabs-stt node, or asks about ElevenLabs in Nebula. Sourced from the official ElevenLabs API reference (elevenlabs.io/docs/api-reference) cross-checked against backend/handlers/elevenlabs.py + backend/data/node_definitions.json, and the Nebula audit guide docs/api-guides/elevenlabs.md, on 2026-06-05.
ElevenLabs Skill
ElevenLabs is the audio studio inside Nebula: turn text into lifelike speech, generate sound effects from a description, swap one voice for another, strip background noise out of a recording, dub a clip into another language, and transcribe speech to text — six drag-and-drop nodes, all in the audio-gen category.
When to use
User configures an elevenlabs-tts, elevenlabs-sfx, elevenlabs-sts, elevenlabs-isolation, elevenlabs-dubbing, or elevenlabs-stt node
User wants text-to-speech / narration / voiceover, sound effects from a prompt, re-voicing a recording, denoising/isolating speech, translated dubbing, or transcription (speech → text or subtitles)
User asks which ElevenLabs node to use, what params are valid, how to set a voice, or how to chain audio nodes
User asks for music, multi-speaker dialogue, voice design, or voice cloning — so you can tell them those are not Nebula nodes (see Capability boundaries)
Universal rules
Auth header + env var. Every node authenticates with the xi-api-key: <key> HTTP header (note: xi-api-key, notAuthorization: Bearer). The backend reads the key from the ELEVENLABS_API_KEY environment variable. If it is missing, the node fails with ELEVENLABS_API_KEY is required. Get the key from the ElevenLabs dashboard (Profile → API Keys).
Execution pattern = sync (request/response) for all six nodes. From the graph's perspective every node is executionPattern: "sync" — you wire it, run it, and it returns one output (an audio file, or text for STT). There is no streaming or WebSocket node. Caveat: elevenlabs-dubbing is sync to the graph but the handler internally does submit → poll the job every 5s (up to ~10 min) → download the result, so a dubbing node can legitimately take minutes before its audio output appears. The other five return in seconds.
Request encoding differs by node. TTS and SFX send a JSON body (Content-Type: application/json). STS, isolation, dubbing, and STT send multipart/form-data with the source audio file attached (STS/isolation under form field audio; dubbing and STT under form field file). You don't set this in Nebula — the handler picks the right encoding — but it explains why audio-input nodes need a real file on disk, not a URL string.
Status / error codes. A non-200 from ElevenLabs surfaces as ElevenLabs API error <status>: <body> (or the per-node variant, e.g. ElevenLabs SFX error …, ElevenLabs Dubbing submit error …). Common cases: 401 bad/missing key, 422 invalid params (bad voice_id, out-of-range value, unsupported output_format), 429 rate limit / quota, 400 malformed request. Dubbing-specific terminal states: the job can return status: "failed" (→ ElevenLabs Dubbing failed: <error>) or never finish (→ ElevenLabs Dubbing timed out after the poll cap).
Input-URI / input-file rules. TTS and SFX take Text input (a string — wire a Text node or any Text output into the text port). STS, isolation, dubbing, and STT take Audio input: the upstream audio value must be a local file path that exists on disk (the handler does Path(value).read_bytes() and raises Audio file not found: <path> otherwise). Feed them the audio output of another audio node (TTS, SFX, isolation) or an audio upload node — not a bare remote URL. STT is the one node that goes the other way: Audio in → Text out (its text output is a string you can wire into text-driven nodes), whereas the rest output Audio.
Key gotchas.
PCM is saved as .pcm. When you pick a pcm_*output_format (TTS/SFX/STS), ElevenLabs returns raw PCM samples with no WAV header, and the handler saves them with a .pcm extension to keep the extension honest. Many players/preview surfaces can't open a headerless .pcm. Prefer an mp3_* format unless the user specifically needs raw PCM for their own pipeline.
Isolation and dubbing always output MP3, regardless of any format setting — isolation has no params at all, and dubbing's download is hard-coded to MP3.
voice_id is free text, not a dropdown. Nebula cannot list or search voices. Default is Rachel (21m00Tcm4TlvDq8ikWAM). To use another voice, paste its ElevenLabs voice ID (from the Voice Library / your Voices page) into the field. An invalid ID fails at request time with a 422/404.
style is only sent when > 0 (TTS and STS). Leaving it at the default 0 omits it from voice_settings.
seed is only sent when set (TTS and STS). Blank = random each run; set an integer for reproducibility.
SFX model_id is fixed. The node exposes no model selector; the handler hard-codes the sound-effects model (eleven_text_to_sound_v2). Don't tell the user they can choose an SFX model.
Pick the right node
One row per real Nebula node. IDs, ports, endpoints, and params all match node_definitions.json + backend/handlers/elevenlabs.py.
Nebula node (display name)
Node ID
Endpoint / model
In → Out ports
Key params
ElevenLabs TTS
elevenlabs-tts
POST /v1/text-to-speech/{voice_id} · model from model_id enum
Types, ranges, enums, and defaults below are exactly what the node exposes. "(handler)" notes describe behavior in backend/handlers/elevenlabs.py the user can't see in the panel.
elevenlabs-tts (Text → Audio)
voice_id — string. Default 21m00Tcm4TlvDq8ikWAM (Rachel). Free-text ElevenLabs voice ID. Goes into the URL path.
(handler) style and use_speaker_boost may be forwarded inside voice_settings if present on the node, but the STS node panel does not expose style/use_speaker_boost controls — treat them as TTS-only knobs.
elevenlabs-isolation (Audio → Isolated Audio)
No params. Submit audio, get back denoised MP3. Output port labeled "Isolated Audio".
elevenlabs-dubbing (Audio → Dubbed Audio)
target_lang — enum, required, default es. Options: enesfrdeitptjakozharruhinltrplsv. The download endpoint uses this code, so it must be one the dubbing job produced.
source_lang — enum, default auto (Auto-detect). Options: autoenesfrdejazh. (handler) only sent when not auto.
num_speakers — integer 0–10, optional (placeholder "Auto-detect"). (handler) only sent when > 0; 0/blank = auto.
drop_background_audio — boolean, default false. (handler) only sent when true. Drops music/ambience, keeps speech.
disable_voice_cloning — boolean, default false. Default (off) preserves the original speaker's voice in the dub; turn on to use a generic voice instead. (handler) only sent when true.
elevenlabs-stt (Audio → Text)
model_id — enum. Default scribe_v1. Options: scribe_v1 (Scribe v1) · scribe_v2 (Scribe v2). Both verified working against the live API.
language_code — string, optional. Blank = auto-detect the spoken language. Set an ISO language code to force a specific language.
diarize — boolean, default false. Labels which speaker said what.
num_speakers — integer 1–32, optional. Hint for how many distinct speakers to expect; leave blank to let the model decide.
transcript_format — enum. Default text. Options: text (plain transcript) · srt · vtt (subtitle files). (handler) plain text returns the transcript string in the text output port; srt/vtt return the subtitle content instead.
(handler gotcha) requesting srt or vttforces diarize=true + timestamps_granularity=word automatically, because the ElevenLabs API returns HTTP 400 ("Requesting additional formats must have diarization and timestamps enabled") otherwise. You don't set this — the handler does — but it means a subtitle export is always diarized + word-timestamped regardless of how you left diarize. (Caught via live smoke 2026-06-05.)
Recipes
1. Script → narrated voiceover (single node).
Add a Text node (or any Text output) with the script.
Wire it into elevenlabs-ttstext port.
Set voice_id; pick model_id (eleven_multilingual_v2 for quality, eleven_flash_v2_5 for speed); tune stability / style / speed.
Run → the audio output is the narration MP3.
2. Narration + matching ambience (branch into two nodes).
Branch a Text/prompt into:
elevenlabs-tts for the spoken line, and
elevenlabs-sfx with a prompt like "soft rainstorm with distant thunder", loop on, duration_seconds ~15 for a bed.
Take both audio outputs downstream into a mix/composite step to lay voice over ambience.
3. Clean up, then re-voice or translate a clip.
Start from an audio upload node or an elevenlabs-ttsaudio output.
If the source is noisy, feed it through elevenlabs-isolation first (audio → "Isolated Audio").
Then either:
same language, new voice: into elevenlabs-sts — set the target voice_id; optionally enable remove_background_noise (redundant if you already isolated).
new language, same speaker: into elevenlabs-dubbing — set target_lang (e.g. ja), leave source_lang on auto, keep disable_voice_cloningoff so the original voice is preserved.
Start from an audio upload node, or the audio output of any audio node (TTS, SFX, isolation, dubbing).
Wire it into elevenlabs-stt; leave transcript_format on text for a plain transcript.
Wire the node's text output into any text-driven node — e.g. a chat/LLM node (claude-chat, gpt-4o-chat) to summarize/translate, or Combine Text to assemble a script.
For subtitles instead: set transcript_format=srt (or vtt) — the text output then carries the subtitle content (the handler auto-enables diarization + word timestamps for these formats; see the param note).
In the nebula_nodes context
Node IDs:elevenlabs-tts, elevenlabs-sfx, elevenlabs-sts, elevenlabs-isolation, elevenlabs-dubbing, elevenlabs-stt. All category: "audio-gen", all apiProvider: "elevenlabs", all envKeyName: "ELEVENLABS_API_KEY".
Handler file:backend/handlers/elevenlabs.py — one handler per node (handle_elevenlabs_tts, _sfx, _sts, _isolation, _dubbing, handle_elevenlabs_stt). Shared _save_audio / _audio_extension helpers map output_format → file extension (mp3→.mp3, pcm→.pcm, wav→.wav, default .mp3) for the audio-output nodes; handle_elevenlabs_stt does not call _save_audio — it returns a text string, not a file.
Ports:
elevenlabs-tts: in text (Text, required) → out audio (Audio).
elevenlabs-sfx: in text (Text, required) → out audio (Audio).
elevenlabs-sts: in audio (Audio, required) → out audio (Audio).
elevenlabs-isolation: in audio (Audio, required) → out audio (Isolated Audio).
elevenlabs-dubbing: in audio (Audio, required) → out audio (Dubbed Audio).
elevenlabs-stt: in audio (Audio, required) → out text (Text).
Chaining rules: Text-input nodes (tts, sfx) accept any Text output. Audio-input nodes (sts, isolation, dubbing) accept any Audio output, but the upstream value must resolve to a local file path that exists — chain them off another ElevenLabs audio node or an audio upload, not a raw URL. Output of one audio node → input of the next is the normal way to build isolation → sts/dubbing and tts → sts chains.
How outputs render: each node writes its bytes to the run directory (get_run_dir()) as <12-hex>.<ext> and returns {"audio": {"type": "Audio", "value": "<path>"}}. The frontend serves/plays that file as an audio result. .mp3 plays everywhere; a .pcm (raw, headerless) generally won't — another reason to prefer MP3.
Dubbing internals (why it's slow): submit returns a dubbing_id; the handler polls GET /v1/dubbing/{id} every 5s up to 120 times (~10 min), tolerating up to 5 consecutive poll failures, then downloads GET /v1/dubbing/{id}/audio/{target_lang} and saves MP3. So a single dubbing node may run for minutes — that's expected, not a hang.
Capability boundaries — what ElevenLabs offers but Nebula does NOT expose
Never promise these via a node; there is no node for any of them (per the audit gap table in docs/api-guides/elevenlabs.md):
Music generation (/v1/music/compose, stems, video-to-music) — prompt-to-music and stem separation. No node.
Text-to-Dialogue (/v1/text-to-dialogue) — multi-speaker scenes in one call. No node; elevenlabs-tts is single-voice only.
Voice Design (/v1/text-to-voice/design|create|remix) — invent a brand-new voice from a description. No node.
Voice Cloning (/v1/voices/add IVC, /add/professional PVC) — clone a voice from samples. No node.
Voice management / library (GET /v1/voices, /shared, /similar) — Nebula takes a voice_id string but cannot list, search, or pick voices in-app. The user must supply the ID.
Models list (GET /v1/models) — model choices are hard-coded enums, not fetched live; SFX has no model selector at all.
Dubbing extras — the node submits/polls/downloads only. It does not expose transcripts, per-segment editing, speaker-by-speaker rendering, or watch/CSV-script dubbing.
Forced Alignment, Pronunciation Dictionaries, History, Conversational AI/Agents, Studio/Projects, Audio Native, and all streaming/WebSocket variants — none are nodes.
TTS output is MP3 or raw PCM only — no WAV/OGG/Opus container choice from the node; raw PCM lands as .pcm.
Roughly ~33% of the ElevenLabs API surface is wired into Nebula (6 nodes). If the user wants anything in the list above, the honest answer is "not a Nebula node — you'd call the ElevenLabs API directly."