API Keys — public vs secret
API keys are how services identify your application. Get this wrong and you'll be sending strangers your bill. Get it right and your app stays secure even if your frontend code is fully public.
What is an API key?
An API key is a string of characters that identifies your application to a third-party service. When you call the OpenAI API, for example, you include your key in the request header. OpenAI uses that key to know who you are, track your usage, and bill your account.
// The key goes in the Authorization header
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ /* ... */ })
});Notice process.env.OPENAI_API_KEY — the key comes from an environment variable, not hardcoded into the source code. This is the only correct pattern.
Public keys vs secret keys
Many services issue two kinds of keys for the same account. They are not interchangeable:
✓ Public Key
Designed to be embedded in browser-facing code. Has very limited permissions — usually can only do one specific thing, like tokenize a payment card or identify your account to a frontend SDK.
Examples: Stripe publishable key (pk_live_...), Supabase anon key, Cloudflare Turnstile site key, Google Maps embed key, Segment write key.
Safe to commit to GitHub, safe in frontend JS.
✗ Secret Key
Full access to your account. Can create charges, access all data, modify settings, delete records. Never designed to be seen by anyone other than your server.
Examples: Stripe secret key (sk_live_...), OpenAI API key, Supabase service role key, Resend API key, any key starting with sk_.
Never in browser JS. Never in Git. Server-side only.
The one rule
Never put a secret API key in client-side JavaScript.
Not in React. Not in Vue. Not in a const at the top of a script. Not in a comment. Not "just temporarily." Not "because it's a side project." Never.
Here's why. When you build a React app, Vite bundles all your JavaScript into files users can download. Anyone who visits your site can press F12, open the Sources tab, and search for sk_live_. Your key is sitting there, plain text, readable in seconds.
// ⌠Anyone who visits your site can read this
const apiKey = 'sk-proj-abc123def456';
const result = await fetch('https://api.openai.com/v1/chat/completions', {
headers: { 'Authorization': `Bearer ${apiKey}` }
});// ✅ Browser calls YOUR backend
// YOUR backend calls OpenAI with the secret key
const result = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ message })
});
// Server-side (/api/chat route in Node.js):
// const openaiRes = await fetch('...openai...', {
// headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` }
// });Restricted keys — least privilege
Many services let you create restricted keys that only have permission to do specific things. This is called the principle of least privilege — a key should only have as much access as it actually needs.
If this key leaks, the attacker has access to your entire account — billing, data, settings, everything. Avoid this for production applications.
A key that can only read blog posts from your CMS, for example. If it leaks, an attacker can read your public content — but can't write, delete, or access other resources.
Different API keys for development and production. If your dev key leaks, your production data stays safe.
Some services let you restrict a key to only work from specific IP addresses — your server's IP. Even if the key leaks, it can't be used from anywhere else.
Rotation and revocation
Rotation means replacing a key before it expires or gets compromised — a proactive security practice. Revocation means immediately invalidating a key because it's been compromised.
Both follow the same process: generate a new key in the service dashboard, update your environment variables (on Railway, Vercel, Cloudflare, etc.), deploy, then revoke the old key.
How to tell if a key is public or secret
When you get a new API key from any service, ask yourself:
- Does the service documentation say this key is for "client-side" or "browser" use? → Public, probably safe
- Does the key prefix suggest it — pk_ for publishable, sk_ for secret, service_role vs anon? → Follow the naming
- If someone else gets this key, what can they do? → If the answer includes "spend money," "access user data," or "modify your account," it's a secret
- When in doubt, treat it as secret