{"record":{"id":"99e0bca37516fe84","repo":"zeroclaw-labs/zeroclaw","slug":"elevenlabs-voice-id-contains-invalid-characters","errorCode":null,"errorMessage":"ElevenLabs voice ID contains invalid characters: {voice}","messagePattern":"ElevenLabs voice ID contains invalid characters: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/tts.rs","lineNumber":229,"sourceCode":"}\n\n#[async_trait::async_trait]\nimpl TtsProvider for ElevenLabsTtsProvider {\n    fn name(&self) -> &str {\n        \"elevenlabs\"\n    }\n\n    fn output_format(&self) -> &str {\n        // ElevenLabs default output is MP3 (mp3_44100_128).\n        \"mp3\"\n    }\n\n    async fn synthesize(&self, text: &str, voice: &str) -> Result<Vec<u8>> {\n        if !voice\n            .chars()\n            .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')\n        {\n            bail!(\"ElevenLabs voice ID contains invalid characters: {voice}\");\n        }\n        let url = format!(\"https://api.elevenlabs.io/v1/text-to-speech/{voice}\");\n        let body = serde_json::json!({\n            \"text\": text,\n            \"model_id\": self.model_id,\n            \"voice_settings\": {\n                \"stability\": self.stability,\n                \"similarity_boost\": self.similarity_boost,\n            },\n        });\n\n        let resp = self\n            .client\n            .post(&url)\n            .header(\"xi-api-key\", &self.api_key)\n            .json(&body)\n            .send()\n            .await","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/tts.rs#L211-L247","documentation":"ElevenLabsTtsProvider::synthesize interpolates the voice string directly into the URL path (https://api.elevenlabs.io/v1/text-to-speech/{voice}). Before sending, it requires every character of the voice to be ASCII alphanumeric, '-' or '_'; anything else bails immediately. This is a path-injection guard that stops '/', '?', '#', '.', and whitespace from rewriting the request URL.","triggerScenarios":"Calling synthesize/synthesize_with_voice with an ElevenLabs voice name (\"Rachel\", \"Aria\"), a voice label containing a space or dot, a URL-encoded voice id, or any value with '/', '\\', or ':' in it. Config values like voice = \"Rachel (English)\" under [providers.tts.elevenlabs.<alias>] hit the same check.","commonSituations":"Developers copy the human-readable voice name from the ElevenLabs dashboard instead of the 20-char voice ID (for example 21m00Tcm4TlvDq8ikWAM). A stray space or newline from copy-paste into TOML also trips it.","solutions":["Use the ElevenLabs voice ID (alphanumeric, '-' or '_'), not the display name.","Set [providers.tts.elevenlabs.<alias>].voice to that ID so TtsManager picks it automatically.","Trim whitespace from the configured or passed voice before calling synthesize.","List your account's real voice IDs with GET https://api.elevenlabs.io/v1/voices and copy the id field."],"exampleFix":"# before — display name, contains a space\n[providers.tts.elevenlabs.main]\napi_key = \"xi-...\"\nvoice = \"Rachel\"\n\n# after — the account's voice ID\n[providers.tts.elevenlabs.main]\napi_key = \"xi-...\"\nvoice = \"21m00Tcm4TlvDq8ikWAM\"","handlingStrategy":"validation","validationCode":"// Run before calling synthesize/synthesize_with_voice.\nfn is_valid_elevenlabs_voice_id(voice: &str) -> bool {\n    !voice.is_empty()\n        && voice.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')\n}\n\nassert!(is_valid_elevenlabs_voice_id(&voice), \"pass the ElevenLabs voice ID, not the name\");","typeGuard":"fn is_elevenlabs_voice_id(v: &str) -> bool {\n    v.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') && !v.is_empty()\n}","tryCatchPattern":null,"preventionTips":["Store voice IDs (20-char alphanumerics) in config, never display names.","Fetch the canonical id list from GET /v1/voices at setup time and copy the id field verbatim.","Trim configured voice values in TOML to avoid trailing-space surprises."],"tags":["elevenlabs","tts","validation","voice-id"],"backgroundTag":"input-validation-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}