We were putting QR codes on printed restaurant menus. Every tool we tried was either bloated, expensive, or held the codes hostage.
The one we were using charged €25 a month for "unlimited" dynamic codes. Every scan went through a five-second countdown page with an upsell banner — embarrassing next to a restaurant's actual brand. And when we cancelled the subscription, every code we had ever printed stopped working. Menus, table cards, all of it, dead, because a payment lapsed.
That is the pattern across this market. Free tools serve an ad on every scan. Paid tools hold your printed codes hostage to a recurring charge. Neither is really about the QR code. Both are about the subscription.
So I built Scanoo.
A printed code is not software
A QR code on paper is ink. Once it is on a menu, a sign in a front garden, or a sticker on a shop window, you cannot patch it. The only thing you can still change is where it points.
That is exactly where a dynamic code earns its keep, and exactly why the market holds it hostage: it is the only lever you have left after the printer has been.
So the free tier here makes codes that work, full stop. There is an interstitial on free scans — that is what keeps the tool free — but the codes keep working even if you never pay a cent, and the free tier does not expire. Paying removes the interstitial and adds what people actually ask for: SVG export, file hosting, scan analytics, AI-generated artistic codes. Nine euros a month, no scan limits, cancel any time.
One writer, many readers, and a SQLite file
The backend is a FastAPI service split into separate reader and writer deployments on top of SQLite.
That sounds like asking for trouble, because SQLite and multiple processes are famously a bad match. The split is what fixes it. Reader pods serve API traffic and answer queries directly against the database. Writes never touch a reader's local copy: they go over a Redis stream to a single writer through a command bus, and that writer is the only process that ever mutates the database.
What it buys: reads scale horizontally, deploys go out with no downtime, and there is never more than one writer on that file. You keep SQLite's reliability without the mess that usually comes with it.
It is the same shape as profotograaf — one writer, several readers — but there it is Postgres underneath and the split happens inside a single binary. Here there is a Redis stream in between. Same idea, different scale.
Where it runs
On our own Kubernetes (k3s), on EU servers we operate ourselves. Not a PaaS, not a hyperscaler's managed Kubernetes. Traefik in front, TLS everywhere via Let's Encrypt, and no unencrypted path to the app.
What sits around it is deliberately small. Sign-in goes through our own identity provider, IAMBarn, rather than an auth vendor holding account data. Payments run through Stripe, so card details never touch our servers. The artistic codes come from ControlNet via Replicate. Errors, product analytics and traces go to BugBarn, FunnelBarn and SpanBarn — all self-hosted, no third-party trackers.
Secrets are encrypted with SOPS and age, never committed in plaintext and never logged. A push to main builds and tests automatically; production is a deliberate promotion someone has to press.
Why we run it ourselves
Because we can, and because it is cheaper. That is most of the reasoning. There is no case for routing customer data through a stack of vendors for a product whose main job is to redirect, reliably.
It also makes the promise real. "Your codes keep working" is easy to say when you are sitting on someone else's platform. It means rather more when you hold the servers, the deploys and the backups yourself.
What is next
Everything that ships gets written up on the changelog, including the reasoning behind it rather than just the feature list. That is meant as honesty about what is actually in the product.
I still use it myself for exactly the kind of work that started it: small businesses that need a QR code to just work, with no subscription trap attached.