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, for AI | Your OpenRouter API key, used by chat, generate and identify. If it is missing, provider setup returns provider_not_configured. |
AI_FREE_MESSAGES | No | Overrides freeMessagesPerMonth from supabase/functions/_utils/ai.config.json on the server. Keep it at the same value as the app’s allowance. The supplied default is 10. |
REVENUECAT_SECRET_KEY | Yes, for Pro features | Your RevenueCat secret API key for API v1. The server uses it to check Pro entitlements. If it is unset, image generation and image-output chat models are refused for every user. |
PRO_ENTITLEMENT_ID | No | The RevenueCat entitlement that grants Pro. Defaults to pro. Must match config.js → purchases.proEntitlementId. |
Supabase supplies SUPABASE_URL and the project’s API keys to the functions, when
deployed and under supabase functions serve. Do not add them as custom project
secrets; supabase secrets set rejects the SUPABASE_ prefix. The functions read the
publishable and secret keys from SUPABASE_PUBLISHABLE_KEYS and SUPABASE_SECRET_KEYS,
and fall back to the legacy SUPABASE_ANON_KEY and SUPABASE_SERVICE_ROLE_KEY on a
project that has not created the new keys. The secret and service-role keys bypass
row-level security and must never be included in your app. See Supabase’s
default secrets .
supabase/functions/.env.example documents all of them.
For local development, keep custom secrets in
supabase/functions/.env. Chat messages and scans share the monthly allowance;
image generations are excluded. See quota for how usage is counted.
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 may see Pro features in the app while the server refuses image generation or applies the free allowance.
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"Set them
Get an OpenRouter key
Create a key from the OpenRouter dashboard and add credit to use paid models.
Get your RevenueCat secret key
Copy a secret API key for API v1 from your RevenueCat project. It starts with
sk_. Keep it separate from the public SDK keys in .env.local. See
RevenueCat authentication .
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 listConfirm that the secret names are listed. This verifies that the values were saved, not that the credentials work. After deploying the functions, send a chat message and test image generation with a Pro account.
Hosted secret changes are available without redeploying. Changes to code or to a
function’s config.json or _utils/ai.config.json still require a deploy. See Supabase’s
production secrets .
Replace the legacy Supabase keys
Supabase is removing the legacy anon and service_role keys by the end of 2026; see
its API keys guide . If
EXPO_PUBLIC_SUPABASE_ANON_KEY holds a key starting with eyJ, your app ships the
legacy anon key. Once a project’s legacy keys are off, every request that carries
one is refused with Legacy API keys are disabled. Do these steps in order.
Create the new keys
In the Supabase dashboard, create a publishable key and a secret key in the project’s API keys settings. They work alongside the legacy pair, so nothing changes yet.
Redeploy the functions
supabase functions deploy chat generate identify delete-accountLeave out any function whose feature you removed. The functions use the new keys when the project has them and the legacy keys otherwise, so this deploy works before and after the switch.
Release a build with the publishable key
Put the publishable key (sb_publishable_…) in EXPO_PUBLIC_SUPABASE_ANON_KEY,
in .env.local and in every
EAS environment.
The variable name stays the same. Build and release the app to the stores.
Disable the legacy keys
Disable them in the same dashboard settings once no installed version of your app
still ships the anon key. An installed build keeps the key it was compiled with,
so changing the environment variable does not reach it. You can turn the legacy
keys back on after disabling them; Supabase’s removal cannot be undone.
Verify
yarn skills:doctorIf EXPO_PUBLIC_SUPABASE_ANON_KEY still holds a legacy key, the Supabase check
says so. Then sign in with the released build and send a chat message.
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 | A Pro-only feature was requested without a verified entitlement. Check REVENUECAT_SECRET_KEY and the entitlement name. |
quota_exceeded | 402 | The user used up AI_FREE_MESSAGES this month and is not Pro. |
ai_consent_required | 403 | Refused even after the user taps Allow: the deployed functions carry a newer consentVersion than the installed app build. See AI consent. |
invalid_model | 400 | The requested model slug is not in the deployed registry. Add it to supabase/functions/chat/config.json and redeploy chat. |