Secrets
The AI edge functions read their configuration from Supabase function secrets. These are server-side only: they are never bundled into your app, and must never be given an EXPO_PUBLIC_ prefix.
What you need
| Secret | Required | What it is |
|---|---|---|
OPENROUTER_API_KEY | Yes | Your OpenRouter API key. Used by chat, generate and identify. Without it every AI call returns provider_not_configured. |
AI_FREE_MESSAGES | No | Monthly free-message allowance per user. Defaults to 10. Keep it in sync with config.js → ai.freeMessagesPerMonth. |
REVENUECAT_SECRET_KEY | Recommended | Your RevenueCat secret API key . Used server-side to check the user’s Pro entitlement. When it is unset, every user is treated as not-Pro, so Pro-only features (image generation, image-output chat models) are refused for everyone. |
PRO_ENTITLEMENT_ID | No | The RevenueCat entitlement that grants Pro. Defaults to pro. Must match config.js → purchases.proEntitlementId, see below. |
Supabase injects SUPABASE_URL, SUPABASE_ANON_KEY and SUPABASE_SERVICE_ROLE_KEY into deployed functions automatically. You do not set those yourself, and supabase secrets set rejects the SUPABASE_ prefix. You do need them in supabase/functions/.env for local development.
supabase/functions/.env.example documents all of them.
AI_FREE_MESSAGES counts a user’s chat messages plus their scans in the current calendar month. Image generations are not counted, because they are Pro-only and never metered.
The two entitlement settings must match
Two places name the RevenueCat entitlement that unlocks Pro. The app gates its UI on one; the server enforces the other.
| Where | Setting | Default |
|---|---|---|
| App | config.js → purchases.proEntitlementId | 'pro' |
| Server | PRO_ENTITLEMENT_ID secret | pro |
If they disagree, a paying user gets an app that looks unlocked while every server call answers 403 pro_required: tapping a Pro feature opens the upgrade paywall to someone who already paid.
Both default to pro, so if you name your RevenueCat entitlement pro there is nothing to do. If yours is named something else, such as premium or pro_monthly, set both to that value:
purchases: {
proEntitlementId: 'premium',
},supabase secrets set PRO_ENTITLEMENT_ID="premium"
supabase functions deploySet them
Get an OpenRouter key
Create a key from the OpenRouter dashboard . Add credit to the account: a key with a zero balance fails on the first request.
Get your RevenueCat secret key
Copy your project’s secret API key from the RevenueCat dashboard. It starts with sk_, and it is not the public SDK key you put in .env.local.
Push the secrets to your Supabase project
From your project root:
supabase secrets set OPENROUTER_API_KEY="sk-or-v1-..."
supabase secrets set REVENUECAT_SECRET_KEY="sk_..."
supabase secrets set AI_FREE_MESSAGES="10"Verify
supabase secrets listThis prints OPENROUTER_API_KEY, REVENUECAT_SECRET_KEY and AI_FREE_MESSAGES alongside their digests.
Secrets are read at function start-up. After changing one, run supabase functions deploy again. An already-running function keeps the old value.
Error codes
The function response body carries a code. These are the ones caused by configuration:
| Code | HTTP | Meaning |
|---|---|---|
provider_not_configured | 500 | OPENROUTER_API_KEY is not set. |
pro_required | 403 | The user is not Pro. Also what you get for every user when REVENUECAT_SECRET_KEY is missing. |
quota_exceeded | 402 | The user used up AI_FREE_MESSAGES this month and is not Pro. |
invalid_model | 400 | The requested model slug is not in the function’s allow-list. Add it to both config.js and supabase/functions/chat/index.ts. |