Build notes · PriceScout

The scan that costs money.

PriceScout lets you photograph a thing and find the cheapest live price for it in the UK. The part everyone asks about is the photograph. The part that actually shaped the product was the invoice.

Live: pricescout.cheap →

The idea is old and obvious: you are stood in a shop holding something, and you want to know whether the price on the label is a good one. Every part of solving that is now cheap except the bit nobody thinks about.

What it’s actually made of

A React front end — Create React App, driven through craco so I could bend the build config without ejecting. Tailwind for layout, Radix for the handful of components where getting keyboard behaviour right by hand is a waste of a day.

The back end is Python. FastAPI, one server.py, with the parts that do real work split out: price_providers.py for the retailer lookups, notify.py for the alerts, og_image.pyfor generating share images. It runs in a container on Render. Photo identification goes to OpenAI’s vision model; price lookup goes to SerpAPI.

That is not an exotic stack and it was not meant to be. I have watched people spend a fortnight choosing a framework for a thing that needed to exist by Friday.

The bit that changed the product

Here is the thing about building on top of somebody else’s API. Every time a user taps the scan button, I am charged. Not metaphorically. There is a counter somewhere and it goes up, and at the end of the month I pay it.

A free tier on a paid API is just a bill with a nice interface.

The first version had no cache. It worked beautifully and it would have bankrupted me at about four hundred users. Two people scanning the same pair of headphones an hour apart triggered two identical paid lookups, for a price that had not moved.

So the real build work — the work that took the longest and appears nowhere in the interface — was a cache layer, a hard monthly spend cap that degrades the service rather than letting the bill run, and a /api/ping endpoint so I can see the usage figures without opening a dashboard. The product only became a product once it could survive being popular.

I think this is the difference between building something as an exercise and building something you own. An exercise ends when it works. A thing you own has to keep working on a Tuesday in November when four hundred people have found it and you are asleep.

Making it an app

The web version has a file input for the photograph. On a phone that is a miserable experience — two taps and a system sheet between the user and the thing they wanted to do.

Rather than rewrite it native, I wrapped the existing build in Capacitor and swapped one component: on iOS and Android the file input becomes the real camera, straight to the viewfinder. Same codebase, one conditional. Bundle identifier uk.co.pricescout.app, permission strings written properly in the iOS plist, icons and splash screens generated from the existing brand.

That is not the purist’s answer. The purist writes it twice in Swift and Kotlin. I am one person, and the honest trade is that a slightly heavier app that exists beats a beautiful native one that I am still writing at Christmas.

The bug that cost me a day

Payments went live and immediately did not work. Stripe was configured correctly. The keys were right. The code was right. I read the same forty lines until they stopped meaning anything.

The environment variable was set on the wrong Vercel project. Two projects, similar names, and I had been carefully typing the correct secret into a box that nothing was reading. The front end was posting to an empty string, which fails in a way that looks exactly like a code problem and is not one.

I now check the plumbing before the logic. Is the value actually arriving? Print it. Nine times out of ten the answer is embarrassing, and the tenth time you have learned something.

What I’d tell a client from this

PriceScout is not a client project and never will be. It is the thing I point at when someone asks whether I have shipped anything with money running through it, because the answer involves a live Stripe account, an API bill, an app-store listing and a support inbox, all of which are mine.

The lesson that transfers is the cache. Most of what makes a website good is invisible and unglamorous and happens where nobody claps — the thing that stops it falling over, the thing that stops it costing you money quietly, the thing that means it still works in a year. It is the same instinct as putting a heading on a homepage. Nobody notices it until it is missing.

— Jay

Build notes · Published 31 July 2026 · Beverley · See the work →