moeru-ai/airi · error · Error

Gemini TTS request failed: ${response.status} ${await respon

Error message

Gemini TTS request failed: ${response.status} ${await response.text().catch(() => '')}

What it means

Thrown by the Gemini TTS fetch wrapper when the Gemini API responds with a non-2xx status. The message includes the HTTP status and the raw response body (best-effort, empty on read failure). This is the upstream Google API rejecting the request — most often auth/quota/model issues. It fires only after the input/model body checks passed.

Source

Thrown at packages/stage-ui/src/libs/providers/providers/google-gemini-audio-speech/index.ts:99

    if (!body.model)
      throw new Error('Missing model for Gemini TTS')

    const response = await globalThis.fetch(new URL(`models/${body.model}:generateContent`, baseUrl), {
      method: 'POST',
      headers: { 'x-goog-api-key': apiKey, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        contents: [{ parts: [{ text: body.input }] }],
        generationConfig: {
          responseModalities: ['AUDIO'],
          speechConfig: {
            voiceConfig: { prebuiltVoiceConfig: { voiceName: body.voice || 'Kore' } },
          },
          ...(body.temperature !== undefined ? { temperature: body.temperature } : {}),
        },
      }),
    })
    if (!response.ok)
      throw new Error(`Gemini TTS request failed: ${response.status} ${await response.text().catch(() => '')}`)

    const data = await response.json() as {
      candidates?: Array<{ content?: { parts?: Array<{ inlineData?: { data?: string } }> } }>
    }
    const audio = data.candidates?.[0]?.content?.parts?.find(part => part.inlineData)?.inlineData?.data
    if (!audio)
      throw new Error('Gemini TTS response missing audio data')

    return new Response(toWavFromPCM16(decodeBase64(audio), 24000), {
      status: 200,
      headers: { 'Content-Type': 'audio/wav' },
    })
  }
}

export const providerGoogleGeminiAudioSpeech = defineProvider<GoogleGeminiSpeechConfig>({
  id: 'google-gemini-audio-speech',
  name: 'Google Gemini',

View on GitHub (pinned to 27111382b4)

Solutions

  1. Read the status and body in the message: 401/403 → fix the API key and ensure Gemini API is enabled; 429 → wait and retry with backoff; 404 → correct the model id; 400 → check the request shape.
  2. Confirm the API key in provider settings is valid and has access to the chosen TTS model.
  3. Verify the model id supports audio output (responseModalities: AUDIO).
  4. Enable billing / check quota in the Google Cloud console for sustained 429s.
Defensive patterns

Strategy: try-catch

Validate before calling

// before fetch, sanity-check the api key and model name
if (!apiKey) throw new Error('Gemini API key required')
if (!body.model) throw new Error('Gemini model required')
// network/quota errors are server-authoritative; catch at runtime

Type guard

null

Try / catch

try {
  const res = await provider.speech(model).fetch(url, { body: JSON.stringify(body) })
}
catch (err) {
  if (err instanceof Error && err.message.startsWith('Gemini TTS request failed')) {
    // parse status: 401/403 fix key; 429 back off; 404 fix model id; 400 fix request shape
  }
  else throw err
}

Prevention

When it happens

Trigger: globalThis.fetch to models/${model}:generateContent returns non-ok. Typical Google API errors: 400 (malformed request or unsupported model for TTS), 401/403 (invalid/missing API key or no access to the model), 429 (quota exceeded), 404 (model name wrong/unavailable).

Common situations: Wrong API key or key without Gemini API access. Quota/rate limit hit. Model id typo or a model that does not support AUDIO generation. Regional restriction. Billing not enabled on the Google Cloud project.

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/bbf050b8028db98d. Report an issue: GitHub.