All articles Security

What an API key actually protects

A key is not a password. Understanding the difference is most of understanding how to secure an integration.

Photo: Carl Lender from Sunrise, USA (CC BY 2.0) / Wikimedia Commons

Most people treat an API key like a password: a secret that, if kept, keeps them safe. It is a reasonable instinct and it is wrong in a way that causes real incidents.

A password authenticates a person. An API key identifies an application. The difference matters because an application's key is, in the general case, impossible to keep secret.

If it reaches a browser, it is public

If your key is in a JavaScript bundle, it is public. Minification is not encryption. Obfuscation is not encryption. Environment variables inlined at build time are not encryption — the value ends up in the bundle exactly as if you had typed it there.

Anyone who wants your key opens developer tools, filters the network tab, and reads it off a request. This takes about ninety seconds and requires no skill. We are not describing an attack; we are describing how the web works.

So any security model that depends on a browser-delivered key staying secret is not a security model. It is a hope.

What is protecting you, then

The binding, not the secrecy.

A key locked to yourdomain.com and called from attackersite.com is refused, and it is refused whether or not the attacker knows the key. That is the property that survives your key being public, and it is the one worth building on.

This is why domain locking is not an optional extra in BuildAPI — it is the mechanism. A key without a verified domain is a key protected only by obscurity, and obscurity has a half-life measured in the time it takes someone to look.

It is also why we insist on *verified*. A domain somebody typed into a field is a claim. A domain proved by a DNS record only its operator could place is a fact. If typing were enough, an attacker who lifted your key could bind their own key to your domain, and now they hold a legitimate credential for a site they do not own.

Where secrecy does still matter

Server keys. A key used from your backend can be kept secret, has no domain to be checked against, and can therefore do more.

That is a genuine secret. It belongs in an environment variable read at runtime, not in a repository, not in a config file you commit, and not in a client bundle. We store server keys as a peppered hash, so a copy of our database does not hand anybody a working credential — but that protects you from us being breached, not from you publishing it.

The rule that follows is simple:

If the key can be read by a user, it must be bound to something. If it cannot be read by a user, it may be trusted.

The thing people get wrong in the middle

Mobile apps. A key shipped inside an app binary is not secret — anyone can unpack an APK — but there is no domain to bind it to either. The honest answer is that a mobile app should talk to your own backend, and your backend holds the server key. Putting a client key in a mobile app and calling it secured is the middle ground that protects nobody.

What to do this afternoon

  • Look at every key you have and decide which of the two kinds it is. Most teams have never made that decision explicitly.
  • Make sure every client key has a *verified* domain, not merely a domain typed into a field.
  • Move any server key out of your source tree if it is still there, and rotate it, because a key that has been in a repository is a key that has been read.
  • Add the scope parameter to your calls now, even if the key holds every scope. Adding it later means walking a codebase you have not touched in a year.

None of this requires a rewrite. It requires deciding, per key, which kind it is.

Start building

Lock a key to your domain in about five minutes.

Get started