Building a Credit Card Tracker Without a Server

I had four credit cards and a system for using them. The system lived in my head, and it worked approximately never. I would stand at a register, fail to remember which card paid four percent on groceries, and pull whichever one was in front.

Then a ninety five dollar annual fee posted on a card I had stopped using. I noticed three months later. That was the second one I had missed.

Credit Card Central exists because of those two failures, and it is deliberately much less ambitious than the category of app it sits in.

The decision that shaped everything: no bank connection #

Every serious personal finance app connects to your accounts. You hand over bank credentials to an aggregator, it pulls your transactions, and the app categorizes and analyzes them automatically.

I decided not to do that, and it is the single decision that determined what the app is.

Connecting would have bought automatic transaction import, actual spending analysis, real category totals, and the ability to tell you what you earned rather than what you should have earned. Genuinely valuable, and it is why those apps have the users they do.

What it would have cost: a server, because aggregator integrations do not run on a phone. Handling bank credentials, or tokens that are functionally as sensitive. Storing a database of which citizens hold which credit cards and where they shop, which is an extraordinarily attractive target and a permanent liability. Ongoing per-user cost, which means a subscription, which means a business model that has to justify itself every month. And a compliance surface I did not want to own as one person.

The thing I actually wanted was much smaller. I wanted to know which card to pull out, and I wanted to be warned before a fee posted. Neither of those requires knowing my transactions. They require knowing my cards, which is information I can type in once in four minutes.

So the app has no server, no account, no login, and no network calls. Everything lives in a local database on the device. If you lose the phone, you lose the data, and the tradeoff is that nobody holds a list of your credit cards on a server somewhere.

I did not fully appreciate how much simpler this made everything until I was done. No auth, no sync conflicts, no session handling, no password reset flow, no privacy policy that needed a lawyer, no breach exposure, no monthly infrastructure bill, no subscription to justify. The app could be free because it costs nothing to operate.

The data model, which is less obvious than it looks #

The core question the app answers is: given a spending category, which of my cards earns the most?

Naively that is a table of cards, a table of categories, and reward rates joining them. Which is where it starts, and then reality intrudes.

Rates vary by category and cards have several: four percent dining, three percent groceries, one percent everything else. Some cards have caps, like five percent up to $1,500 in a quarter and then one percent, so the best card for groceries changes mid-quarter once you have hit the cap. Some cards rotate categories quarterly and require activation.

Some pay points rather than cash, and points have a valuation that depends on how you redeem them. A card paying “3x points” is only comparable to a card paying “3% cash” if you fix a cents-per-point value, and reasonable people disagree about that number.

And category definitions do not match reality, because what actually determines your reward is the merchant category code the store is registered under, not what you bought. Costco is usually not a grocery store as far as your card is concerned. This is covered in more depth in picking the right card for each spending category.

I resolved the points problem by letting the user set their own points valuation, defaulting to one cent, because pretending there is a single correct answer would be dishonest and hard-coding my own opinion would be worse.

I resolved the merchant category code problem by not resolving it. The app tells you which card is best for the category you select. It cannot know how a given store codes, and neither can any other app without transaction data. Being clear about that limit was better than pretending to a precision the app does not have.

Presets, because nobody enters twelve reward rates by hand #

The first version required you to enter every card manually: name, issuer, annual fee, fee date, and a reward rate per category. It was correct, complete, and nobody would ever have finished it.

Setup friction kills utility apps. If it takes twenty minutes before the app does anything for you, you will abandon it in minute four.

So the app ships with presets for popular cards. Pick your card from a list, the rates populate, and you are done in seconds. Custom entry still exists for anything not covered, and for the many people whose card terms differ from the standard product.

The catch, and I want to be honest about it: card terms change. Issuers adjust categories, rates, and fees regularly. Presets are a starting point, not a source of truth, and the app makes every field editable for exactly that reason. An app that confidently displayed stale reward rates as fact would be worse than one that made you check.

The annual fee reminder, which is the actual point #

This is the feature I built the app for, and it is technically the least interesting part, which is usually how it goes.

You enter each card’s annual fee and the month it renews. The app schedules a local notification some weeks ahead. Not on the day, because on the day it is too late to do anything. Weeks ahead, while you still have time to run the numbers, call for a retention offer, or downgrade to a no-fee version. The math on that decision is here.

The engineering wrinkles were all in scheduling.

Local notifications, not push. No server, so no push infrastructure. Local notifications are scheduled on the device by the OS, which is exactly right for something a year away.

Both platforms limit how many notifications you can have pending. Scheduling a decade of annual reminders for a dozen cards blows through that limit. The fix is to schedule a rolling window and re-schedule when the app opens.

Annual recurrence is a date problem. “The same date next year” has edge cases, and February 29 is the obvious one. It is the kind of bug that surfaces once every four years and then looks ridiculous in a crash report.

And if the user never opens the app, the rolling window eventually runs out. For an app you might legitimately not open for eleven months, that is a real failure mode. I schedule further out than I otherwise would to buy margin, and re-arm on every launch.

What I gave up #

An honest accounting.

No spending analysis. The app does not know what you spent. It cannot tell you that you earned $847 last year or that you left $200 on the table. Users ask for this and it is a reasonable ask, and it requires transaction data, which requires the bank connection I chose not to build.

No sync across devices. Local storage means local. Get a new phone, re-enter your cards. It takes four minutes and it is genuinely annoying.

No backup. Lose the device, lose the data. I have gone back and forth on whether to add an export file, and I probably will.

And presets go stale, covered above. It requires user vigilance, which is a cost I pushed onto the user in exchange for not running a server.

Those are real limitations, and I would make the same trade again. The app does two things well instead of ten things while holding a database of everyone’s financial life.

The design constraint: three seconds at a register #

Like the speedometer app, the usage context defined the interface. You are standing at a checkout, someone is waiting, and you have about three seconds.

So the category picker is the home screen. Not a dashboard, not a summary. Open the app, tap a category, see which card wins. Two taps, done. The answer is one card, large, not a ranked table you have to parse, with the runners-up visible below for the case where you do not have the top card with you and visually subordinate. And nothing requires typing in the moment of use, since all the typing happens during setup.

I built a nicer version first, with a portfolio overview and charts, and then realized the overview screen was something you would look at twice and the category lookup was something you would use weekly. So the useful thing became the home screen and the pretty thing moved to a tab.

Lessons #

Deciding what not to build is most of the design. Refusing the bank connection eliminated the server, the subscription, the security exposure, and about eighty percent of the work, and cost one feature I did not personally need.

Setup friction is where utility apps die. The presets were not a nice-to-have, they were the difference between an app people use and an app people install.

Local-first is underrated for small apps. No accounts, no sync, no infrastructure, no monthly cost, no breach risk. There is a whole category of software that does not need a backend and has one anyway.

And the boring feature was the point. The fee reminder is unglamorous scheduling code and it is the reason the app exists.

The credit card thinking behind it is in credit card rewards without getting burned. Other builds: a GPS speedometer, a retirement planner, and an offline AI chat app.