Skip to content
Back to systems
Veyra

A ledger, not a spreadsheet

Veyra

Personal Finance Tracking App

A personal finance system built on a double-entry ledger: every balance is derived from posted entries rather than stored and hoped for, and no import, correction or adjustment is allowed to bypass that.

In closed testing · a product in development

The Veyra transactions ledger on a laptop, with the mobile dashboard showing net position beside it.
Type
Product in development, in-house
Surface
Next.js app, 22 feature domains
Data
Postgres — 20 tables, 28 migrations
Scale
~67k lines · 394 tests · CI on every push

The system

What it does

Most personal finance apps store a balance and update it. That works until something goes wrong — a duplicate import, a correction, a refund posted twice — and then the number on screen has no way of explaining itself. Veyra is built the other way round: balances are a cached projection of a ledger, and the ledger is the truth.

One module owns every movement of money. Posting is atomic, validates the projected balance before it writes, and is idempotent on a scope, key and content hash — so importing the same statement twice cannot double-count. Financial history is append-only: an event is voided or replaced, never deleted, so the trail of a correction survives the correction.

Around that sit the planning surfaces — budgets, bills, income schedules, goals, loans, net worth — and an import pipeline that turns a PDF or CSV statement into staged entries for review. The rule that makes the design hold is that none of them write balances directly. Imported money goes through the same posting path as anything typed by hand.

Features

What was built

Everything listed is in the repository — not a roadmap.

  • 01

    One place money moves

    A single posting module owns every balance change — post, replace, void. No feature writes a balance directly, so there is one code path to reason about rather than twenty.

  • 02

    Balances are derived, not stored

    An account balance is a cached projection of its ledger entries. If the two ever disagree, the ledger wins and the projection is rebuilt.

  • 03

    Append-only history

    Events are voided or replaced, never deleted — so a correction leaves the original and the reason behind it intact rather than quietly rewriting the past.

  • 04

    Idempotent posting

    Every posting carries a scope, a key and a hash of its own content. Importing the same statement twice produces the same ledger, not double the money.

  • 05

    Statement import

    A PDF or CSV bank statement is parsed into staged rows for review, with duplicates detected and existing entries matched before anything is posted.

  • 06

    Dated reconciliation

    Proves an account against a statement at a given date: compares the statement’s closing balance with the ledger-derived balance for that day, explains the gap, and posts an adjustment only on explicit confirmation.

  • 07

    Budgets that do not post

    Spending caps are a planning surface, deliberately kept out of the ledger — a budget is an intention, not a transaction, and conflating the two is how forecasts end up in balances.

  • 08

    Bills, income and schedules

    Recurring obligations and expected income modelled as schedules, so the question "what is likely to happen next" has something to answer from.

  • 09

    Goals, loans and net worth

    Longer-horizon surfaces, with net worth captured as dated snapshots rather than recomputed history — so a past figure stays what it was at the time.

  • 10

    Natural-language quick add

    A typed sentence parsed into a draft entry, reviewed before it posts rather than written straight through.

  • 11

    Assistant over your own data

    A chat surface answering questions against the ledger, scoped to the signed-in account.

  • 12

    Authentication on every procedure

    Clerk-backed sessions, with all 119 real API procedures requiring an authenticated caller — the only public one is the unused base definition. Every query filters on the owning user, and the client never supplies a trusted owner id.

  • 13

    Rate limiting at the edge

    Per-user limits on the expensive paths, so a single account cannot exhaust the shared resources behind them.

Beyond the prototype

Built to become a subscription product

Approved direction — billing not yet built

Veyra was built for a small circle of users, and the move to a paid product is written down rather than assumed: a costed plan covering tenancy, rate limiting, observability and billing, approved as a direction in July.

What makes it additive rather than a rewrite is that the expensive half is already done. Authentication is uniform across every procedure, every query is scoped to its owner, and the money core is atomic, idempotent and append-only. Those are the parts that are painful to retrofit; billing on top of them is mostly new code rather than surgery on old code.

  • Subscriptions on Stripe

    Customer and subscription objects, checkout, the customer portal, and webhook ingestion — with plan state derived from Stripe’s webhooks rather than trusted from the client.

  • Entitlements as their own tables

    Subscription and billing-event tables separate from the finance schema, so a plan change is never confused with a movement of the user’s own money.

  • Per-user tenancy, deliberately

    One subscription per user, matching the isolation already enforced in every query. Shared household workspaces were considered and explicitly ruled out of scope rather than left vague.

  • The audit came first

    The security and authorisation review was completed before any of the commercial work started — which is what makes the rest of the track additive.

Interface

The parts worth looking at

Plate 01

Position at a glance

The first question answered first: net position, what is available against what is owed, and the movement that got there.

Position at a glance
Plate 02

The ledger

Transactions as posted events — each one categorised, attributed to an account, and dated, rather than summed into a single running number.

The ledger
Plate 03

An account, and what it funds

One account in detail, with the commitments already pointed at it — so a balance is read against what it is spoken for, not on its own.

An account, and what it funds
Plate 04

Bills and obligations

What is due and when, kept apart from the ledger: an upcoming bill is a plan, and planning never posts.

Bills and obligations
Plate 05

Goals

A target with progress measured against it, and the accounts backing it named rather than assumed.

Goals
Plate 06

Ask Veyra

The assistant, scoped to the account holder’s own data and marked read-only in its own header.

Ask Veyra

Stack

What it was built with

Dependencies actually in use, not everything in package.json.

  • Next.js
  • TypeScript
  • Vercel
  • Neon
  • PostgreSQL
  • Clerk
  • Upstash Redis
  • Resend
  • OpenAI
  • Tailwind CSS

Framework

  • Next.js (App Router)
  • TypeScript
  • tRPC
  • React Query

Data

  • PostgreSQL (Neon)
  • Drizzle ORM
  • Zod

Interface

  • Tailwind CSS
  • Radix / shadcn
  • Recharts
  • Framer Motion

Platform

  • Vercel
  • Clerk
  • Upstash Redis
  • Resend
  • OpenAI

Software Is Never Really “Done.” Neither Are We.

Let’s talk about how your business runs today, what a tailor-fit system could look like, and what it means to have a technology partner who doesn’t disappear after launch. Just need to hire a developer or a BI analyst? That conversation is just as welcome — and stands entirely on its own.

Two routes, one form. Say which one fits and we will reply to arrange a short discovery call — no pitch deck, no obligation.

Tell us what you need

I need

Goes straight to the founding team. No autoresponder, no sales sequence.