decolua/9router · error · Error
Gemini TTS failed: ${res.status}
Error message
Gemini TTS failed: ${res.status} What it means
When the Gemini generateContent call (with responseModalities AUDIO) returns a non-OK HTTP status, gemini.js surfaces Google's `error.message` if present, else this generic `Gemini TTS failed: <status>`. It means Google rejected the synthesis request or the call failed upstream; the status code distinguishes auth (401/403), bad model/voice (400/404), quota (429), and outages (5xx).
Source
Thrown at open-sse/handlers/ttsProviders/gemini.js:79
export default {
async synthesize(text, model, credentials, _responseFormat, opts = {}) {
if (!credentials?.apiKey) throw new Error("No Gemini API key configured");
const { modelId, voiceId } = parseGeminiModelVoice(model);
const url = `${TTS_BASE}/${modelId}:generateContent?key=${credentials.apiKey}`;
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
contents: [{ parts: [{ text: buildPrompt(text, opts.language) }] }],
generationConfig: {
responseModalities: ["AUDIO"],
speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName: voiceId } } },
},
}),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err?.error?.message || `Gemini TTS failed: ${res.status}`);
}
const data = await res.json();
const b64 = data?.candidates?.[0]?.content?.parts?.find((p) => p.inlineData?.data)?.inlineData?.data;
if (!b64) {
const reason = data?.candidates?.[0]?.finishReason || data?.promptFeedback?.blockReason || "unknown";
throw new Error(`Gemini TTS returned no audio (finishReason: ${reason}, voice: ${voiceId}, model: ${modelId})`);
}
const wav = pcmToWav(Buffer.from(b64, "base64"));
return { base64: wav.toString("base64"), format: "wav" };
},
};
// Voice fetcher — return prebuilt voices (Gemini has no list API)
const PREBUILT_VOICES = [
{ id: "Zephyr", lang: "en", gender: "Female" },
{ id: "Puck", lang: "en", gender: "Male" },
{ id: "Charon", lang: "en", gender: "Male" },
{ id: "Kore", lang: "en", gender: "Female" },View on GitHub (pinned to 90b52e06ff)
Solutions
- Read the status: 400/404 → check that modelId is a currently available Gemini TTS model and voiceName matches a prebuilt voice exactly (case-sensitive); 401/403 → fix the API key and enable the Generative Language API; 429 → wait or raise quota.
- Update the configured TTS model to a currently supported one (the module's KNOWN_MODELS list comes from config/providerModels.js — keep it current).
- Test the key and model directly with curl against the generateContent endpoint to see Google's full error body.
- Retry on 429/5xx with backoff.
- Fall back to a different TTS provider if the preview model is deprecated in your region/account.
Example fix
// before: hardcoded retired preview model
await gemini.synthesize(text, "gemini-3.1-flash-tts-preview/kore", creds);
// after: use a live model + exact-case voice
const model = "gemini-2.5-flash-preview-tts"; // verify availability first
await gemini.synthesize(text, `${model}/Kore`, creds); Defensive patterns
Strategy: retry
Validate before calling
const VALID_VOICES = ["Zephyr","Puck","Charon","Kore","Fenrir","Leda","Orus","Aoede"];
if (voiceId && !VALID_VOICES.includes(voiceId)) throw new Error(`Unknown Gemini voice: ${voiceId} (case-sensitive)`);
if (modelId && !/tts/.test(modelId)) throw new Error(`${modelId} is not a Gemini TTS model`); Try / catch
try {
const audio = await gemini.synthesize(text, `${modelId}/${voiceId}`, creds);
} catch (e) {
if (/failed: 429/.test(e.message)) {
await sleep(10000);
return gemini.synthesize(text, `${modelId}/${voiceId}`, creds);
}
if (/failed: (400|404)/.test(e.message)) throw new Error(`Gemini model/voice invalid: ${modelId}/${voiceId}`);
if (/failed: 40[13]/.test(e.message)) throw new Error("Gemini API key invalid or API not enabled");
throw e;
} Prevention
- Keep KNOWN_MODELS/config providerModels.js updated as Google rotates preview TTS models.
- Use exact-case prebuilt voice names (Kore, Puck, ...).
- Enable the Generative Language API on the key's Google Cloud project.
- Back off and retry on 429/5xx; fail fast with a clear message on 400/404.
When it happens
Trigger: Non-OK response from POST {TTS_BASE}/{modelId}:generateContent?key=... — invalid/expired API key (401/403), unknown modelId (404/400, e.g. a preview TTS model that was retired), unsupported voiceName or malformed request body (400), quota/rate limits (429), or Gemini outage (500/503).
Common situations: Preview TTS model names (like gemini-2.5/3.1-flash-tts-preview) rotating out of availability so the configured model id 404s; API key from a project without the Generative Language API enabled; free-tier quota exhausted; voice name typo (voice ids are case-sensitive, e.g. "Kore" not "kore").
Related errors
- Bing TTS failed: ${res.status}${body ? " - " + body : ""}
- ElevenLabs voices fetch failed: ${res.status}
- ElevenLabs 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/83a1ab8024265bca.
Report an issue: GitHub.