The message is four words long and it names nothing useful. Invalid API key, sitting in a red box where a map, a payment form, an email confirmation or an AI answer used to be. You did not write the line of code that produced it. The app worked yesterday.

The message comes from one of the services your app talks to, not from your app. One of your connected services looked at the pass your app showed it and said no. The exact words tell you which service, so read them before you change anything.

A working app built with Lovable, Replit or Base44 usually holds five keys at once: one for AI, one for the database, one for payments, one for maps, one for sending email. Any of the five can be the one being refused, they fail in different words, and each of them lives in a different place depending on which builder made your app.

Every string on this page was copied out of the vendor’s own error reference on 25 August 2026: OpenAI’s error-code guide, Stripe’s error-code table and its errors reference, Supabase’s REST error codes and API-keys guide, Google’s Maps JavaScript error list, Resend’s error table and SendGrid’s mail-send reference. Where a vendor does not publish the words people actually see, this page says so on the date it checked instead of guessing.

What does “invalid API key” mean?

An invalid API key means the service your app called looked at the credential it was handed and rejected it. The key is wrong, expired, rotated, restricted, or from a different project. The failure is a symptom: the logic of your app is usually fine, and what is wrong is the credential or the configuration that hands it over, such as a variable under the wrong name.

Every service your app connects to hands out a long string of characters that stands in for your account. Your app attaches that string to every request it makes. The service reads it, decides whether it belongs to a live account with permission to do the thing being asked, and answers. An invalid key message is the moment that check came back negative.

That is why the message is so unhelpful on its own. It is generated by whichever service is refusing you, and it says nothing about the other four. The one piece of real information in it is the wording. Vendors write these strings differently, and the differences are the whole diagnosis.

ServiceThe words you seeStatusWhat the vendor says it means
OpenAIInvalid Authentication401The fix given is to “Ensure the correct API key and requesting organization are being used.”
OpenAIIncorrect API key provided401Cause: “The requesting API key is not correct.” Fix: “Ensure the API key used is correct, clear your browser cache, or generate a new one.”
OpenAIcredit_balance_exhausted429”Your organization has no prepaid credits remaining.” The key is valid and the account is empty
OpenAIorganization_spend_limit_exceeded429”Your organization reached its enforced spend limit.”
StripeNo valid API key provided401The entire description Stripe gives its 401 status
Stripeapi_key_expirederror code”The API key provided has expired. Obtain your current API keys from the Dashboard and update your integration to use them.”
Stripesecret_key_requirederror code”The API key provided is a publishable key, but a secret key is required…”
Stripelivemode_mismatcherror code”Test and live mode API keys, requests, and objects are only available within the corresponding mode.”
SupabaseUNAUTHORIZED_MISSING_API_KEYgatewayThe code the API gateway recorded, and the field is empty when the request reached PostgREST and failed there
Supabaseno_authorizationauth”This HTTP request requires an Authorization header, which is not provided.”
Supabasebad_jwtauth”JWT sent in the Authorization header is not valid.”
Google MapsInvalidKeyMapErrorbrowser console”The API key included in the script element that loads the API is not found.”
Google MapsApiNotActivatedMapErrorbrowser console”The Maps JavaScript API is not activated on your API project.”
Google MapsRefererNotAllowedMapErrorbrowser console”The current URL loading the Maps JavaScript API has not been added to the list of allowed referrers.”
Google MapsBillingNotEnabledMapErrorbrowser console”You have not enabled billing on your project which is causing this error.”
Resendmissing_api_key401”Missing API key in the authorization header.”
Resendrestricted_api_key401”This API key is restricted to only send emails.”
Resendrestricted_api_key403”API key is not active”
Resendsuspended_api_key403”This API key is suspended”
Resendvalidation_error403”… domain is not verified. Please, add and verify your domain.” A working key that still cannot send
SendGridauthorization required401The message inside the body SendGrid returns when the request is not authorized

Sources, one at a time. The two OpenAI 401 rows and all three 429 rows come from OpenAI’s error-code guide. The 401 wording is from Stripe’s errors reference and the three named codes from Stripe’s error-code table. UNAUTHORIZED_MISSING_API_KEY appears in Supabase’s REST error-code reference, which describes sb_error_code as “the error code the API gateway recorded for a request, such as UNAUTHORIZED_MISSING_API_KEY”, and the two auth codes come from Supabase’s auth error codes. The four Maps strings are from Google’s Maps JavaScript error list. The email rows are from Resend’s error table and SendGrid’s mail-send reference, where the 401 body is printed in full:

{"errors":[{"message":"authorization required","field":"null"}]}

Two of those rows are missing something, and the absence is worth as much as the strings. As of 25 August 2026, neither Stripe page prints the literal words Invalid API key anywhere. The documented 401 wording is No valid API key provided. And as of the same date, Supabase’s REST error-code reference and its API-keys guide do not publish an Invalid API key response body either. So if your screen shows those exact four words next to a Stripe or Supabase failure, the wording is your own app’s, or your builder’s, wrapped around whatever the service actually returned. That matters, because searching the four words you can see will take you to somebody else’s vendor and somebody else’s answer.

The same failure turns up in the smallest starter app anyone builds. A weather app key that stops being accepted takes the weather panel down with it, and it belongs to exactly the same four causes as a payment that will not go through.

Work out which of your five keys is the one being refused

There are only three things worth reading before you touch anything, and all three are already on your screen or one click away. Do them in this order.

  1. 01 Copy the exact string, all of it, including anything in backticks or capitals. A vendor-shaped code like InvalidKeyMapError or credit_balance_exhausted names the vendor by itself. Generic words like invalid api key do not, which is why the next two reads matter.
  2. 02 Find the status number if one is visible. A 401 is a refusal: the credential was read and rejected. A 429 is a limit: the credential was accepted and the account has run out of something. A 403 usually means the key is real but restricted or turned off. Those three tell three different stories and only the first one is a key problem.
  3. 03 Name the part of the app that stopped. If the map is blank and the checkout works, the refused key is the maps key and the payments key is fine. If everything is blank at once, the database key is the candidate, because most of the screen is usually reading from it.

Those three reads narrow five keys to one most of the time. Where they do not, it is normally because the app is showing its own wrapper text instead of the service’s, and then the useful move is to open the browser console on the failing page, where Google Maps in particular prints its error name in full.

One thing is worth ruling out before you spend any more time on the key. If the builder itself is having a bad morning, every service it proxies can look broken from inside your project, so a look at the builder’s own status page comes before anything you change in your project. A key that gets refused and a request that never comes back are also two different failures with two different fixes, and the second one usually returns nothing at all rather than a message.

This page starts from the words on your screen. Where a value is supposed to be set, and why a deployed build can be missing one, is the longer read, and it is the right one if what you have is a blank where a value should be and not a service saying no.

Where Lovable keeps your API keys, and how they go stale

Lovable keeps API keys in Secrets, reached from the Cloud tab. Saved values are write-only, so you cannot read back what you entered, only replace it. If your project uses an external Supabase rather than the built-in backend, the keys live in that dashboard instead.

Lovable’s Secrets documentation defines them in one line: “Secrets store sensitive values such as API keys, tokens, and credentials without exposing them in code. They are encrypted, injected into your Edge Functions automatically, and never reach the browser.” The last clause explains why a key can be perfectly correct and still produce nothing you can inspect from the page itself. It never went to the browser, so the browser cannot show you what it sent.

The write-only rule is the part that catches people out, and the same page states it plainly: “Secrets are write-only: after you save a secret, its value can never be viewed again in Lovable, only replaced or deleted.” You cannot check the value you saved against the value in the vendor’s dashboard. You can only overwrite it with a fresh copy and see whether the error goes away. That is the intended way to work, and it makes re-pasting the key a correct first move instead of a lazy one.

The surfaces are named on the same page and nowhere else worth trusting: the Cloud tab, then Secrets, with an Add secret dialog, a Remove action, and a Rotate action for LOVABLE_API_KEY. Two name prefixes are off limits. The documentation says the SUPABASE_ and LOVABLE_ prefixes “are managed automatically by the built-in backend and cannot be created or overwritten in the Secrets UI”, so if the key you want to change starts with either, the Secrets panel is the wrong place to be looking.

Which brings up the split that sends more people to the wrong dashboard than anything else on this page. Lovable’s Cloud documentation says “The built-in backend utilizes Supabase’s open-source foundation”, and names it the built-in backend, in contrast to a Supabase project you connect and own yourself. Two things that are both Supabase, in other words, and only one of them is yours. One person building on Lovable described the confusion exactly:

I noticed lovable is using it’s our superbase yet I have my own superbase project with my own tables. I have tried to connect onto mt superbase project but lovable is denying to connect to mine.

If your project is on an external Supabase, the Secrets panel is not where the key lives. The documentation is specific: secrets are managed in Supabase, and “the backend view shows a Manage secrets button that opens your Supabase project’s dashboard”. Everything you change happens over there.

Last thing about Lovable, and it decides whether an AI key is even yours to fix. Lovable’s integrations documentation says “Lovable AI gives your app AI capabilities without API keys or external accounts.” So if an OpenAI key is the one being refused, somebody wired that one in on purpose, and quite often the reason was cost. One builder asking for help with exactly that setup put it this way: “I am using my own API thing instead of lovable credit because those run out way faster and also Gemini has a limit. I am just wondering, could anyone help me out please?” That trade is a reasonable one to make, and the thing you take on with it is a key that is now yours to keep working.

Which Lovable join broke, service by service, is a different question with its own answers per service. This page stops at the key.

Replit and Base44 store keys in a different place

Replit calls the feature the same thing and puts it somewhere else. Replit’s Secrets documentation describes it as a tool: “The Secrets tool stores and encrypts secrets, your Replit App’s sensitive information, such as API keys, authentication tokens, and database connection strings.” The pane holds two tabs, App Secrets and Account Secrets, and the difference between them is the one to check first when a key that exists still is not reaching the app.

Secrets arrive in the running app as environment variables. In JavaScript that means the code reads process.env.MY_SECRET, which is worth knowing because the name in the pane and the name in the code have to match exactly, and a key that was added under a slightly different name will produce a refusal that looks identical to a wrong key.

One line on that page rules out a whole class of confusion: “Secrets are available for all deployment types except Static Deployments.” If the published thing is a static deployment, there is no secret to be missing and no key to fix, because that deployment type never received one.

Base44 takes a key two ways, and the first one is easy to forget you used. Base44’s integrations documentation says you can “Enter your API key in the AI chat when prompted”, or go to Dashboard then Secrets and use Add Secret. A key typed into a chat weeks ago is still a key, and it is stored in the same place either way. Custom integrations built at the workspace level work differently again: the documentation says those credentials are “configured once by a workspace admin when creating or editing the integration and are stored as encrypted workspace secrets, not per-app Secrets”, so an app owner looking in their own app’s Secrets will find nothing.

Some Base44 apps have no key to check at all. The same page says “Some integrations come preinstalled in Base44 and are available to use immediately”, naming Invoke LLM, SendEmail, SendPushNotification, UploadFile, GenerateImage and ExtractDataFromUploadedFile. If the failing feature is one of those, nothing you own is being refused and the fault is somewhere else entirely.

Lovable, Replit, and Base44 paths to app, account, external Supabase, and workspace API key storage.

A note on where the Base44 answer came from, because the shape of the documentation matters here. As of 25 August 2026 the documentation home page at docs.base44.com links no dedicated secrets or API-keys page. Everything above is documented inside the integrations page, which is a young docs site organizing itself, and it is the reason searching Base44’s docs for “secrets” is less useful than it should be.

Why it worked yesterday

Four things cause a key that worked to stop working, and none of them require anyone to have touched your code: a key was rotated, money ran out, the app is running somewhere the key was never valid, or a test key is being asked to do live work.

A key was rotated

Somebody generated a new key and the old one stopped being accepted, either at the vendor or by a security tool doing its job. The sharpest documented version of this is Supabase’s, because rotating one secret takes everything with it. Supabase’s troubleshooting guide on rotating secrets states that once the JWT secret is regenerated, “all current API secrets will be immediately invalidated, and all connections using them will be severed”, and that “You will need to deploy the new secrets for connections to begin working again”. Note the second sentence. Changing the value in one place is not the end of the job.

Stripe’s api_key_expired is the same story with a calmer name: “The API key provided has expired. Obtain your current API keys from the Dashboard and update your integration to use them.”

Supabase is also mid-change on key formats, which is its own source of rotations. Its API keys guide describes publishable keys (sb_publishable_...) as “Safe to expose online: web page, mobile or desktop app, GitHub actions, CLIs, source code”, and marks secret keys (sb_secret_...) as backend-only because they read past the project’s row-level rules. The older anon and service_role keys are on a clock too: the same page puts an end-of-2026 deprecation on them.

The free tier ended, or the balance ran out

This is the commonest thing people mistake for a broken key, and the vendors themselves tell them apart cleanly. OpenAI codes an empty balance as a 429, not a 401: credit_balance_exhausted, described as “Your organization has no prepaid credits remaining.” The key is valid. There is nothing left to spend. The same page codes organization_spend_limit_exceeded and project_spend_limit_exceeded the same way, which is worth knowing because a spend cap you set months ago can end a working app on an ordinary Tuesday.

An app that stopped because the bill stopped is a different problem from a key that stopped being accepted, even though both arrive on the same morning. If the number you are looking at is a 429, the question to answer is a billing one and the key is a red herring.

The wrong environment

A key can be correct and still be refused because of where it is being used from. Supabase documents one case with no ambiguity at all: “You cannot use a secret key in the browser (matches on the User-Agent header) and it will always reply with HTTP 401 Unauthorized.” Always. It does not matter that the key is real.

Replit’s static-deployment exception belongs to the same family: the key exists in the workspace and never arrives in the published thing. So does the classic case where preview works and the published site does not, which is the wider question of an app that runs on your laptop and fails on the live URL.

A test key where a live key is required

Stripe names both halves of this. secret_key_required reads “The API key provided is a publishable key, but a secret key is required…”, which is the wrong kind of key rather than the wrong value. And livemode_mismatch reads “Test and live mode API keys, requests, and objects are only available within the corresponding mode”, which is the sk_test_ and sk_live_ split doing what it is designed to do. A test key in a live checkout is refused on purpose.

Moving a whole Stripe integration from testing to live is more than the key: prices, the webhook and its signing secret all move too. Here, the key is the only thing in question.

Check it yourself before you pay anyone

Most of this ends with a value that needs re-entering, and re-entering a value is not work worth paying for. The order below is the whole check, and none of it requires reading code.

  1. 01 Copy the exact error string and find its row in the table above. If the string names a vendor or carries a vendor-shaped code, the rest of the check applies to that one service and you can ignore the other four keys entirely.
  2. 02 Read the status number. 401 means go on to step three. 429 means the account behind the key has run out of credit or hit a spend cap, and no amount of re-entering the key will change that. 403 means the key is real but restricted, suspended, or pointed at something it is not allowed to touch.
  3. 03 Open the one place your builder keeps that one key: Secrets under the Cloud tab in Lovable, the Secrets tool in Replit, Dashboard then Secrets in Base44, or your own Supabase dashboard if the project is on an external backend.
  4. 04 Ask whether the key was changed. Check the vendor dashboard for a key created or rotated in the last few days, and remember that a security scanner or a teammate can rotate one without telling you.
  5. 05 Ask whether the account behind the key still has money. This is a separate tab in a separate dashboard and it answers the 429 case in about fifteen seconds.
  6. 06 Ask which copy of the app you are looking at. A preview and a published site can hold different values, and a key that is right in one is not automatically present in the other.
  7. 07 Replace the value with a fresh copy from the vendor, save it, and reload the failing page. On builders where secrets are write-only, this is the only way to compare the two values, so treat it as the step itself.

That check ends in one of three places. The key was stale and is now fine. The account is out of money, which is a decision rather than a repair. Or the key checks out, the string is unchanged, and the app is still broken.

The third one is the honest reason to get a second pair of eyes on it, because a valid key that still fails means the fault was never the key. At that point the per-builder fix ladders start further back than this page does: the Lovable fix ladder and the Replit version both begin with what changed, not with what is refusing you.

Common questions about an invalid API key

Why did my API key stop working overnight?

Usually because something on the vendor’s side changed while you slept. A key was rotated, a free trial ended, a prepaid balance hit zero, or a spend cap you set months ago was reached. None of those touch your code, which is why the app looks identical and behaves differently. Read the status number first: a 401 points at the key itself, and a 429 points at the account behind it.

Is an invalid API key the same as running out of credits?

No, and the vendors code them differently. OpenAI returns 401 for a bad key and 429 for an empty balance, with credit_balance_exhausted described as “Your organization has no prepaid credits remaining.” A key that is refused and an account with nothing left to spend need completely different actions, and replacing the key when the real problem is the balance wastes an afternoon.

Can I look up the key I already saved?

On Lovable, no. Its documentation says secrets are write-only and that after you save one, “its value can never be viewed again in Lovable, only replaced or deleted.” Other builders vary, but the safe assumption everywhere is that you cannot read a saved secret back. The way to be certain about a value is to copy a fresh one from the vendor and overwrite what is there.

Does an invalid API key mean somebody got into my account?

Almost never. The ordinary causes are rotation, expiry, an empty balance and the wrong environment, in roughly that order. A key being refused is the system working. If you do have reason to think a key was exposed and not simply expired, you are answering a different question, and rotating the key is only the first part of it.

Do I need a new key when I publish, or does the preview key carry over?

It depends on where the value is stored, and this is worth checking instead of assuming. Some builders inject the same secret into both the working copy and the published app, and some treat them as separate environments with separate values. Stripe adds its own version of this, since a test key is refused outright in live mode by design.

The general shape of the difference is covered in which values travel to the published app and which do not.

Where is the key supposed to live so it is not in the browser?

In your builder’s secret store, where it is injected into server-side code at run time. Lovable’s documentation describes secrets as encrypted, injected into Edge Functions automatically, and never reaching the browser. Replit’s arrive as environment variables in the running app. The rule underneath all of them is the same: a key that a visitor can read in the page source is a key anyone can use.

Why does one part of my app work and another part fail?

Because each part is talking to a different service with a different key. The checkout uses the payments key, the map uses the maps key, the sign-in and most of the screen use the database key, and the AI feature uses whichever AI key was wired in. One being refused takes down exactly one feature. This is the fastest way to name the failing key without reading a single line of code.

Do I need a developer to fix this?

Usually not. The commonest outcome of the check above is a value that needs pasting in again, in a panel your builder already gives you. Where it does turn into real work is when the key is confirmed good and the app is still broken, or when a rotation broke several connected things at once and they now have to be brought back in the right order.