Flux Tech Logo

The API Key Mistake Every Developer Makes (And How I Fixed It)





 


The first time I got the email from Anthropic saying my usage had spiked overnight, my stomach dropped. Not because something cool had happened. Because I hadn't done anything. Someone else had. And the key I left sitting in my JavaScript code — right there in plain sight, const apiKey = "sk-ant-..." — was the reason.

I had done what most people do when they first build something with an AI model API. I got excited. I had a working prototype. It called Claude, it returned a response, it felt like magic. I deployed it on GitHub Pages, posted the link on Telegram, went to sleep. By morning, someone had scraped the key from the source code and was running their own prompts on my bill.

This is not a niche mistake made by beginners. Senior developers do this. People who've been coding for years do this. I did this. The thing is, when you're building in the browser — no backend, no server, just HTML and JavaScript because you want something lightweight and fast and free to host — you're working in the most exposed environment imaginable. Every line of your code is public. The browser reads it, the user reads it, the scrapers read it. There is no privacy in frontend JavaScript. And yet we keep putting secrets there.

Let me tell you exactly what I learned, what I changed, and how you can protect your API key today — even if you're still running everything on the frontend with no backend at all.


The Analytical Complication: "Just Don't Put It in the Frontend" Is Useless Advice

Every forum answer says the same thing. Every Stack Overflow thread ends with: "You shouldn't be calling the API from the frontend. Use a backend." And technically, yes. That's correct. A backend acts as a proxy — the user never sees the key, requests go through your server, you have full control. Perfect solution. Also completely useless for most indie builders who are trying to ship something fast, free, and without maintaining a Node server or paying for a hosted API layer.

Here's the real tension: the tools that make AI-powered apps accessible — GitHub Pages, Netlify, Vercel static sites, Blogger-hosted tools — are all frontend. No server. No environment variables. No secrets vault. The architecture that protects your API key requires infrastructure that most people building solo projects specifically chose to avoid. Telling someone "use a backend" when they're deploying on GitHub Pages is like telling someone to "just buy a bigger house" when they ask how to organize a small apartment. Accurate. Unhelpful.

So what do you actually do?

The honest answer is layered. You can't make a frontend-only app perfectly secure. But you can make it expensive enough to abuse that nobody bothers. You can rate limit. You can scope the key. You can monitor usage. You can build a kill switch. None of these replace a backend, but together they transform your exposure from "open fire hydrant" to "guarded tap." That's the goal — not perfection, but enough friction that your key isn't worth stealing.

The people who get burned aren't usually targeted. They're opportunistic victims. A scraper finds a GitHub repo, pulls the key, sells it or uses it. If your app costs ten minutes of effort to abuse, most scrapers move on. That's not nothing. That's your entire defense model when you're working without a backend.


The Human Element: What It Actually Feels Like to Build This Right

When I rebuilt my AI tools after the incident, I wasn't thinking about architecture. I was thinking about control. I wanted a dashboard — something I could look at and see what was happening. Not because I'm a security engineer. Because the feeling of watching your API costs climb for someone else's usage is a specific kind of violation. You built something. Someone took it. And you had no idea until the bill arrived.

The first thing I added was a rate limiter. In the browser, without a backend, this means using localStorage to track how many requests a user has made in a given window. Something like: store a timestamp array, check the count before each request, block if they've hit the limit. It's not foolproof — anyone can clear their localStorage — but it catches casual abuse. It catches the person who opened your tool, thought "oh this is free," and started running a hundred prompts in a row. Most of the damage comes from that kind of lazy, opportunistic use. A five-requests-per-minute limit stops it cold.

Here's what that actually looks like in code:

javascript
function checkRateLimit() {
  const now = Date.now();
  const windowMs = 60 * 1000; // 1 minute
  const maxRequests = 5;
  
  let timestamps = JSON.parse(localStorage.getItem('req_timestamps') || '[]');
  timestamps = timestamps.filter(t => now - t < windowMs);
  
  if (timestamps.length >= maxRequests) {
    return false; // blocked
  }
  
  timestamps.push(now);
  localStorage.setItem('req_timestamps', JSON.stringify(timestamps));
  return true; // allowed
}

Five lines of logic that sit between your user and your bill. Not elegant, not unbreakable, but real. It does something.

The second thing I added was a kill switch. This one changed how I think about deploying tools entirely. The idea is simple: before every API call, your app checks a remote flag — a JSON file on GitHub, a value in a Firebase Realtime Database, a simple endpoint — and if the flag says "disabled," the app stops making requests. You flip the flag from your phone, the tool goes dark in seconds, no server required.

My implementation uses a public GitHub raw file. The app fetches a one-line JSON — {"active": true} — on load. If it's true, requests are allowed. The moment I change it to false and push the commit, every running instance of the tool stops calling the API within the next page refresh. It's not instantaneous, but it's close enough. If I see unusual usage in my Anthropic dashboard at 2am, I can disable the tool from my phone in under a minute.

javascript
async function isToolActive() {
  try {
    const res = await fetch('https://raw.githubusercontent.com/youruser/yourrepo/main/config.json');
    const config = await res.json();
    return config.active === true;
  } catch {
    return false; // fail closed — if the check fails, block requests
  }
}

Notice the catch. If the config fetch fails for any reason, the function returns false. The tool blocks requests. This is called "failing closed" — when you don't know the state, you choose safety over availability. That single decision is the difference between a tool that gets silently abused during an outage and one that shuts down gracefully.

The third layer is key scoping. Every AI API provider I've used has some form of usage limits or key restrictions you can configure on the dashboard. Set a monthly spend cap. Set an alert at 50% of that cap. These are not technical solutions — they're administrative ones — but they're the easiest thing you can do right now, before you touch a line of code. Log into your Anthropic console. Set a hard limit. You cannot be billed beyond it. This alone would have saved me.

There's something uncomfortable in all of this that I want to name directly: when you build AI tools for the public using your own API key, you are essentially subsidizing everyone who uses them. You're the one with the account. You're the one who agreed to the terms. You're the one on the hook. Every free AI tool on the internet that doesn't have a backend is either burning through its creator's credits or using a key so restricted it barely functions. There's no magic. There's no free API. There's just someone, somewhere, paying for it — and if you're not careful, that someone is you, paying for someone else's use.

Building in public is beautiful. Shipping fast and free is one of the best things about the modern web. But you have to build the walls before you open the door.


The Parting Shot

Here's what I know now that I didn't know when I first exposed that key: the mistake isn't stupidity. It's excitement. You built something that works and you want people to see it. The gap between "this works locally" and "this is safe to deploy" is invisible when you're moving fast. Nobody warns you. The tutorials don't cover it. The documentation shows you how to call the API, not how to protect the call.

But the lesson goes deeper than API keys. Every time you build something in the open — a tool, a blog, a product — you're creating an attack surface. Not because the internet is evil, but because it's large. Most people won't do anything with your exposed key. A small number will. That small number is enough to ruin your month.

The rate limiter, the kill switch, the spend cap — none of these are the backend solution the forums keep telling you to build. They're the pragmatic middle ground for builders who move fast and ship lean. They're the difference between sleeping fine and waking up to an email you don't want.

I still build frontend-only tools. I still deploy on GitHub Pages. I still use my own API key. But now there's a config file that can shut everything down in thirty seconds, a rate limiter that blocks the lazy abusers, and a spend cap that means the worst case is a minor billing alert, not a major incident.

Your API key is not a password you can change and move on. It's a payment method. Treat it like one.

Go check your dashboard right now. Set that spend limit. Push that config file. You'll sleep better — and your bill will thank you.


Want more tools and strategies like this? Browse what I've been building:

👉 AI Tools Hub — fikrago.com/p/tools.html 👉 Digital Market — fikrago.com/p/digital-market.html 👉 Products — fikrago.com/p/products.html