What Ledger17 is

Ledger17 is a headless double-entry ledger: an append-only record of balanced accounting transactions, exposed exclusively through a REST API. There is no bundled UI and no workflow engine — your systems record facts, and the ledger keeps the books.

It is built on CQRS and event sourcing, split into two services:

ServiceDefault portRole
Command API:8080Writes — record transactions, submit business events, reverse mistakes
Query API:8081Reads — balances, transaction lines, subledgers, live journal

All business endpoints live under /ledger/v1. Both services publish a live OpenAPI document at /q/openapi and Swagger UI at /q/swagger-ui.

The query side is built for speed: balances and rollups are pre-calculated as transactions post — across every dimension in parallel — so reads are constant-time lookups rather than scans over the event history.

Five things to know before your first request

  1. Everything is append-only. Posted transactions are immutable. Corrections are new, compensating transactions — never edits. See Core concepts.
  2. Every block must balance. A transaction is a block of two or more entries where the sum of debits equals the sum of credits, exactly — integer arithmetic, no rounding. Unbalanced blocks are rejected.
  3. Amounts are scaled integers. 125000 with unit: "EUR" (scale 2) means €1,250.00 — no decimals anywhere in the API. The accounting side of an entry comes from entryType (DEBIT or CREDIT); the scale comes from the unit, which can also be non-monetary (kgCO2e, hours, …).
  4. Requests are tenant-scoped. Send the organization id in the X-Tenant-ID header on every call. See Authentication.
  5. Writes are idempotent. POST /ledger/v1/transactions requires an X-Idempotency-Key header; retrying with the same key can never post twice.

Your first transaction

Record a balanced block — a sales invoice of €1,250.00 including 25% VAT:

  curl -X POST http://localhost:8080/ledger/v1/transactions \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: org-84120557" \
  -H "X-Idempotency-Key: inv-2026-0042" \
  -d '[{
    "name": "Sales invoice #42",
    "transactionDate": "2026-07-01",
    "unit": "EUR",
    "entries": [
      { "accountReference": "1500-receivables", "entryType": "DEBIT",  "amountMinorUnits": 125000,
        "dimensions": { "CUSTOMER": "cust-001" } },
      { "accountReference": "3000-sales",       "entryType": "CREDIT", "amountMinorUnits": 100000 },
      { "accountReference": "2700-vat-out",     "entryType": "CREDIT", "amountMinorUnits": 25000 }
    ]
  }]'
  

The ledger answers 201 Created with the recorded transaction:

  [{
  "transactionId": "7d9e1a4c-…",
  "tenantId": "org-84120557",
  "name": "Sales invoice #42",
  "transactionDate": "2026-07-01",
  "unit": "EUR",
  "entryCount": 3,
  "totalDebitMinorUnits": 125000,
  "recordedAt": "2026-07-01T09:14:03.512Z"
}]
  

Then read it back from the query service:

  curl "http://localhost:8081/ledger/v1/balances?accountReference=1500-receivables&timePeriodType=MONTHLY&timePeriodValue=2026-07" \
  -H "X-Tenant-ID: org-84120557"
  

Where to go next