Supabase says "Invalid API key" on every request
You built an app with Lovable, Bolt, v0, or Cursor on top of Supabase. Now every request comes back 401 Unauthorized with {"message":"Invalid API key"}, or the app crashes on load with supabaseUrl is required. Often it worked yesterday, or works locally and dies on the deployed site. This error has exactly five common causes, and each one takes about two minutes to check.
First, see what the app is actually sending
Do this before touching anything. Open the broken page, press F12 for devtools, open the Network tab, and reload. Click a failing request to supabase.co and look at the request headers for one named apikey.
- No
apikeyheader, or no requests to supabase.co at all (often paired withsupabaseUrl is requiredin the Console tab): the key never made it into the build. That is cause 2 below. - An
apikeyheader is present but everything 401s: the key is wrong, stale, disabled, or from another project. Copy its value; you will compare it in causes 1, 3, and 4. - Requests fail with no response at all, or the project URL will not load: check cause 5, a paused project.
Every fix below ends the same way: confirm the correct values on one dashboard page. In Supabase, open your project, then Settings, then API. That page shows the project URL and the API keys side by side. If your app's URL and key both came from that one page, causes 1, 3, and 4 are impossible.
Cause 1: wrong key type, or a legacy key that got disabled
Supabase projects historically had two JWT keys: anon for browsers and service_role for trusted servers. Newer projects use a second generation instead: a publishable key starting with sb_publishable_... and a secret key starting with sb_secret_.... Two ways this bites AI built apps:
- The service role or secret key ended up in the browser. If the key in your frontend code or env vars is the
service_roleJWT or starts withsb_secret_, stop and rotate it now. That key bypasses all Row Level Security, and anyone who opens devtools on your site can read it and then read your entire database. Supabase may also reject it from browser contexts outright. The frontend must only ever use theanonorsb_publishable_key. - Legacy keys were disabled and old generated code broke overnight. On the Settings, API page there is a legacy API keys section with a disable option, and Supabase has been migrating projects toward the new keys since 2025. If that switch is off, every
anonandservice_roleJWT stops working instantly, and code an AI tool generated months ago starts returning "Invalid API key" with no code change on your side. Fix: either re-enable legacy keys as a stopgap, or (better) swap the app to thesb_publishable_key from the same page and redeploy.
Cause 2: env vars exist locally but not on the deploy host
The most common cause when it works locally and 401s in production. Your .env file holds VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY (or NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY on Next.js). But .env is gitignored, so it never reached Vercel, Netlify, or wherever you deployed. The deployed bundle gets undefined for both, which surfaces as supabaseUrl is required or as requests with a garbage key.
- In your hosting dashboard, find Environment Variables (Vercel: Project Settings, Environment Variables. Netlify: Site configuration, Environment variables).
- Add both variables with the exact same names your code reads. The prefix matters:
VITE_andNEXT_PUBLIC_are what expose a variable to browser code, so a var saved as plainSUPABASE_URLis invisible to the frontend. - Redeploy. Not restart, redeploy. Frontend env vars are baked into the JavaScript bundle at build time. Adding a variable changes nothing until a new build runs, and this single detail explains most "I added the env vars and it still fails" threads.
Habit that prevents this permanently: keep a .env.example in the repo listing every variable name with placeholder values. It gets committed (no secrets in it), so any deploy host, collaborator, or diagnosis tool can see exactly which variables the app needs.
Cause 3: the key belongs to a different project than the URL
Each Supabase project has its own URL and its own keys, and a key from project A is invalid on project B. This happens constantly with AI builders: Lovable provisions a fresh Supabase project for you, you fork or remix an app, or you copy the key from an old project's tab while the URL points at a new one.
To check: your project URL looks like https://abcdefghij.supabase.co. That abcdefghij part is the project ref. Legacy JWT keys contain the same ref; paste the key into a JWT decoder (or the debugger at jwt.io) and compare the ref claim to the subdomain in your URL. If they differ, that is your bug. New sb_publishable_ keys do not decode this way, so use the simpler check that works for both: open Settings, then API, in the one project you actually want, and copy the URL and the key from that single page in one sitting. Never mix values from two tabs.
Cause 4: the key is truncated, quoted, or has a trailing newline
Legacy anon keys are long JWTs, over 200 characters. Things that mangle them:
- Cut off in the middle. A partial copy, or a chat tool or editor truncating the line. A JWT has exactly two dots separating three chunks of letters, numbers, dashes, and underscores. Fewer than two dots, or a key that ends abruptly, means truncation.
- Wrapped in quotes in the deploy host UI. Hosting dashboards want the raw value. If you enter
"eyJhbG..."with quotes, the quotes become part of the key and Supabase rejects it. Quotes are fine inside a local.envfile, which is exactly why the same copy paste breaks in the dashboard. - Trailing newline or space. Copying from a terminal or a file often grabs an invisible newline. Some platforms store it, and the sent header no longer matches.
To check: in the Network tab, copy the exact apikey header value the browser sent and paste it next to the key on your Settings, API page in a text editor. Compare lengths and endings character by character. If they differ at all, re-paste the key carefully and redeploy.
Cause 5: your free tier project is paused
Supabase pauses free tier projects after about a week without activity. A paused project refuses all API requests, which the frontend can surface as failed fetches or auth errors, and it commonly hits apps that were built in a burst and then left alone. Open the Supabase dashboard: a paused project shows a paused banner and a restore button on its home page. Click restore, wait a few minutes for it to come back, and reload your app. If the app matters, open it once a week or upgrade the project so it stays awake.
Still stuck?
Run the instant diagnosis. Paste your public repo URL into the form and an automated clean-room check reports what is broken: install, build, missing env config, and render, with the exact blocker named, in minutes, free. It catches the missing env var and truncated key cases directly. Keys and project settings live in your Supabase dashboard rather than the repo, so for those also paste the failing request from your Network tab (without the key value) into the issue and I will look the same day. Private repo or zip? Email works too.
Get an instant free diagnosisOr email me instead. Fixes with 24 hour turnaround start at $95. Prefer self-serve? The $5 instant diagnosis on Apify checks your repo privately, no public issue needed.