Skip to content
Fixing API errors

Why your API key suddenly stopped working

The key you have not touched in weeks started returning 401. Here are the causes, ordered by how often they turn out to be the real one, and how to confirm each in under a minute.

SandyPublished 6 min read
green and silver padlock on yellow surface, illustrating why your api key suddenly stopped working
Photo by FlyD on Unsplash.

Nothing changed. You did not touch the integration. This morning it returns:

{ "error": "Invalid API key" }

Keys do not decay. Something changed, and it is usually one of a small set of things. Here they are in the order they actually turn out to be the cause, with a quick way to confirm each.

Short answer

Run the key through curl first. If curl works and your app does not, the key is fine and your code is not sending what you think it is, usually because of a missing environment variable or a trailing newline. If curl fails too, the key itself was rotated, revoked, expired, quota-exhausted, or auto-revoked after being committed to a public repository.

Start here: isolate the key from your code

One command splits the problem in half:

curl -s -o /dev/null -w "%{http_code}\n" \
  -H "Authorization: Bearer $API_KEY" \
  https://api.example.com/v1/me
  • 200 → the key is valid. Skip to Your code is not sending what you think.
  • 401 or 403 → the key is genuinely rejected. Continue below.

Make sure your shell actually has the variable set. echo ${#API_KEY} prints its length, which tells you it is loaded without printing the secret into your scrollback.

The key itself is rejected

1. Someone rotated it

By far the most common cause on a team. A colleague regenerated the key in the provider's dashboard, which instantly invalidates every existing copy. The person who rotated it updated their own environment and forgot the deployment, the CI secret, or the shared staging config.

Check the dashboard for a "last created" timestamp. If it is recent and you did not create it, this is your answer.

2. It was committed and auto-revoked

Push a key to a public repository and it can be dead before you finish reading the push output. GitHub scans public repos for credential patterns and notifies the provider, and many providers revoke immediately.

# Search the entire history, not just the working tree.
git log -p --all -S 'sk_live' -- . | head -40

A .env file that was never in .gitignore is the classic route. So is a committed test fixture, a Postman export, or a screenshot in a README.

3. The free trial ended

Many providers issue a key with full access for a trial period, then silently downgrade it. The key stays valid, and specific endpoints start returning 403 while others keep working. If some calls succeed and others do not, this is likely, and it is a plan problem rather than a key problem.

4. A quota boundary passed

Daily quotas reset on the provider's clock, not yours. A job that runs at 00:30 local time may be landing before or after the reset depending on the season, because daylight saving shifts your offset while UTC stays fixed. An integration that breaks on the last Sunday of March or October is almost always this.

5. The key has an expiry

Some providers issue keys that expire after 90 days or a year. This is increasingly common for security reasons and is usually announced by email to whoever created the key, which may be someone who has since left.

6. Restrictions were tightened

Keys can be scoped to referrers, origins or IP ranges. If your deployment platform changed its egress addresses, or someone added a restriction to tighten security, valid credentials start being refused from the wrong network. The dashboard shows the restrictions; compare them against where your code actually runs.

Your code is not sending what you think

curl worked, the app did not. The key is not reaching the API in the form you expect.

Trailing whitespace

const key = process.env.API_KEY;
console.log(JSON.stringify(key)); // "sk_live_abc123\n"  <- there it is

A newline arrives from reading a file, from a heredoc, from some secret managers, and from pasting into a .env editor that adds one. The header value no longer matches.

const key = process.env.API_KEY?.trim();
if (!key) throw new Error('API_KEY is not set');

Trim once at the boundary and fail loudly when it is missing. A missing key should crash at startup, not produce a confusing 401 an hour later.

The variable is not loaded

console.log('key present:', Boolean(process.env.API_KEY));

Common reasons it is absent:

  • The .env file is not being read, because the framework only loads .env.local, or the process started from a different directory
  • The variable exists in your shell but not in the deployment platform's settings
  • The name differs by one character between environments, such as API_KEY versus APIKEY
  • In Next.js, a client component is reading a variable without the NEXT_PUBLIC_ prefix, which is undefined in the browser by design

The placeholder was never substituted

# The literal string ${API_KEY} is being sent as the credential.
headers:
  Authorization: "Bearer ${API_KEY}"

In a config file that is not processed for interpolation, this is sent verbatim. Log the length: if it matches the placeholder rather than the key, you have found it.

A proxy is stripping the header

Some reverse proxies and CDNs drop or rewrite Authorization. If the request works from your laptop and fails from inside your infrastructure, check what sits between the two.

Confirming the fix, and not repeating it

Once you have a working key, three changes prevent most repeat occurrences.

Validate at startup. Fail immediately with a clear message rather than at the first API call:

lib/env.ts
import { z } from 'zod';
 
const schema = z.object({
  API_KEY: z.string().trim().min(20, 'API_KEY looks truncated'),
});
 
export const env = schema.parse(process.env);

Keep keys out of the browser entirely. A key in client-side code is visible to every visitor in DevTools, and will eventually be scraped and abused until the provider revokes it. Proxy through a route handler so the key stays server-side.

Prefer APIs with no key where a key adds nothing. For prototypes, demos, tutorials and coding exercises, an API that needs no credential cannot have a credential problem.

Browse them in the no-key collection, or the narrower browser-ready collection if you need CORS too.

The one that is nobody's fault

Worth adding because it accounts for a steady trickle of these and looks nothing like the others: the provider rotated their own infrastructure.

A certificate renewal, a change of authentication host, or a migration between identity systems can invalidate tokens that were issued under the old arrangement. Your key is correct, your code is correct, and it stops working anyway. The signature is that it fails for everyone at once and starts working again without anyone changing anything.

The tell is timing. If the failure began at a round time, affects every environment simultaneously, and your last deploy was days earlier, stop looking at your own code. Check the provider's status feed and changelog before spending an afternoon bisecting.

The same reasoning applies to a key that works from one machine and not another. That is almost never the key and almost always the environment around it — a different variable file, a stale value cached in a build, or an allowlist that includes one address and not the other.

The order to work through

CheckCommand or place to lookRules out
Key valid at allcurl with the keyEverything downstream
Recently rotatedProvider dashboard, creation dateTeam rotation
Committed publiclygit log -S across all historyAuto-revocation
WhitespaceJSON.stringify(key)Invisible newline
Variable loadedBoolean(process.env.API_KEY)Environment issues
RestrictionsDashboard key settingsIP and referrer locks

Work down that list and you will land on the cause within a few minutes. The temptation is to regenerate the key immediately, but doing that first destroys the evidence and, if the real problem is a missing environment variable, leaves you with two broken keys instead of one.

Common questions

Why would a working API key stop working on its own?

Keys rarely fail on their own. Something changed: the key was rotated or revoked, a quota reset boundary passed, a trial expired, the key was auto-revoked after being detected in a public repository, or the environment your code reads it from changed.

How do I check whether the key or my code is at fault?

Send the same key with curl from your terminal. If curl succeeds and your application fails, the key is fine and the problem is how your code loads or sends it. If curl fails too, the key itself is the problem.

Can pushing a key to GitHub get it revoked?

Yes. GitHub scans public repositories for credential patterns and notifies the provider, and many providers revoke automatically within minutes. If your key died shortly after a push, check whether it appears anywhere in the repository history.

Why does my key work locally but not in production?

Usually the production environment does not have the variable set, or has an older value. Keys can also be restricted by IP or referrer, which permits your machine but not your deployment platform.

Does a trailing newline really break an API key?

Yes. A newline is part of the header value, so the credential does not match. It is invisible in logs and survives copy and paste, which is why it is worth checking before anything else.

Sources

Written by

Sandy

I build and run this site on my own: the crawler that assembles the catalogue, the checker that probes every listing, and the writing. Before this I built SaveFromInternet and GrabReels, which meant living with other people’s APIs full time — parsers breaking when a platform shipped a change, rate limits arriving without warning, endpoints disappearing overnight. This directory exists because I got tired of free API lists that had never been checked.

APIs mentioned in this article

RandomUser

Test Data

API for generating random user data like names, emails, addresses, and more. Provides JSON, XML, CSV, or YAML objects.

No keyCORSHTTPS

Verified 4 days ago: 100% uptime

View Details

Frankfurter

Currency Exchange

Exchange rates, currency conversion and time series

No keyCORSHTTPS

Verified 4 days ago: 100% uptime

View Details

DummyJSON

Test Data

Fake REST API with products, users, posts, comments, todos and more

No keyCORSHTTPS

Verified 4 days ago: 100% uptime

View Details

Read next