Double-entry micro-ledger API built with .NET 9 Minimal APIs
A lightweight, self-contained accounting API that implements double-entry bookkeeping. Every transaction creates balanced debit and credit entries, maintaining ledger integrity at the application level. No external database required — runs entirely in-memory.
Most demo APIs treat financial operations as simple CRUD. Real accounting systems need double-entry bookkeeping: every debit has a matching credit, running balances must be chronological, and net positions must be queryable across accounts. This project solves that with clean domain modeling on a minimal stack.
- Double-entry transactions — each operation creates paired debit/credit entries across two accounts
- Running balance — account statements show cumulative balance per entry, in chronological order
- Net balance aggregation —
GET /accountsreturns the computed net position for every account - Validation — rejects same-account transfers, missing accounts, and invalid amounts
- In-memory persistence — EF Core InMemory provider, zero setup, restart-safe for demos
- Integration tests — 8 tests covering all endpoints via
WebApplicationFactory
┌─────────────┐ HTTP ┌──────────────────────────┐ EF Core ┌──────────────┐
│ Client │ ────────→ │ Minimal API (.NET 9) │ ─────────→ │ InMemory DB │
│ curl / any │ ←──────── │ │ ←───────── │ │
└─────────────┘ JSON │ POST /accounts │ │ Accounts │
│ POST /transactions │ │ Transactions│
│ GET /accounts │ │ Entries │
│ GET /accounts/{id}/stmt │ └──────────────┘
└──────────────────────────┘
| Layer | Technology |
|---|---|
| Runtime | .NET 9 |
| API Framework | ASP.NET Core Minimal APIs |
| ORM | Entity Framework Core 9 (InMemory provider) |
| Testing | xUnit + WebApplicationFactory<Program> |
| Language | C# |
# Prerequisites: .NET 9 SDK
# https://dotnet.microsoft.com/download/dotnet/9.0
# Clone
git clone https://github.com/binadacode/FinLedgerApi.git
cd FinLedgerApi
# Run
dotnet run
# App starts on http://localhost:5000
# Test
dotnet testcurl -s localhost:5000/accounts -X POST \
-H 'Content-Type: application/json' \
-d '{"name":"Cash","accountType":"Asset"}'
curl -s localhost:5000/accounts -X POST \
-H 'Content-Type: application/json' \
-d '{"name":"Revenue","accountType":"Revenue"}'curl -s localhost:5000/transactions -X POST \
-H 'Content-Type: application/json' \
-d '{
"debitAccountId": 1,
"creditAccountId": 2,
"amount": 1000,
"description": "Initial deposit",
"transactionType": "Credit"
}'curl -s localhost:5000/accounts/1/statementResponse includes chronological entries with runningBalance:
{
"id": 1,
"name": "Cash",
"entries": [
{ "amount": 1000, "type": "Credit", "runningBalance": 1000 },
{ "amount": 250, "type": "Debit", "runningBalance": 750 }
]
}curl -s localhost:5000/accounts| Method | Path | Description |
|---|---|---|
POST |
/accounts |
Create a new account |
GET |
/accounts |
List all accounts with net balances |
POST |
/transactions |
Create a double-entry transaction |
GET |
/accounts/{id}/statement |
Account statement with running balance |
POST /transactions — request body:
{
"debitAccountId": 1,
"creditAccountId": 2,
"amount": 1000.00,
"description": "string",
"transactionType": "Credit | Debit | Transfer"
}Validation rules:
debitAccountIdandcreditAccountIdmust reference existing accountsdebitAccountId != creditAccountIdamountmust be positive
- SQLite/PostgreSQL provider for persistent storage
- Date-range filtering on statements
- Account balance snapshots for audit
- API authentication (JWT)
- OpenAPI/Swagger UI
- Pagination on account lists
Binada Matara Arachchige CS undergraduate — AI + full-stack systems
MIT


