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

  1. 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.
  2. Trim the key when pasting — a trailing newline or space breaks the xi-api-key header.
  3. 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.
  4. Check https://status.elevenlabs.io for outages if the status is 5xx.
  5. 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

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


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/241148805349563c. Report an issue: GitHub.