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 accountTemplate account
Who creates itYou, in the Ledfra appLedfra, on the first transaction that uses its slug
How manyA handful you designOne per object — unbounded
Exists fromThe moment you design itIts first transaction
Example slugassets:cash, income:rentliabilities:user/123, receivables:tenant/42
Use it forAccounts your whole business sharesOne balance per external object
Control accounts versus template accountsControl accounts are a small fixed set you design (assets:stripe, assets:paypal, expenses:payment-processing-fees). Template accounts (liabilities:payables, assets:receivables) expand to one account per object, e.g. assets:receivables/42.Control accountsA small, fixed set you design.assets:stripeassets:paypal$expenses:payment-processing-feesTemplate accountsOne per object — created on first use.liabilities:payablesassets:receivables↳ assets:receivables/42↳ assets:receivables/99↳ assets:receivables/127
Control accounts are a fixed set you design, template accounts expand to one per 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.

bash
# '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" }
    ]
  }'
It's a design choice for DX
We made account creation transparent on purpose. No pre-provisioning, no create-then-post two-step, and no races to manage — post to 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}/accounts lists a ledger's accounts and is how you discover valid slugs. It supports a query substring filter over names and slugs.
  • GET /ledgers/{ledgerId}/accounts/{slug} fetches a single account. A template account returns 404 until its first transaction brings it into existence.

Slugs may contain /, so URL-encode a slug when it appears in a path: receivables:tenant/123receivables:tenant%2F123.

bash
# 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

Know these edges
  • A template account doesn't exist until its first transaction — fetching it by slug returns 404 before 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.