AI expense tracker for Android and web
ThoughtExpense
Type what you spent the way you would say it. “coffee 120, uber 250” becomes logged, categorised expenses, and the categoriser learns from every correction.
- Ownership
- Personal work
- Organisation
- Personal product
- My role
- Sole engineer and designer
- Status
- Live on Android and web
- TypeScript
- React Native
- Next.js
- Node.js
- PostgreSQL
- Redis
- Gemini API
Overview
ThoughtExpense is a personal expense tracker for people who have given up on expense trackers. The whole product is built around one interaction: a single text input where you type what you spent in plain words.
If capture costs nothing, everything downstream becomes possible: budgets, trends, anomaly alerts, and advice grounded in real data. The thesis was that the input, not the dashboard, decides whether the product survives.
Role and ownership
What I personally did
- Product definition and interface design.
- React Native Android app and Next.js web app.
- Node.js API, PostgreSQL schema, Redis caching, and background jobs.
- AI parsing pipeline, Google Play billing, and production deployment.
Constraints
- Solo build across three codebases: Android app, web app, and API.
- A language model sits in the critical path, and model APIs are slow, rate-limited, and occasionally wrong.
- Many target users are on mobile networks behind carrier-grade NAT, where thousands of people share one public IP.
- AI calls cost money per request, so a free tier has to be cheap to run.
Solution
Both clients run a small on-device parser and keyword categoriser, so item counts and category chips appear while you type, before any network call. Composing works offline; only saving needs the server.
On save, the API parses the text with the model, applies a hand-authored category catalogue that overrides the model when it matches, and returns low-confidence items for review instead of silently saving them. Corrections feed a learning loop so categorisation becomes more consistent over time.
Heavy endpoints have an async variant backed by a job queue, so a phone never has to hold a long connection open on a weak network.
Try the pre-parser
ThoughtExpense live demo
- Parsed items appear here as you type.
0 items, ৳0
A simplified version of the on-device parser. It runs in your browser; nothing you type is sent or saved.
Clients
Android app
React Native, on-device pre-parse
Web app
Next.js, same pre-parse rules
API
Node.js REST API
Auth, per-user rate limits
Job queue
Async variants of heavy endpoints
Parsing
Category catalogue
Overrides the model on a match
Model chain
Retry with backoff, then fallback models
Data
PostgreSQL
Expenses, budgets, corrections
Redis
Cache and limits, fails open


Key decisions
Statistics decide, the model narrates
Insights like “unusual spend this week” must be correct, and a language model will happily invent a number.
- Considered
- Send raw transactions to the model and let it produce insights end to end.
- Chose
- Anomaly detection, trend detection, and large-transaction flags are pure functions over currency-normalised data with documented thresholds. The model only writes the explanation, under rules that forbid figures that cannot be derived from the data.
- Trade-off
- Fewer “creative” insights, and every new insight type needs code. In return, the numbers are testable and the model cannot be wrong about money.
Rate-limit by user, not by IP
Behind carrier-grade NAT, one public IP can represent a whole neighbourhood of mobile users.
- Considered
- Standard per-IP buckets at the reverse proxy.
- Chose
- Authenticated routes are limited per user ID. Cache failures fail open, so a Redis outage degrades limits instead of breaking requests.
- Trade-off
- Limits live in application code rather than the proxy, and abuse before sign-in needs separate handling.
A fallback chain instead of a single model
One model provider having a bad minute should not mean a user cannot log lunch.
- Considered
- Call one model and surface errors to the user.
- Chose
- Calls retry with backoff, then cascade through a chain of fallback models. A curated catalogue handles common items without the model at all.
- Trade-off
- More code paths to test and slightly different output styles across models.
Outcome
3
Production codebases shipped solo
Android app, web app, and API, roughly 26,000 lines of TypeScript in total.
45+
REST endpoints
Counted from the API route definitions.
Free and premium tiers with Google Play billing, webhook reconciliation, and an account-deletion flow that meets Play Store policy.
Reflection
Keeping the model out of anything that must be correct made the product far easier to test. What remains hard to test is narration quality, which needs review by reading, not by assertion.
Perceived speed came more from instant on-device feedback than from making the model call faster.
Notes and further reading
- Engineering logStatistics decide, the model narrates
- Engineering logRate limits behind carrier-grade NAT