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.
Needs Reports for the ledger and an API key. API keys are included from Business, and the pricing page shows how many each plan holds. 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 — on Android, iOS, desktop and the web, with its own server behind the tills. 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. Building 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.
- In Ledgr: create the key
Roles & Users → API Keys → Generate. Give it
reports:write— posting a journal needs create permission on the ledger — and nothing else. Looking up the company its journals belong to (GET /api/companies) is a read every key may make, whatever its scopes. Name it "Aura POS" so it can be revoked without anyone wondering what it was for. - 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.
- Exchange it for a token
POST /auth/tokenwith 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. - Find the company
GET /api/companieswith that token lists the companies the key may reach. Every ledger call then carries the company's id in anX-Company-Idheader — there is no default company, and a call without one is refused. The id never changes, so look it up once and keep it with the key.
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.
The trial balance would still balance — 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.
| Aura | What it is | Ledgr | Ledgr account |
|---|---|---|---|
1000 | Cash | 1360 | Petty Cash |
1010 | Yoco settlement due | 1330 | Other Receivables & Prepayments |
1100 | Inventory | 1310 | Inventory / Stock on Hand |
2000 | VAT Output ⚠ | 2130 | VAT Output (Control) |
2100 | Tips Payable ⚠ | 2120 | Other Payables & Accruals |
2200 | Processor fees payable | 2120 | Other Payables & Accruals |
4000/4010 | Revenue | 4010 | Sales — Goods |
5000 | Cost of goods sold | 5040 | Cost of Goods Sold |
6000 | Yoco fees | 6030 | Bank Charges |
Ledgr's ledger endpoint takes account ids, not codes, so resolve them once with
GET /api/accounts (with the X-Company-Id header) and cache the
code→id map. Codes are stable; the ids are per company.
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:
{
"clientId": "3f0c9a52-6d1e-5b8a-9c47-2e1f0a7d4b19",
"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": "Output VAT" },
{ "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, less the VAT it contains at 15%, is 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.
reference is the label a person searches for — one value per location
per day. It is not what stops a double posting; clientId is, below.
Not posting twice
Ledgr deduplicates a journal on its clientId: a UUID Aura generates once per
location per day and sends with every attempt at that day. The first post creates the
journal and answers 201; a retry with the same clientId — after
a timeout Ledgr had in fact committed, or a job that ran twice — answers
200 with the journal that already exists, and posts nothing again. The simplest way to generate it is a name-based (version 5) UUID of the day's reference, so the same day
always yields the same id without Aura having to store it.
A fresh clientId is a fresh journal, and a journal sent without one is never
deduplicated — two identical journals on the same day are sometimes genuinely two
journals. Aura keeps its own record of which sales it has already journalled, which is how it
stays safe when offline sales arrive late; record beside it the clientId and the
journal id Ledgr returned for each day.
The rules are the same for every create in the API; see retrying safely.
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
1330once 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.
Common problems
| What you see | What to do |
|---|---|
| 401 on every call | The key is wrong, revoked, or being sent the wrong way. It goes in the
Authorization header, exchanged at /auth/token first.
Generate a fresh key under Roles & Users → API Keys and paste it whole — a
key is shown once and cannot be read back. |
| 400 “X-Company-Id header is required” | Send the company's id in an X-Company-Id header on every ledger call
— see Authenticating. |
| 403 with a scope named | The key does not hold the scope the endpoint needs. Tick it on the key rather than widening the endpoint, and note that a key with no scopes can reach nothing. |
| 403 with a module named | Your plan does not include that module. A key cannot exceed the plan. |
| The journal posted twice for one day's trade | Send the same clientId for a given day. Ledgr keys on it, so a retry
after a dropped response returns the journal it already has rather than making a
second one. A fresh clientId is a fresh journal, and the
reference does not deduplicate anything. |
| The day will not balance | Takings, VAT, tips and card fees have to reconcile to the amount banked. The refusal names the difference. A journal that does not balance is refused rather than posted, which is what keeps the trial balance meaningful. |
| An account id is refused | It has to be an account in your chart of accounts. Read the chart back from the API and map against the ids you get, rather than hard-coding them — a chart is per company. |
| Tips are landing in revenue | A tip is money you hold for staff, not turnover. Map it to a liability account. It matters on the VAT201 as well as the income statement. |
| Card fees are missing | The gateway's fee is an expense and the amount banked is net of it. Post the gross takings and the fee separately, or the bank will never reconcile. |
| The bank feed and the till both created a transaction | Expected, and they are meant to be matched rather than duplicated. Reconcile the banking side against the journal; see Banking. |
429, with a Retry-After |
You are over the key's rate limit. Wait the number of seconds it gives you rather than retrying immediately. |