Ledgr/Documentation/Connecting Aura POS

Connecting Aura POS

Aura is the till. Ledgr is the books. This is how a day's takings get from one to the other without anyone retyping them.

Plan.

Needs Reports (Business upwards) for the ledger, and an API key, which is on every plan. Aura is a separate product with its own subscription.

What Aura is

Aura POS is a hospitality point-of-sale system for South African restaurants — Kotlin Multiplatform on Android, iOS, desktop and web, with its own Ktor backend. Table-side QR ordering, a kitchen display, split bills, tips, Yoco/SnapScan/Zapper, WhatsApp receipts, and offline-first sync built for load shedding.

It is not a Ledgr module and it is not becoming one. A restaurant till has a different shape from an accounting ledger: it cares about covers, courses, prep stations and shift hand-overs, and it has to keep taking orders when the power and the network are both gone. Bolting that into an accounting package would compromise both. What the two share is the money.

Which way the data flows

One direction only: Aura → Ledgr. Aura owns the sale, the menu, the stock behind the menu and the payment. Ledgr owns the books, the VAT return and the financial statements. Ledgr never writes back into the till, because a POS that could have its takings edited from an accounting package is a POS whose cash-up means nothing.

Push a day, not a sale

Aura already computes double-entry journals for every checkout, in its own ledger. The integration sends those to Ledgr — summarised by day, per location, not one document per sale.

This matters more than it sounds. A restaurant doing 300 covers a night would create 300 Ledgr documents an evening, 9 000 a month, for a set of books whose owner wants one line saying what Tuesday took. The detail belongs in Aura, which is where anyone asking "what did table twelve order" is going to look anyway. Ledgr gets the total, the VAT, and a reference back to the day.

Push after the cash-up, not during service. A day that is still open can still change — a void, a late tip adjustment, a re-opened tab — and a journal posted mid-service has to be reversed rather than simply not yet written.

Authenticating

Aura holds a Ledgr API key and exchanges it for a short-lived token, exactly as any other integration does. It never needs a Ledgr password.

  1. In Ledgr: create the key

    Settings → Roles & Users → API keys → Generate. Give it reports:write — posting a journal needs create permission on the ledger — and nothing else. Name it "Aura POS" so it can be revoked without anyone wondering what it was for.

  2. In Aura: store the key

    As a server-side secret, not in a client build. The POS terminals never talk to Ledgr; only Aura's backend does.

  3. Exchange it for a token

    POST /auth/token with the key as a bearer credential returns a one-hour access token. Aura's nightly job asks for a fresh one each run rather than caching it — a job that runs once a day gains nothing from reusing an hour-old token.

Full details of the exchange, the scopes and the rate limits are in the developer documentation.

Mapping the accounts — read this part twice

Aura's journal generator uses its own account numbers, and they are not Ledgr's. The two charts overlap in the ranges but disagree on the codes, and two of the collisions are the dangerous kind: Aura's 2000 is VAT Output, while Ledgr's 2000 is the Non-Current Liabilities header. Aura's 2100 is Tips Payable, while Ledgr's 2100 is the Current Liabilities header.

Posting Aura's codes into Ledgr unchanged would credit header accounts.

The trial balance would still foot — debits would equal credits — and the balance sheet would be nonsense, with VAT owed to SARS sitting inside a heading. That is the worst class of accounting bug, because nothing looks broken. Map every line explicitly, and let an unmapped code fail loudly rather than defaulting to anything.

AuraWhat it isLedgrLedgr account
1000Cash1360Petty Cash
1010Yoco settlement due1330Other Receivables & Prepayments
1100Inventory1310Inventory / Stock on Hand
2000VAT Output ⚠2130VAT Output (Control)
2100Tips Payable ⚠2120Other Payables & Accruals
2200Processor fees payable2120Other Payables & Accruals
4000/4010Revenue4010Sales — Goods
5000Cost of goods sold5040Cost of Goods Sold
6000Yoco fees6030Bank Charges

Ledgr's ledger endpoint takes account ids, not codes, so resolve them once with GET /api/ledger/accounts and cache the code→id map. Codes are stable; the ids are per tenant.

The daily journal

One balanced journal per trading day per location. A Tuesday taking R18 400 including VAT, of which R12 000 on card and R6 400 cash, with R900 of tips and R240 of card fees:

POST /api/ledger/journals
{
  "journalDate": "2026-08-04",
  "memo": "Aura POS takings — Main floor — Tue 4 Aug",
  "reference": "AURA-MAIN-20260804",
  "post": true,
  "lines": [
    { "accountId": "<1360 Petty Cash>",        "debit":  6400.00, "description": "Cash takings" },
    { "accountId": "<1330 Yoco settlement>",   "debit": 11760.00, "description": "Card takings net of fees" },
    { "accountId": "<6030 Bank Charges>",      "debit":   240.00, "description": "Yoco processing fees" },
    { "accountId": "<4010 Sales — Goods>",     "credit": 15217.39, "description": "Food & beverage revenue" },
    { "accountId": "<2130 VAT Output>",        "credit":  2282.61, "description": "VAT at 15%" },
    { "accountId": "<2120 Tips Payable>",      "credit":   900.00, "description": "Tips owed to staff" }
  ]
}

Note what the numbers do. Revenue is the VAT-exclusive figure — R17 500 of sales excluding tips, ÷ 1.15 = R15 217.39, with R2 282.61 of output VAT. Tips are a liability, not revenue: the restaurant is holding staff money, and booking it as income overstates both turnover and the VAT due on it. And the card line is net of fees, because that is what Yoco actually settles — with the fee shown as its own expense so the margin is honest.

Use reference as a natural key: one value per location per day. If a day is pushed twice, Ledgr will create a second journal — the reference is what lets Aura detect that and lets a human find it.

Not posting twice

Aura already tracks this on its own side: its journal generator skips any transaction that already carries a POS_SALE entry, which is how it stays safe when offline sales arrive late through /sync/push. Keep the same discipline for the Ledgr push — record, per day and location, that it was sent and what Ledgr's journal id was.

Ledgr's own idempotency works per source document, and a manually posted journal has no source document, so it will not deduplicate this for you. That is deliberate: two identical journals on the same day are sometimes genuinely two journals.

Stock, and why to leave it alone at first

Aura decrements its own stock on checkout and knows its own recipes. Ledgr has inventory too, with its own costing engine. Running both against the same physical stock means reconciling two sets of numbers forever.

Pick one. For a restaurant, Aura is usually the right owner — it knows the recipes and it is where the stock actually moves — and Ledgr receives the cost of sales as part of the daily journal, which is all the income statement needs. Keep Ledgr's inventory module for anything the till does not sell: cleaning supplies, crockery, kitchen equipment.

VAT and the VAT201

Once the daily journals are landing, Ledgr's VAT analysis and VAT201 pick the output VAT up automatically — it is a normal credit to 2130 like any other sale. Aura also has its own VAT201 export; use one or the other, not both, and if the books are in Ledgr then Ledgr is the one that will agree with the trial balance.

Before you go live

  • Push one quiet day and compare Ledgr's income statement against Aura's dashboard for the same date. They should agree to the cent on revenue and on VAT.
  • Check the balance sheet: tips should be a liability, and the Yoco settlement figure should match what actually lands in the bank two days later.
  • Reconcile the Yoco payout against 1330 once it clears. If the settlement account does not clear to nil over a week, the fee split is wrong.
  • Confirm no journal line landed on a header account — a header with a balance is the symptom of the mapping mistake above.
  • Revoke and re-issue the API key once, to prove that path works before you need it.

Where this stands

Honestly: Aura computes the journals today, in its own ledger, and Ledgr accepts them today over the public API. What is described here is the wiring between the two, and it is a job of a day or so on the Aura side — a nightly task that reads its own journal entries, maps the account codes, and posts one document per location per day. Nothing in Ledgr needs to change for it.