Your AI-built app works on your laptop. Shipping it is the other 30%
Built an app with Claude Code, Cursor or Lovable? What stands between it and a live app: keys, sign-in, App Review, subscriptions, analytics, deep links.
If you built an app with Claude Code, Cursor or Lovable and it runs fine on your laptop, you are most of the way there. What's left is rarely a missing feature. It's the work that only appears once strangers, app stores, money and the occasional attacker touch your app: API keys anyone can lift out of the binary, sign-in paths you never tried, App Review rules, subscriptions that don't actually charge, analytics that counts one person three times, and deep links that open the app and then do nothing.
We call it the 30% because that's how it feels from the inside, not because we measured it. We're Lumio Studio, a small software studio in Istanbul. We ship our own apps (Kalo, Runlock, ScrollShelf, LittleChats and Muslim SuperApp are on the App Store), and most of our code is written with an AI assistant in the loop. Every example below happened to us. We wrote each one down the day we found it, so these are our notes, not a list made up for this post.
Why the demo lies to you
An AI assistant writes code for the path you show it. You test on a fresh install, after a cold launch, signed in as yourself, on a phone that has never asked for a refund. That path works. Production is everything around it, and the model has no reason to think about it unless you ask.
So the useful question isn't "does it work?". It's "what happens on the second try, for someone who isn't me?"
1. The API key inside your app is public
Anything shipped in an APK or IPA can be extracted. Firebase creates one API key per platform, and by default those keys carry no app restriction at all.
In September 2026 someone pulled the unrestricted Android key out of Kalo's public APK and used it to call Vertex AI image models. Kalo is a calorie counter. It never generates images. The calls ran at 150 to 270 successful requests a day and cost us around ₺1,900 before we noticed, and we noticed because of the bill. App Check wasn't enforced, so nothing was blocked. Audit logs were off, so we couldn't even see who it was.
One detail cost us extra time. For Firebase AI Logic, App Check is enforced on the service id firebaseml.googleapis.com. Trying firebasevertexai fails, and that failure is easy to misread as "already covered".
Before your first release:
- Restrict every key: the Android key to your package name and SHA-1, the iOS key to your bundle ID, the browser key to your domains and only the APIs it needs.
- Turn on App Check and enforce it on every service the app calls.
- Put a budget alert on the cloud project, so the bill isn't your monitoring.
If your app reaches a model through your own backend, there's a sibling trap. Our receipt-reading app had a proxy that looked deployed and healthy for days: health check green, URL answering. It had never completed a single model call, because the server's service account had no permission to call Vertex. We found it on TestFlight, after the Android version was already live on Google Play. Our rule since then: a deploy is not the gate. One real request that comes back 200 with model output is.
2. Sign-in breaks on the second path
Most auth bugs we've shipped weren't in the first sign-in. They were in the paths next to it.
Kalo signs people in anonymously at launch and later links Apple or Google onto that anonymous account. After an in-app sign-out, the anonymous user is gone until the next launch. Apple and Google sign-in assumed it was there, threw noCurrentUser, and the user saw "Couldn't sign in" with no way out except force-quitting. Email sign-in already had a fallback. The other two didn't. You only catch this by signing out and back in without relaunching, which is exactly the test nobody runs.
The second one is Sign in with Apple. If linking an Apple credential fails because that Apple ID already has an account, the obvious recovery is to sign in with the same credential. It compiles, and it always fails: an Apple credential is single-use against Firebase, and the error (17094, a "duplicate credential" nonce message) points you at the wrong layer. The fix is a fresh Apple sign-in round trip with a new nonce.
Neither bug shows up in tests that mock sign-in as succeeding. They only appear against the real backend.
3. App Review checks things your simulator doesn't
Buttons that only work on the text
In SwiftUI, a button whose label uses padding or .frame(maxWidth: .infinity) only responds to taps on the text glyphs unless you add .contentShape(...). It looks like a full-width button. The reviewer taps near the edge, nothing happens, and the rejection says the button "did not trigger any action". On iPad this is an easy rejection, and every rejection costs a review cycle.
Concept rejections need a reply, not just a new build
One of our apps was rejected under 4.3(a), Apple's spam guideline. We changed the recording model, the name and the screenshots, then resubmitted through the API. Two days later the same rejection came back, word for word. The reply we had drafted for the Resolution Center was still sitting in the repo, unsent. A new binary answers a crash. A judgement about what the app is needs a written answer first, then the resubmission.
No accounts still means data deletion
Apple's account deletion rule doesn't apply to an app without accounts. GDPR, KVKK and Google Play's Data safety form still apply to personal data, and an analytics ID or a subscription customer ID counts. Ship a "Delete all my data" row in Settings.
4. Subscriptions: the paywall that charges nothing
App Store Connect shows a ready subscription as READY_TO_SUBMIT. That means it may be added to a submission, not that it's in one. We sent an app to review with a paywall whose products weren't attached. If the reviewer notices, that's a rejection. If they don't, the paywall goes live selling nothing. A first subscription group has to go in with the group and every product, added by hand in the console, because the API can't attach them.
Then comes everything after the sale. Kalo sold on Google Play for months without real-time developer notifications switched on. A user got a refund, the refund never reached Adapty (our subscription SDK), and on paper his premium access ran until 2027. Renewals and cancellations were just as invisible.
The worst one: an annual subscriber on Android paid, then had every request refused for five days. The purchase webhook arrived before the app had told Adapty who he was, so our backend rejected the event and kept nothing. When we looked, 12 of 48 webhook deliveries over 60 days had returned the same error. He found the bug for us by asking for a refund. Now an unattributed purchase is stored instead of dropped, the app can claim it right after paying, and a scheduled job retries whatever is left.
5. Analytics that counts one person as three
PostHog, like most analytics SDKs, uses a random anonymous ID until you call identify. If the first event fires before that, each cold launch can become a new person. Funnels break, retention drops appear that aren't real, and you only notice when one tester has four IDs in an hour. Identify with a stable ID (the Firebase uid, or a UUID you persist) before the first event, and turn off autocapture if it fires events before you get the chance.
A quiet dashboard isn't always a calm one. Three Kalo Android releases were built from a checkout that didn't have the gitignored google-services.json. The build passed with a warning, and every install crashed at launch for about ten days. PostHog just went silent, because a crashing app sends nothing, while a paid ad campaign kept sending people to it. We found out from a user's message and Play vitals. Now the release build fails when that file is missing, and we read Play vitals after every release.
6. Deep links that open the app and stop
We built a win-back offer for Kalo across six systems: both stores, Adapty, PostHog, our backend and both apps. Every layer was checked and green, with dozens of passing tests. The feature didn't work. The push arrived, the user tapped, the app opened, and the deep link router fell through to its default case, because nobody had added the paywall route.
The tests were written in the same pass as the code, so they shared its blind spot. We now call an integration done only when we can name one real input we followed from start to finish: this push, through these files, opening that screen.
A prompt to run before you call anything done
Paste this into Claude Code or Cursor at the end of a feature, with the feature filled in at the top:
Feature: <what it does, in one sentence a user would say>
Before I call this done, review it as a skeptic who did not write it:
1. Pick one real input (a tap, a push payload, a webhook, a deep link) and trace it
file by file to what the user sees. Show the file paths. If a step has no code, say so.
2. List every path besides the happy one: second attempt, signed out, offline,
after a refund, after a reinstall, on a brand new account.
3. List every key, token and config file this depends on, and say whether a
release build fails loudly when one is missing.
4. Which analytics event proves this works in production, and which user ID
is it sent with?
5. What did the tests mock that can only fail against the real backend?
Do not fix anything yet. Give me the list.
Launch-week checklist
- Every API key restricted by package and SHA-1, bundle ID or domain, with App Check enforced.
- One real production request through every AI or paid endpoint, returning 200.
- Sign out and back in with each provider, without relaunching the app.
- Every subscription product attached to the review submission.
- Refund and renewal notifications from both stores reaching your subscription backend.
- Analytics identity set before the first event, so one tester shows up as one person.
- Release builds that fail when a config file is missing, and crash reports read after each release.
- One deep link and one push traced from tap to screen.
- A "Delete all my data" path, even with no accounts.
What we still get wrong
We learned every item here by getting it wrong, usually right after a passing test told us things were fine. The list keeps growing, and we won't pretend the next launch will be clean. What changed is that we now run these checks before a release instead of after a user writes in.
The long version, with more cases and the exact checks we run, is a free ebook: Shipping an app with AI: the 30% nobody tells you. If you're still at the idea stage, the app project planner turns your idea into a rough scope and estimate before you write the first prompt.
Want it built? Tell us about your project ↗