Where Do API Keys Belong When Your Site Calls an API?
Where Do API Keys Belong When Your Marketing Site Calls an API?
Only in the browser if the vendor says that specific key is safe there, and never otherwise. Every other key belongs on a server or in a secrets vault. There is no way to hide a key in client-side code: minifying it, base64 encoding it, or fetching it at runtime all leave it readable to anyone who opens the network tab.
This comes up on almost every marketing site we build, because modern sites talk to real services. A map, a search index, a form handler, a payment flow, an analytics endpoint, a chat widget. Each one arrives with a key and no explanation of which kind it is.
Here is how to tell the difference, using what the vendors themselves publish.
Are Some Keys Genuinely Safe in the Browser?
Yes, and vendors label them. Stripe's documentation has a column titled "Safe to expose" and marks exactly one key type Yes: the publishable key, prefixed pk_. Stripe describes it as the key "for Stripe.js, Elements, and mobile SDKs," which "can identify your account and create tokens or PaymentMethods from payment details, but it can't perform sensitive operations such as creating charges or reading account data."
The design is what makes it safe, not the obscurity. A publishable key is scoped so narrowly that possessing it gains an attacker almost nothing. Stripe is explicit about the boundary: "Only publishable keys are safe to expose outside your application's backend."
So the first question for any new key is not "how do I protect this" but "which kind is it." If the vendor does not say a key is safe for client-side use, treat it as a secret.
What Makes a Public Key Safe if Anyone Can Read It?
A second mechanism that constrains what the key can do. Supabase is a clear illustration because both keys look the same from the outside.
Supabase's documentation says its publishable key is "Safe to expose online: web page, mobile or desktop app, GitHub actions, CLIs, source code," while secret keys should "Only use in backend components." The reason the public one is safe is the database policy layer: "Write your Row Level Security policies for the anon and authenticated roles. Policies never apply to a secret key, because service_role has the BYPASSRLS attribute."
And the warning is unambiguous: "A secret key bypasses every Row Level Security policy you have. Never put one in a browser, a shipped application, or source control."
That is the general pattern. A browser-safe key is safe because the server enforces rules regardless of who holds the key. If there are no rules on the server side, no key is safe in the browser, whatever it is called.
What Do You Do With a Key That Must Be Public but Costs Money?
Restrict it, because a public key with a billing meter attached is a public meter. Google's Maps Platform security guidance is blunt about the exposure side, recommending "never exposing them directly in client-side code" and that keys be "stored securely outside your application's source code."
For keys that unavoidably ship to a browser, Google offers restriction rather than concealment. It documents four application restriction types: websites by referrer URL with wildcard support, IP addresses in IPv4, IPv6 or CIDR form for server-side use, Android apps by package name and SHA-1 signing certificate fingerprint, and iOS apps by bundle identifier. Separately, API restrictions limit a key to specific APIs.
Google also recommends one key per application, noting you can create up to 300 keys per project, so that a single compromise has a small blast radius. And it advises caution on rotation, treating it as a last resort after restriction, because old and new keys remain active during migration.
Where Should Server-Side Keys Actually Live?
In a secrets vault, or failing that an environment variable, and never in the repository. Stripe's list is worth following verbatim: store sensitive keys in a secrets vault provided by your hosting platform, and if you cannot, use environment variables. "Don't put keys in source code or configuration files checked into version control." Do not share keys over email, chat, or other unencrypted channels.
The version control rule is the one that gets broken most, usually by accident, and it is the hardest to undo. A key committed once lives in the history even after you delete the line, so the fix is always rotation rather than editing.
On our own builds the discipline is boring and absolute: no key literal in any file the build reads, keys supplied by the platform at build or run time, and a scan in the pipeline that fails on anything that looks like a credential. It is the same posture we set out in our website security guide.
What If a Vendor Only Gives You One Secret Key?
Put a thin server endpoint in front of it. This is the standard fix and it takes an afternoon. Your browser calls your own endpoint, your endpoint holds the secret and calls the vendor, and the key never leaves your infrastructure.
The trap is building that proxy with no controls, which converts a private key into a public one with extra steps. A useful proxy needs at least three things: a check on where the request came from, a rate limit, and a restriction on which vendor operations it will perform. If your endpoint will forward any request body to the vendor, you have published the key.
Rate limiting matters more than people expect here, because an unauthenticated proxy is an invitation to burn your quota. We went through the options in rate limiting a website.
Can You Reduce the Permissions Instead of Hiding the Key?
Often yes, and it is the better lever. Stripe now recommends this as the default: restricted API keys, prefixed rk_, are keys "with permissions you control," and Stripe says of unrestricted secret keys, "Because you can't limit their permissions, we don't recommend using secret keys for new use cases, and for existing integrations, we recommend migrating secret key usage to RAKs."
That is a meaningful change in posture from a major vendor. The advice is no longer just protect the key, it is stop issuing keys that can do everything. Create as many narrow keys as you have jobs.
Stripe goes further with access policies, which it recommends configuring on all live mode keys, restricting use by IP address or CIDR range, or by autonomous system number, country and threat category. If a request arrives with a key from somewhere it should not be, Stripe blocks it and tells you.
How Should You Handle Keys for AI Agents?
With narrower permissions and an approval step, not with a shared secret key. Stripe has built this in: when creating a restricted key you can tag it as belonging to an autonomous agent, and Stripe's documentation says agent-tagged keys "are automatically subject to approval rules, which require a designated reviewer to approve sensitive actions before they take effect," with default rules covering payouts, refunds and account configuration changes.
Stripe's stated rationale is worth quoting because it is unusually candid: the approval rules "act as a safeguard against agent mistakes, unexpected behavior, and hallucinations."
The general lesson applies even where a vendor has not built this. If an automation holds a credential, give it the smallest possible key, log every call, and require a human confirmation for anything irreversible.
What Should You Do When a Key Leaks?
Rotate it, then work out how it leaked. Stripe's guidance lists the triggers: a key lost and unrecoverable, a key compromised, a team member with access leaving or changing roles, or a policy interval.
Rotating safely is a process, not a button. Stripe documents a grace period where "both the old and new keys work for up to 7 days," advises rolling the new key out to a small subset of servers first while watching logs, and says to check the old key's request logs and expire it "only after its request volume has been at zero for a few hours or days."
Write that sequence down before you need it. The reason leaked keys stay live for weeks is almost never that nobody cared. It is that nobody knew which services used the key, so nobody was willing to revoke it.
What Is the Short Version for a Marketing Site?
Four rules cover nearly every case. First, only ship a key to the browser if its vendor explicitly labels that key type safe to expose. Second, restrict every public key by referrer or domain and to the specific APIs it needs. Third, put anything else behind your own endpoint, with origin checks and a rate limit. Fourth, keep every secret in a vault or environment variable, never in the repository, and know in advance which services use each one.
None of this is difficult. It goes wrong because keys arrive one at a time, during a build, when somebody is trying to ship a feature, and the vendor's documentation page about key types is the one page nobody reads. Reading it takes ten minutes and it is the highest-return ten minutes in the whole integration, especially for the form and widget endpoints we covered in running forms without a backend.
If you want someone to audit what your site is currently exposing, we do that as part of every build and we are happy to do it as a one-off. You can reach us at phoenix.studio.
Want a site that performs like this?
Tell us about your project. We will come back with a clear next step, no pressure.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
Have a project like this?
Tell us where you want to go. We'll tell you how we'd get you there.