04
The Gemini relay
What the relay does for your coach, what it costs in latency, and how the worker talks to it.
4. The Gemini relay
Your card carries a team token, not a Google key. With GEMINI_API_KEY=<team token> and
GEMINI_BASE_URL=https://james-72765--rtcoach-gemini.modal.run exported, every Gemini byte from your laptop goes to our relay
(modal/gemini_relay.py), which swaps the token for our key and forwards to Google: the Live websocket (/ws/..., the
worker's --voice gemini) and plain HTTPS (generateContent, models.list, TTS for cues_tts, streaming alt=sse). The
token travels in the x-goog-api-key header; the google-genai SDK does that itself once the two variables are set, so the
worker and cues_tts need no change. An unknown token gets HTTP 401 (JSON {"error": "unknown team token"}) or a websocket
close 1008. Each team has 240 Live minutes; past the cap the relay answers 429 or close 1008 "team cap reached, ask the
organiser". GET /health is public and shows token counts and totals, never a key or token. A team's own Google key works
too: put it in GEMINI_API_KEY and leave GEMINI_BASE_URL unset. Smoke test (replace the token, never paste it anywhere):
curl -s -X POST "https://james-72765--rtcoach-gemini.modal.run/v1beta/models/gemini-2.5-flash:generateContent" -H "x-goog-api-key: <team token>" -H "content-type: application/json" -d '{"contents":[{"parts":[{"text":"hi"}]}]}'