Skip to content
    Field notes

    Your API Key Is Sitting Inside Your App Right Now

    The most common way a vibe-coded AI feature goes wrong isn't a bug — it's the API key that got written straight into your client bundle, working perfectly for whoever pulls it out.

    3 min read

    There's a particular kind of email you don't want to get. It comes from your AI provider, it arrives at 3am, and it's about usage.

    Not a warning about the app you built. A bill for the app someone else is now running on your key.

    This is the most common way a vibe-coded app goes wrong, and almost nobody sees it coming, because nothing about it looks like a bug. The app works. The feature works. The AI feature you were so pleased with is working beautifully — for everyone who pulled your key out of it.

    Why this happens to AI-built apps specifically

    When you ask a model to add an AI feature to your app, it does the direct thing: it writes the API call, and it puts your key where the call is. In your client. In the app that gets shipped to phones.

    That's not the model being careless. It's the model doing what it was asked, in the only place it can see. It doesn't know you have no backend. It doesn't know you were planning to add one later. It writes working code, and working code with a key in it is still working code.

    The second half of the problem is that nothing tells you. There's no warning, no build error, no review rejection. Your app ships. It works. The key sits in the bundle, and a mobile app bundle is not a secret. Anyone can pull one apart in a few minutes with tools that are free and legal.

    The three that usually go together

    Keys in the client are rarely the only issue, because they come from the same source: code generated feature-first, without the boring layer underneath.

    No rate limiting. Even if your key were safe, one user in a loop can run up a bill you did not budget for. There's no ceiling unless someone builds one, and no prompt asks for one.

    Open database rules. The default rules on a new Firebase project are permissive so you can develop quickly. They're meant to be locked down before launch. An AI writing your data layer works with whatever rules exist, and locking them down is a console task in an interface it can't see. So the reminder never comes.

    No auth on the calls that cost money. If your AI endpoint doesn't check who's calling it, you're not running a product. You're running a free proxy with your credit card behind it.

    Why "just add a backend later" doesn't happen

    Everyone knows the answer here. Keys go server-side, calls get authenticated, usage gets rate limited. None of this is controversial.

    It doesn't happen because of when it lands. It's not a feature, so it doesn't feel urgent while you're building. It becomes urgent at launch, which is exactly when you have the least appetite for infrastructure work, and by then it isn't a small change. It's rewriting the AI layer you already built, plus a server you don't have, plus auth you were going to add anyway.

    So it gets deferred one more week, and the app ships, and the key ships with it.

    What actually prevents this

    The layer has to exist before the feature does. Not as advice you follow, as code that's already in the project when you start.

    That's what ShipAppFast gives you. It's a production-ready Flutter template where the AI plumbing is already built the right way, alongside authentication, payments, the paywall, and push notifications. When you add an AI feature, you're adding it into a structure that already knows where keys belong and who's allowed to call what.

    The difference isn't that you become more disciplined. It's that the shortcut stops being available. You can't put a key in the client when there's already a correct place for it.

    Ship the feature. Don't ship the key.

    shipappfast.site