Other clients
Gemini API drop-in
Google's Gemini API for speech, streamed and not, through the cache: keep Google's GenAI SDK, change the base URL and key.
Google's GenAI SDK#
The Gemini API’s speech generation, mirrored under https://api.ttscacherouter.dev/gemini. Keep Google’s GenAI SDK (google-genai): pass the router as its base_url and your router API key as its api_key.
import osimport wavefrom google import genaifrom google.genai import typesclient = genai.Client( api_key=os.environ["TTS_CACHE_ROUTER_API_KEY"], http_options=types.HttpOptions(base_url="https://api.ttscacherouter.dev/gemini"),)config = types.GenerateContentConfig( response_modalities=["AUDIO"], speech_config=types.SpeechConfig( voice_config=types.VoiceConfig(prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name="Kore")), ),)pcm = bytearray()for chunk in client.models.generate_content_stream( model="gemini-3.8-flash-tts", contents="Your order has shipped. It arrives on Friday.", config=config,): content = chunk.candidates[0].content # None when Gemini stops a take without audio for part in content.parts if content else []: pcm += part.inline_data.data # 16-bit PCM, 24000 Hz, monowith wave.open("speech.wav", "wb") as f: f.setnchannels(1) f.setsampwidth(2) f.setframerate(24000) f.writeframes(pcm)import osfrom google import genaifrom google.genai import typesclient = genai.Client( api_key=os.environ["TTS_CACHE_ROUTER_API_KEY"], http_options=types.HttpOptions(base_url="https://api.ttscacherouter.dev/gemini"),)response = client.models.generate_content( model="gemini-3.8-flash-tts", contents="Your order has shipped. It arrives on Friday.", config=types.GenerateContentConfig( response_modalities=["AUDIO"], speech_config=types.SpeechConfig( voice_config=types.VoiceConfig(prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name="Kore")), ), ),)audio = response.candidates[0].content.parts[0].inline_datawith open("speech.wav", "wb") as f: # the 3.8 models answer a WAV file here f.write(audio.data)generate_content_stream and generate_content work through the router with every speech model, voice and setting, sync or async (client.aio). Instead of http_options, you can set GOOGLE_GEMINI_BASE_URL=https://api.ttscacherouter.dev/gemini in the environment, which every GenAI client in the process then uses. Requests, responses and errors are Google’s own, sent to the Gemini API with the Gemini key stored under Provider keys. The Gemini API only: Vertex AI doesn’t go through the router.
LiveKit’s Gemini plugin (google.beta.GeminiTTS) uses the same SDK, so GOOGLE_GEMINI_BASE_URL points it at the router too: see Gemini with LiveKit.
This is Google’s API, with Google’s request shape. The router’s own /v1/audio/speech takes gemini/gemini-3.8-flash-tts as the model, with voice and instructions as fields, and serves every provider.
REST#
Send your router API key where Google takes its key: the x-goog-api-key header, or the key query parameter.
POST /gemini/v1beta/models/{model}:streamGenerateContent?alt=sse: Gemini’s server-sent events, each with a part of the audio.POST /gemini/v1beta/models/{model}:generateContent: one JSON response with the whole audio.GET /gemini/v1beta/voices: Gemini’s voices, never cached. GET /v1/voices lists them in the router’s own shape.GET /gemini/v1beta/modelsandGET /gemini/v1beta/models/{model}: Google’s model list, never cached.
Any other path under /gemini answers Google’s empty 404.
curl -N "https://api.ttscacherouter.dev/gemini/v1beta/models/gemini-3.8-flash-tts:streamGenerateContent?alt=sse" \ -H "x-goog-api-key: $TTS_CACHE_ROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"contents": [{"parts": [{"text": "Your order has shipped."}]}], "generationConfig": {"responseModalities": ["AUDIO"], "speechConfig": {"voiceConfig": {"prebuiltVoiceConfig": {"voiceName": "Kore"}}}}}'curl "https://api.ttscacherouter.dev/gemini/v1beta/models/gemini-3.8-flash-tts:generateContent" \ -H "x-goog-api-key: $TTS_CACHE_ROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"contents": [{"parts": [{"text": "Your order has shipped."}]}], "generationConfig": {"responseModalities": ["AUDIO"], "speechConfig": {"voiceConfig": {"prebuiltVoiceConfig": {"voiceName": "Kore"}}}}}'curl "https://api.ttscacherouter.dev/gemini/v1beta/voices?pageSize=100" \ -H "x-goog-api-key: $TTS_CACHE_ROUTER_API_KEY"What is cached#
- Each request as a whole: the same body again, to the same model and endpoint, replays the same audio. A request is cached when its
contentsis one text, as the SDK sends a string; anything else, andstreamGenerateContentwithoutalt=sse, goes to Google uncached. - A reply from the cache carries the audio only: the same audio, in events that each carry some of it, the last with
finishReason: "STOP". Its usage counts zero tokens (Google spent none on it), and it has noresponseId. - With the 3.8 models, a request as LiveKit’s Gemini plugin sends it shares cached sentences with the router’s own /v1/stream and our plugin, when the settings are the same.
Errors#
Errors come in Google’s shape. A wrong router key gets Google’s own refusal, HTTP 400 API_KEY_INVALID, and no key at all HTTP 403 PERMISSION_DENIED. An org with no Gemini key stored gets the same 400, with a message that links to the dashboard, and an org out of credit HTTP 402. Errors from Google, such as a voice the model doesn’t have, reach you as Google sent them.
HTTP/1.1 400 Bad Request{"error": {"code": 400, "message": "API key not valid. Please pass a valid API key.", "status": "INVALID_ARGUMENT", "details": [{"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "API_KEY_INVALID", …}, …]}}