A dark-mode finance super-app for Indian small businesses: ledger, GST, invoicing, payments, and credit in one legible interface.

The real prototype, not a recording. It opens on the splash and walks you into the welcome screen, fresh every time. On a wide screen a panel beside the phone jumps to any flow, and nothing you enter leaves the page. Runs entirely in your browser.The real prototype, not a recording. It opens over this page, and closing it brings you straight back here.
Picture the object this app actually replaces: a paper khata, a bound ledger of who owes what, updated by hand every night, perfectly legible to its owner and to nobody else. India has more than 63 million small businesses, and most of them still run on some version of that book, stitched together with five other tools for GST filing, invoicing, and chasing credit. Spendee folds everything orbiting that paper ledger (filing, invoicing, UPI collection, the loan application the bank keeps rejecting) into one dark-mode app that still runs on the khata's own logic.

Every serious ledger app in India (Khatabook, OkCredit, the rest) models a transaction as 'I Gave' versus 'I Got.' That's not a UI trend, it's the digital translation of a centuries-old paper convention, and Spendee builds directly on it rather than inventing a friendlier version nobody already trusts. The app runs on near-black, which does real work: green means money in and settled, red means overdue, amber means pending; learn that once and every screen reads faster.
The interesting move here isn't the ledger; competitors already do that well, and cheaply. It's the ledger-health score: a number built from on-time payments, cash inflow and GST compliance, used to pre-approve working capital and let an owner discount a specific overdue invoice for instant cash. It's deliberately styled like a CIBIL score, out of 900, because owners already know what a 782 means and don't need a new mental model taught to them. If that thread doesn't hold, this is just a nicer Khatabook, which isn't a business.

Concept work has no real users to test against, so the most honest evidence of process is what a revision pass caught. The first version of the dashboard priced everything in dollars and shipped three identical placeholder balances: To Get, To Give and Net Position all read the same number, which no real ledger could produce. The shipped version runs one coherent figure every other screen reconciles against: ₹4,15,200 minus ₹1,18,400 equals ₹2,96,800, in rupees, matching everywhere it appears.


One correction to make first, because the section above invites it: the dashboard's three headline figures are typed into the markup. They agree with each other because I made them agree, not because anything adds them up. The statement is the screen where a ledger actually runs. Four figures and a transaction count sit above the list, and not one of them is written down. The period chip filters a single array, money in and money out are reductions over whatever survives that filter, and the closing balance is the opening plus one minus the other.
const inPeriod = (tx) => state.period === 'custom' ? tx.d >= state.from && tx.d <= state.to : tx.d.slice(5, 7) === { apr: '04', may: '05', jun: '06' }[state.period]; // … the active-filter counter. function render() { const base = TX.filter(inPeriod); // summary reflects the period (not search/filters) const inSum = base.filter((t) => t.a > 0 && !t.isNew).reduce((s, t) => s + t.a, 0); const outSum = base.filter((t) => t.a < 0 && !t.isNew).reduce((s, t) => s - t.a, 0); const firstDay = state.period === 'custom' ? state.from : null; const open = state.period === 'custom' ? 188600 + TX.filter((t) => !t.isNew && t.d < firstDay).reduce((s, t) => s + t.a, 0) : OPEN[state.period]; $('sOpen').textContent = money(open); $('sIn').lastElementChild.textContent = money(inSum); $('sOut').lastElementChild.textContent = money(outSum); $('sClose').textContent = money(open + inSum - outSum); $('sCount').textContent = base.filter((t) => !t.isNew).length + ' transactions'; $('fIn').style.flexGrow = inSum || 1; $('fOut').style.flexGrow = outSum || 1; // … then the rows themselves, grouped by day, off that same filtered array.The failure this prevents is the one I caught in my own first draft, just above: three balances that looked like a ledger and were really three strings reading the same number. A summary you type can disagree with the list underneath it and nobody notices, because the two are never compared. Deriving all of it from the same filtered array means a transaction cannot be in the list and missing from the total. It is also what lets the months chain. For a custom range the opening balance is not looked up at all, it is April's ₹1,88,600 with every earlier transaction replayed over it, and the three month constants have to survive exactly that arithmetic. June's four figures do ship in the markup as static text, which render() overwrites with its own values on load, and that is a standing check as much as a first paint: the day the array and the typed figures disagree, the screen flickers and tells on me.
Use the panel beside the phone to jump to Payments, then tap the Statement tile. It opens on Jun 2025: opening ₹2,78,040, in ₹1,69,050, out ₹1,04,200, closing ₹3,42,890, over 10 transactions. Tap May 2025 and all five change at once, and May closes at ₹2,78,040, the number June was opening with a second ago. Tap Apr 2025 and it happens again: ₹2,18,700 closes April and opens May. The only figures held as constants are the three opening balances. The chain between them is arithmetic.
Prototype code · runs in the demo above
A splash, then a four-step intro that frames the value (a smart ledger, AI on your cash flow, credit on your books) before handing off to the near-black product.

Splash

Welcome

Smart ledger

AI insights

Smart credit
Mobile-first auth: number, one-time code, a four-digit PIN, permissions asked for one at a time, and GSTIN captured at registration.

Sign in

Register + GSTIN

Verify number

Create PIN

Reset PIN

Permissions

All set
The core loop: see the business at a glance, then record money you gave or got, with every contact's balance one tap away.

Home

Notifications

Contacts

Add contact

Customer

Entry details

New entry

Scan receipt
Create, send and chase invoices with the CGST/SGST split worked out, and discount an unpaid one for cash today.

Invoices

New invoice

Invoice preview

Invoice detail

Get paid early
A built-in account collecting over UPI: pay, scan, transfer, see what failed and why, and a statement whose balances run.

Cards & banking

Send money

Scan & pay

Transfer

Payment success

Payment failed

Statement

Card controls
GST filing as a guided task, the reports owners act on, stock, and the ledger-health score that turns good books into working capital.

Credit & growth

Apply for loan

Loan approved

GST Center

File GSTR-3B

Reports

Inventory
The business profile, a two-person team, settings and support.

More

Settings

Edit profile

Team & roles

Help & support
With real access, I'd watch five business owners record a day of entries next to their actual paper khata and note everywhere the app's model diverges from theirs. Then I'd pressure-test the GST filing centre against one real filing month (deadlines, credit reconciliation, late fees) with an accountant in the room, because a compliance flow that only works in a demo isn't a compliance flow.

