Configuration
Configuration comes from three places, and each has one job.
| Place | Holds | Read by |
|---|---|---|
api/wrangler.jsonc → vars | Non-secret settings, pinned per environment: models, voice, DATABASE_PROVIDER, AI Gateway ids, ALLOWED_ORIGINS | wrangler dev and wrangler deploy |
api/.dev.vars (gitignored) | Local secrets and local overrides: GEMINI_API_KEY, DATABASE_URL, CF_AIG_TOKEN | wrangler dev, and the CLI scripts through loadDevVars |
wrangler secret put | Deployed secrets, per environment | The deployed Worker |
Non-secret settings are pinned in wrangler.jsonc rather than set in the dashboard because a wrangler deploy sets only what is in that file. A value set by hand in the dashboard is lost on the next deploy.
wrangler dev reads .dev.vars and vars only at start-up. Restart it after changing either.
Variables
The full typed list is Env in api/src/env.ts. The environment reference has the same table with defaults per environment.
| Variable | Required | Default | Purpose |
|---|---|---|---|
GEMINI_API_KEY | for chat, voice, generation | none | Google AI Studio key. Without it /api/health reports configured: false and chat returns an error event. |
DATABASE_PROVIDER | no | d1 | d1, neon or none: which claims and feedback store the Worker uses. none runs with no store, for a deployment that only serves embedded hosts. |
DATABASE_URL | when neon | none | Neon connection string (project EAIW, database claimpilotchat). |
AI_PROVIDER | no | gemini | scripted swaps Gemini for the offline test adapter. Tests only. Never on a deployment. |
GEMINI_MODEL | no | gemini-3.1-flash-lite | Text chat model. |
GEMINI_LIVE_MODEL | no | gemini-3.1-flash-live-preview | Voice model (Live API). |
GENERATOR_MODEL | no | GEMINI_MODEL | Model used by the claim generator. |
GEMINI_THINKING_LEVEL | no | model default | low, medium or high. Capabilities may set their own. Any other value is ignored. |
GEMINI_VOICE | no | Charon | Prebuilt Live voice name. |
AI_GATEWAY_ACCOUNT_ID | no | pinned | Cloudflare account id. With AI_GATEWAY_ID, routes model traffic through AI Gateway. |
AI_GATEWAY_ID | no | claimpilot-chat | AI Gateway name. Either id missing means straight to Google. |
CF_AIG_TOKEN | when the gateway is authenticated | none | cf-aig-authorization bearer token. |
ALLOWED_ORIGINS | for cross-origin embedding | unset | Comma-separated origins allowed to embed the assistant. An entry ending :* matches any port on that host. |
EMBED_KEY | no | unset (open) | Shared key on /api/chat, /api/voice and /api/feedback. A budget guard, not access control. |
The defaults in the table are the code's fallbacks. wrangler.jsonc pins some of them to other values (for example a newer model); /api/health always reports what is actually in use.
Cloudflare AI Gateway
Both gateway ids are pinned in wrangler.jsonc, so the gateway is on for deployments. Every model call (chat, generation and voice) is proxied through AI Gateway, which gives us request logs, cost per call, retries and rate limiting. Clear both ids and the same code calls Google directly.
How it is wired:
api/src/ai/gateway.tsis the only place gateway URLs are built. Blank values count as unset.- Google sits behind two provider paths, and they are not interchangeable.
google-ai-studiois the REST provider, handed to@google/genaias itsbaseUrlfor chat and generation.googleis the realtime provider, used as the voice relay's upstream. - We keep sending our own key (
x-goog-api-key) rather than storing it in the gateway. Gateway-side key injection is where the Gemini 3.x preview models have been unreliable. - The integration is URL-based rather than the
env.AIbinding, because the generator CLI runs in plain Node, where Worker bindings do not exist.
The ids decide the route; the token does not. The gateway is authenticated, so CF_AIG_TOKEN must be set wherever the ids are. A missing token does not fall back to Google: the gateway rejects every call with AiGatewayError 2009 Unauthorized.
Locally the gateway behaves exactly as in production. It is a public endpoint and wrangler dev makes real outbound calls, so local traffic is logged too. To go straight to Google locally, blank both ids in api/.dev.vars:
GEMINI_API_KEY=…
AI_GATEWAY_ACCOUNT_ID=
AI_GATEWAY_ID=
/api/health, the Claims Data page and /status all show which route is live.
Local Neon instead of local D1
To point local development at the shared Neon database, set both of these in api/.dev.vars and restart:
DATABASE_PROVIDER=neon
DATABASE_URL=postgres://…
Neon is shared. Prefer local D1 for anything destructive.