The half nobody screenshots
Two posts crossed my feed the same week and they rhyme. One is the Mu3ic update: the dev gave away 20 activation codes to the first 20 people who dropped an email, and spelled out the rules without blinking. One code, one device, no rebinding, dead after 24 hours, no replacements. Redeem path: Settings → About → Pro → paste the code. The other is a builder who, since March, let AI write a project end to end — 20k commits, 350-plus TestFlight builds, over 1.2 million lines across Go, Rust, Swift and Kotlin — and said he hasn't read a line of it in months. His one admitted gap, in his own words: he still hasn't made money.
Those two things are the same story from different ends. AI will hand you the app. What it quietly skips is the unglamorous plumbing between "someone wants Pro" and "this install is now Pro." Issue a code, bind it to a device, make it expire, check it on redeem. Nobody screenshots that. But that's a big part of the distance between a cool build and a build that collects.
What the Mu3ic flow actually asks of you
Strip the Mu3ic giveaway down and it's four behaviors, each with real state behind it:
- Mint a batch of codes you can hand out.
- Bind one code to exactly one device on first use, and refuse to move it.
- Enforce an expiry — 24 hours for the giveaway, or a permanent unlock for the paid buyout.
- Validate on redeem, from inside the client.
There's a fifth wrinkle that's easy to miss: Mu3ic also lets an activated user view and copy their code, and explains how to carry Pro to a new phone. That means the "no rebinding" rule isn't absolute — it's a default you sometimes override by hand. Which tells you exactly where the state has to live and who needs to be able to reach into it.
Three small pieces, and where each one sits
The durable core here isn't fancy. It's a ledger, a check, and a page.
The ledger is a SQLite table. Columns roughly: the code, its status, a device id that's null until first redeem, issued-at, expires-at, redeemed-at. The reason I reach for SQLite over anything heavier is the day a user emails "I switched phones." You open the row in the SQLite editor, clear the device id, done. That hand-correctable escape hatch is the whole point for a solo operator — the rules run on their own, but you can still step in.
The check is a Python validation endpoint. You run Python online and expose it as a hosted API the client calls on redeem. The logic is short: look the code up, reject if it's missing or past expires-at, then handle the device binding — if device id is null, write the incoming one and flip status to redeemed; if it's already set, pass only when it matches and deny otherwise. Return a plain allow or deny. That's it. No framework ceremony required.
The redeem surface is a thin HTML page. If you're handing codes out as links, or you want a simple paste-a-code box outside the app, you run HTML online and post to the endpoint. For an in-app flow the page is optional — the app's own "enter code" screen calls the same endpoint — but having a web redeem page is handy for giveaways and for people who aren't in the app yet.
Where the boundary actually is
Be clear about what this does and doesn't cover, because the confusion costs you later.
VicroCode hosts the backend, not the mobile app. The Android APK, the iOS build, and getting either into users' hands all live elsewhere — building and distributing the client is outside the platform's scope. What sits here is the ledger and the check the client talks to.
Mu3ic mailed those 20 codes out. Email delivery isn't one of the confirmed capabilities, so treat it as a boundary: you generate and export codes here, but the sending happens on your own channel. Same with taking payment for the buyout — don't assume a card processor. The platform's monetization is for projects you publish on it, not a payment rail bolted onto an external app's Pro tier.
And a flag worth saying out loud: an activation endpoint exposed to the open internet with no auth is just a public code-checker anyone can probe. At minimum put a shared secret or a signed request between client and endpoint, so a stranger with the URL can't sit there guessing codes. The device-bind and expiry rules protect the user's seat; they don't protect the endpoint itself.
Why the boring version wins
The builder who hasn't made money yet is right that the technical bar has dropped. Spinning up the app is the easy part now. The money gap is partly this ledger — the part that quietly tracks who's entitled to what and survives contact with real users, who reinstall, switch phones, miss the expiry window, and paste the wrong code.
A SQLite table you can open and fix beats a rigid system you can't reach into, precisely because solo support is all exceptions. Let the expiry and one-device rule run automatically, keep the editor as your manual override, and keep the check small enough that you actually understand it. I've made no claims here about conversion or revenue — those stay unverified, and they depend on your product, not the plumbing. But the plumbing is the half AI leaves on the floor, and it's cheap to build right.