Accounts
A ledger is a chart of accounts, and every transaction entry posts to one of them. Some accounts you can plan up front — your cash account, your revenue account. Others you can't: a running balance for each of your users, tenants, or orders, where there might be millions and new ones appear every day. Ledfra handles both, and you address every account the same way — by a human-readable slug like income:rent or liabilities:user/123.
The two kinds of account
Every account is one of two kinds. The difference is simply who creates it and when.
Control accounts
A control account (type: CONTROL) is a standalone bookkeeping account you design up front in the Ledfra app — cash, revenue, processing fees. There is a small, known set of them, you name them yourself, and the API only ever reads them. Use control accounts for the figures your whole business shares — the lines you'd put on a balance sheet by name.
Template accounts
A template account (type: TEMPLATE) is a per-object subsidiary account — one wallet per user, one payable per seller, one receivable per tenant. You don't design each one. You design the template once (its currency, normal balance, and category), and Ledfra creates the individual account the first time a transaction references its slug. Use template accounts when you need one balance per external thing and can't list them ahead of time.
| Control account | Template account | |
|---|---|---|
| Who creates it | You, in the Ledfra app | Ledfra, on the first transaction that uses its slug |
| How many | A handful you design | One per object — unbounded |
| Exists from | The moment you design it | Its first transaction |
| Example slug | assets:cash, income:rent | liabilities:user/123, receivables:tenant/42 |
| Use it for | Accounts your whole business shares | One balance per external object |
Why slugs: accounts created on the fly
The obvious way to track a balance per user is to create an account row for every user before you can record their first transaction. That means a second API call, error handling when it already exists, and a race when two events for a new user arrive at once. It is busywork, and it is easy to get wrong.
Slugs remove that step. You reference an account by a stable, readable name, and posting a transaction is a transparent upsert: if the template account already exists we use it, and if it doesn't we create it from its template and then post — all in one call. You never call a "create account" endpoint. We provision the account for you, behind the scenes.
# 'liabilities:user/123' doesn't exist yet. Ledfra creates it from the
# 'user' template, then posts the entry — one call, no pre-provisioning.
curl 'https://ledfra.com/api/ledgers/{ledgerId}/transactions' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"description": "Deposit to user wallet",
"entries": [
{ "account": "assets:cash", "operation": "DEBIT", "amount": "USD:100.00" },
{ "account": "liabilities:user/123", "operation": "CREDIT", "amount": "USD:100.00" }
]
}'liabilities:user/123 and the account is simply there. The slug doubles as an audit trail: it says exactly what the account is and which object it belongs to, in every transaction that touches it.Each account also carries a normalBalance (CREDIT or DEBIT) — the side on which its balance increases — and the currency it is denominated in. For how amounts and currencies are represented, see Money.
Listing and fetching accounts
GET /ledgers/{ledgerId}/accountslists a ledger's accounts and is how you discover valid slugs. It supports aquerysubstring filter over names and slugs.GET /ledgers/{ledgerId}/accounts/{slug}fetches a single account. A template account returns404until its first transaction brings it into existence.
Slugs may contain /, so URL-encode a slug when it appears in a path: receivables:tenant/123 → receivables:tenant%2F123.
# List accounts (optionally filter by name or slug)
curl 'https://ledfra.com/api/ledgers/{ledgerId}/accounts?query=tenant' \
-H 'Authorization: Bearer YOUR_API_KEY'
# Fetch one account — note the URL-encoded '/' in the slug
curl 'https://ledfra.com/api/ledgers/{ledgerId}/accounts/receivables:tenant%2F123' \
-H 'Authorization: Bearer YOUR_API_KEY'Limitations
- A template account doesn't exist until its first transaction — fetching it by slug returns
404before then. - You can't create a template account directly through the API. Only a transaction brings one into being, through the upsert described above.
- The upsert only works for slugs that map to something you've already designed — an existing control account, or a template. A slug that matches neither is rejected, so you can't invent arbitrary accounts just by posting to them.
- An account holds a single currency.
Next, see how amounts and balancing work in Money, or jump to the account endpoints in the API reference.