Inspiration
Every time I build a system that uses AI, I end up re-coding the same accounting backend: track tokens, price models, hold credit, enforce a spending limit, keep an audit trail. It's critical, it's easy to get subtly wrong, and it has nothing to do with the feature I actually set out to build. LedgerCap is that backend extracted into an online ledger API so I - and anyone metering AI or SaaS usage - can stop rebuilding it.
The sharper motivation: a spending cap is only worth anything if it holds when an autonomous agent fires many paid calls at once. A naive "read balance, decide, write" counter lets concurrent requests all read the same balance and overspend it. I wanted a cap that's provably safe under contention - which turned the project into a database problem, and made Aurora DSQL the load-bearing choice rather than just storage.
What it does
LedgerCap is a prepaid spend-control API for AI products and SaaS platforms. It answers one question before any expensive work runs: is this wallet allowed to spend this amount right now?
- Wallet per agent / customer. Every agent or customer gets its own wallet - a first-class balance owner. You fund it, charge it, and suspend it at a threshold.
- Hard, preventive cap. The spend decision is a predicate on a conditional
UPDATE, not an app-side read. A charge that would cross the floor simply changes nothing - the expensive call is prevented, not reported afterwards. - Reservations.
authorize → capture → voidholds reserve credit when the final cost isn't known up front, so concurrent calls can't collectively over-promise the balance. - Append-only ledger. Every accepted movement is recorded with the resulting balance, in the same atomic statement as the debit - an audit trail, not double-entry accounting.
- Integer money. Balances are integer nanodollars (1 nano = 1e-9 USD); the API takes
amountNanosoramountCents, never floats. - Idempotent operations. Idempotency keys are bound to a request fingerprint, so a retried call can't double-charge or silently change an operation (at-least-once-safe).
- Two planes, one core. A token-authed data plane (
/api/v1/*) for machines and a Clerk-authed dashboard control plane (/api/*) call the same money library, so the UI and the API can never disagree about money. - A live consistency proof. A playground fires concurrent charges at one wallet and shows the safe path finishing exactly at the floor next to a naive version that overspends - the guarantee, demonstrated rather than claimed.
How I built it
- Frontend & deploy: Next.js (App Router) from a v0-linked scaffold, deployed on
Vercel. The
@vercel/functionsSDK handles AWS OIDC credentials and database-pool lifecycle - this does not use theaipackage. - Database: Amazon Aurora DSQL over the PostgreSQL wire protocol. The whole data model
is built around DSQL's optimistic concurrency: one hot balance row per wallet
(
owner_id) as the concurrency boundary; the charge is a single writable CTE that reads the live limit, conditionally debits, and inserts the ledger row atomically. - Concurrency: OCC conflicts surface as SQLSTATE
40001and are retried with bounded exponential backoff; an exhausted retry budget maps to HTTP429(retryable), never a- Unique-violations (
23505) are deliberately not retried - writes are designed so concurrent same-key callers conflict on the hot row, not a primary-key insert.
- Unique-violations (
- Security: No static AWS keys ship in the app. At runtime the function assumes an IAM role via OIDC and mints a short-lived DSQL auth token for a least-privilege database role.
- Validation & shape: Zod schemas shared between client and server; routes stay thin (auth → validate → call lib → shape JSON) with all business logic and SQL in the lib.
- Testing: k6 load tests drive the charge path under contention, plus live probes and API verification against a real DSQL cluster after each deploy.
- Process: I set the product model and the correctness invariants; most implementation ran through a repository-aware AI harness in scoped phases - executable checks, adversarial and Copilot review, automatic Vercel deploys, then probes against real DSQL.
Challenges I ran into
- Making the cap correct under concurrency. The core insight was to make the spend
decision a predicate on the
UPDATErather than a preceding read, so every competing charge conflicts on the same row and DSQL's OCC serializes them - then retry40001and re-evaluate against the freshly committed balance. - DSQL is PostgreSQL-compatible, not PostgreSQL. No foreign keys (referential integrity
lives in the app layer, so balances are keyed by an opaque
owner_id);ALTER TABLE ADD COLUMNcan't take aDEFAULT(nullable columns +COALESCE); indexes must be createdASYNCand are eventually consistent during the build; and parameter arithmetic needs explicit::bigintcasts or DSQL rejects$2 - $3as ambiguous. - Idempotency that survives concurrency. A replay and a refusal both look like "zero rows updated," so the cold path has to classify the result, and the idempotency design has to be OCC-safe rather than racing on an insert.
- Vercel deploy gotchas that pass locally but fail the pipeline. Function
memoryinvercel.jsonis rejected with Fluid compute on the Hobby plan, and any DB/network side-effect at module top level runs duringnext build(no DSQL reachable) - both had to be designed around. - Money precision. Keeping everything in integer nanodollars and being careful that
computed values (rate × tokens) can exceed JS's 2^53 safe-integer limit even though
MAX_NANOSdoesn't.
Accomplishments I'm proud of
A hard spend cap that demonstrably holds under concurrent load, with the guarantee shown live in a playground rather than asserted - and a data model that treats Aurora DSQL's consistency as the enforcement mechanism, not just a place to store rows.
What I learned
How much of "metering" is really a distributed-systems correctness problem, and how far a single strongly-consistent conditional write can take you when you stop reading-then-writing and start letting the database serialize the decision.
What's next
- Multi-region active deployment (the app is multi-region-aware; production is currently
single-region, co-located with DSQL in
us-east-1). - Real funding rails (e.g. Stripe) behind top-ups, and open self-serve signup.
- Observability and billing surfaces.
- Housekeeping: remove leftover scaffold schema and grow the automated test suite beyond load tests.
Built With
- dsql
- k6
- nextjs
- typescript
- vercel
Log in or sign up for Devpost to join the conversation.