decolua/9router · error · Error
ElevenLabs TTS failed: ${res.status}
Error message
ElevenLabs TTS failed: ${res.status} What it means
The ElevenLabs synthesize call POSTs to /v1/text-to-speech/{voiceId}; on a non-OK response it first tries to parse the JSON body and surface ElevenLabs' own `detail.message`, falling back to this generic `ElevenLabs TTS failed: <status>` when the body is not parseable or lacks a message. It means ElevenLabs rejected the synthesis request or the request failed upstream.
Source
Thrown at open-sse/handlers/ttsProviders/elevenlabs.js:42
export default {
async synthesize(text, model, credentials) {
if (!credentials?.apiKey) throw new Error("ElevenLabs API key required");
let modelId = "eleven_flash_v2_5";
let voiceId = model;
if (model && model.includes("/")) [modelId, voiceId] = model.split("/");
const res = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, {
method: "POST",
headers: { "xi-api-key": credentials.apiKey, "Content-Type": "application/json" },
body: JSON.stringify({
text,
model_id: modelId,
voice_settings: { stability: 0.5, similarity_boost: 0.75 },
}),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err?.detail?.message || `ElevenLabs TTS failed: ${res.status}`);
}
const buf = await res.arrayBuffer();
if (buf.byteLength < 1024) throw new Error("ElevenLabs TTS returned empty audio");
return { base64: Buffer.from(buf).toString("base64"), format: "mp3" };
},
};
View on GitHub (pinned to 90b52e06ff)
Solutions
- If the message shown is ElevenLabs' own detail.message, follow it; if you only got the status, re-run with the raw response logged to capture the body.
- 401 → regenerate/update the API key; 429/quota → wait or upgrade the ElevenLabs subscription.
- Verify the voice id exists: GET /v1/voices with the same key and use one of its voice_ids (model string is `modelId/voiceId`).
- Confirm the account has access to the requested model_id; switch to `eleven_multilingual_v2` or `eleven_flash_v2_5` per your plan.
- If the response body is HTML (proxy error), check your network path/proxy rather than ElevenLabs itself.
Example fix
// before
await synthesize(text, "eleven_flash_v2_5/old-voice-id", { apiKey });
// after: verify the voice exists first
const list = await fetchElevenLabsVoices(apiKey);
const voice = list.find(v => v.voice_id === voiceId || v.name === voiceId);
if (!voice) throw new Error(`Unknown ElevenLabs voice: ${voiceId}`);
await synthesize(text, `eleven_flash_v2_5/${voice.voice_id}`, { apiKey }); Defensive patterns
Strategy: retry
Validate before calling
const voices = await fetchElevenLabsVoices(apiKey); // throws early on bad key
const voice = voices.find(v => v.voice_id === voiceId || v.name === voiceId);
if (!voice) throw new Error(`Unknown ElevenLabs voice: ${voiceId}`);
if (!text?.trim()) throw new Error("TTS text is empty"); Try / catch
try {
const audio = await elevenlabs.synthesize(text, `${modelId}/${voiceId}`, { apiKey });
} catch (e) {
if (/failed: 429/.test(e.message)) {
await sleep(5000);
return elevenlabs.synthesize(text, `${modelId}/${voiceId}`, { apiKey });
}
if (/failed: 40[13]/.test(e.message)) throw new Error("Check ElevenLabs key/plan access for this model");
throw e;
} Prevention
- Resolve voice ids from GET /v1/voices instead of hardcoding them.
- Confirm your ElevenLabs plan supports the chosen model_id.
- Track character quota to avoid surprise 429/401s mid-month.
- Log response bodies on this path to see ElevenLabs' detail.message when the fallback message fires.
When it happens
Trigger: Non-OK response from POST https://api.elevenlabs.io/v1/text-to-speech/{voiceId}: unknown/removed voice_id (404? actually 400/422), invalid key (401), quota exceeded or free-tier limit (401/429), model_id not available on the account's plan (e.g. eleven_flash_v2_5 unavailable), malformed body, or a non-JSON error body (proxy/CDN error page) so the detail parse fails.
Common situations: Voice id deleted or renamed in the ElevenLabs account; using a premium model id on a free plan; running out of character quota; sending text with invalid characters; a reverse proxy returning an HTML 502 that defeats the JSON detail extraction, leaving only the status code.
Related errors
- Bing TTS failed: ${res.status}${body ? " - " + body : ""}
- ElevenLabs voices fetch failed: ${res.status}
- Gemini TTS failed: ${res.status}
- Bing translator fetch failed: ${res.status}
- Edge TTS voices fetch failed: ${res.status}
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/0b0368ceb3cef34a.
Report an issue: GitHub.