decolua/9router · error
ElevenLabs voices fetch failed: ${res.status}
Error message
ElevenLabs voices fetch failed: ${res.status} What it means
When the GET to https://api.elevenlabs.io/v1/voices returns a non-OK status, fetchElevenLabsVoices throws `ElevenLabs voices fetch failed: <status>` (only the numeric status is included). It signals that the authenticated voices-list request was rejected or failed upstream; the numeric code distinguishes auth problems (401) from quota/plan issues (401/429) and outages (5xx).
Source
Thrown at open-sse/handlers/ttsProviders/elevenlabs.js:16
// ElevenLabs TTS — voice id with optional model_id prefix
import { Buffer } from "node:buffer";
const VOICES_TTL = 24 * 60 * 60 * 1000;
const _voicesCache = new Map(); // by API key
export async function fetchElevenLabsVoices(apiKey) {
if (!apiKey) throw new Error("ElevenLabs API key required");
const now = Date.now();
const cached = _voicesCache.get(apiKey);
if (cached && now - cached.time < VOICES_TTL) return cached.voices;
const res = await fetch("https://api.elevenlabs.io/v1/voices", {
headers: { "xi-api-key": apiKey, "Content-Type": "application/json" },
});
if (!res.ok) throw new Error(`ElevenLabs voices fetch failed: ${res.status}`);
const data = await res.json();
// Normalize: derive lang from labels for grouping
const voices = (data.voices || []).map((v) => ({ ...v, lang: v.labels?.language || "en" }));
_voicesCache.set(apiKey, { voices, time: now });
return voices;
}
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({View on GitHub (pinned to 90b52e06ff)
Solutions
- Match the status to ElevenLabs docs: 401 → re-generate the API key and update it in the 9Router dashboard; 429 → wait and retry, or upgrade the ElevenLabs plan.
- Trim the key when pasting — a trailing newline or space breaks the xi-api-key header.
- Validate the key directly: `curl -H "xi-api-key: $KEY" https://api.elevenlabs.io/v1/voices` — if it fails outside 9Router, the problem is the key/account, not this code.
- Check https://status.elevenlabs.io for outages if the status is 5xx.
- Voice results are cached per key for 24h; if the key was fixed after a failure, no stale cache is involved — simply retry.
Example fix
// before
const res = await fetch("https://api.elevenlabs.io/v1/voices", { headers: { "xi-api-key": rawKey } });
// after: sanitize the key and surface the body
const key = String(rawKey || "").trim();
const res = await fetch("https://api.elevenlabs.io/v1/voices", { headers: { "xi-api-key": key } });
if (!res.ok) {
const detail = await res.text().catch(() => "");
throw new Error(`ElevenLabs voices fetch failed: ${res.status} ${detail}`);
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!apiKey?.trim()) throw new Error("ElevenLabs API key missing");
// optional pre-check of key validity
curl -fsS -H "xi-api-key: $KEY" https://api.elevenlabs.io/v1/voices > /dev/null Try / catch
try {
const voices = await fetchElevenLabsVoices(apiKey);
} catch (e) {
const m = e.message.match(/failed: (\d+)/);
if (m?.[1] === "401") throw new Error("ElevenLabs key invalid/revoked — regenerate it");
if (m?.[1] === "429") throw new Error("ElevenLabs rate limited — retry later");
if (m && +m[1] >= 500) throw new Error("ElevenLabs outage — try again shortly");
throw e;
} Prevention
- Trim API keys on input (stray whitespace breaks the header).
- Rotate keys deliberately: update 9Router in the same change as the ElevenLabs dashboard.
- Watch your ElevenLabs quota/plan to anticipate 429s.
- Subscribe to ElevenLabs status page for outage awareness.
When it happens
Trigger: Any non-OK response from the ElevenLabs /v1/voices endpoint: invalid or revoked API key (401), free-tier/quota restrictions (401/429), ElevenLabs outage or maintenance (500/503), or network-layer responses like 451 in restricted regions.
Common situations: API key rotated or deleted in the ElevenLabs dashboard while still configured in 9Router; free-tier account hitting rate limits; typo'd key (extra whitespace/newline when pasting); ElevenLabs regional availability changes; temporary upstream outage.
Related errors
- Bing TTS failed: ${res.status}${body ? " - " + body : ""}
- ElevenLabs TTS failed: ${res.status}
- Gemini TTS failed: ${res.status}
- [${res.status}] ${text}
- Bing translator fetch failed: ${res.status}
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/241148805349563c.
Report an issue: GitHub.