Money
Bank accounts and reconciliation
Importing a statement, matching movements to documents, and why money out with no invoice is reported separately from money in.
A bank account here is a register of movements, not a balance the product maintains. You import a statement, and each line becomes a movement that is either settled against a document, explained in words, or standing unexplained.
Feeds, where the bank has one
Where your bank has an API and has given you access to it, statement lines arrive on their own — four times a day, through the same path an uploaded file takes, with the same duplicate check. Uploading keeps working exactly as it does now, and is what works for any bank at all.
You register the application, in your own bank profile
This product holds no shared key for anybody's bank. Every one of these banks requires it that way in any case — FNB limits its Integration Channel to its own enterprise customers, Citi and J.P. Morgan onboard per corporate, Bank of Georgia issues credentials from your own Business Online admin page — but it is also the right arrangement: the credentials stay yours, under your own contract. Same as connecting a mailbox or a DocuSign account.
Most banks do not have a bank-shaped API — they implement a published standard, and that is what makes this worth having. Berlin Group NextGenPSD2 and the UK open banking standard between them fix the field names for hundreds of institutions, so picking either one means the response mapping is already correct and only your bank’s own base URL is missing. That is a far better answer than a list of named banks, each guessed at.
- Connect it and it works
- Bank of Georgia (Business Online), Mashreq (NEOBiz), Mercury, Wise and Revolut Business — endpoints and field names all filled in. Add your credentials and the account and the feed runs.
- Payment gateways, the same way
- Stripe, PayPal, Razorpay, Paystack and Xendit. Not banks, but an account money moves through is an account, and money nobody can account for is the same problem whichever side of a card network it sits on.
- Your own agreement, plus a base URL
- Every bank that speaks Berlin Group or the UK open banking standard: TBC, Liberty, Basisbank, Credo and Ziraat in Georgia; Barclays, Lloyds, Santander and HSBC in the UK; Commerzbank, Rietumu, Millennium bcp and Bank of China in Europe; UBS and PostFinance in Switzerland — and anything else that speaks either, under the two generic profiles. The response mapping comes from the specification rather than from a guess about that bank, so all that is missing is the address. See the note below about which of the two routes you are on.
- Bring your own pack
- Emirates NBD, ICBC and anything under *Another bank* — paste in the endpoints your onboarding gave you. Citi and J.P. Morgan are the same plus a client certificate, which is presented on every call; both answer in ISO 20022 camt.053 rather than JSON, which is read by its own parser and needs no field mapping at all.
- Not over an API
- FNB only. Its Integration Channel exists, but the response format and the authorisation flow are not published, so there is nothing to map — and a guessed mapping is worse than none, because it imports cleanly into nonsense. Upload the statement instead.
A bank nothing is known about is not in the list at all
A name in a picker is a promise that something is known about it, and a promise that ends in a support ticket is worse than an absence. Where a bank keeps its statement documentation behind an onboarding relationship with nothing published, or has no statement interface at all, it is not named — *Another bank* covers it honestly, and the CSV upload covers it completely.
Nothing here is guessed
A field map written from a plausible reading of a bank’s marketing page does not fail politely: it books every credit as a debit, and that is found weeks later in a reconciliation that will not come out. So a mapping is either taken from something published — a specification or the bank’s own documentation — or it is absent and said to be absent.
How a bank says the direction is the thing that has to be right
One signed number, two columns, or a magnitude plus an indicator — all three are read. An indicator that is not recognised is refused rather than defaulted, because a default picks a direction for every line labelled in a way nobody anticipated, and that error is found weeks later in a reconciliation that will not come out.
Each sweep overlaps the last by a week, because banks post value-dated lines late and a sweep starting exactly where the last one finished misses them in silence. The overlap costs nothing — lines are recognised by the bank's own identifier. Where a bank returns its statement a page at a time the pages are followed, because reading only the first would report a successful sweep with most of the statement missing — and a fetch that runs into more pages than one sweep will follow says so rather than presenting a part as the whole. Credentials are encrypted at rest, never shown again, and connecting and disconnecting are both recorded in the audit log. Disconnecting keeps every movement the feed brought in: those are the statement.
Some banks challenge the read itself
Wise refuses the first statement call every time and hands back a one-time token that has to come back signed. That is the ordinary path rather than a fault, so it is answered rather than reported as a credential problem — register a public key in your Wise account and paste the private half in beside the token. It is encrypted at rest and never shown again.
A gateway is read as a BALANCE, not as a list of charges
Money arrives in a Stripe or PayPal balance net of fees, sits there, and leaves as a payout to your bank. That ledger is what reconciles, and it is what these read. Importing the charge list instead would bring in thousands of customer payments a month, each wanting to be matched against an invoice that one settlement already covers — and the report of what is unexplained would be useless within a week. A payout appears twice on purpose: once leaving the gateway and once arriving at the bank, which is a transfer between two accounts you hold and reads correctly as one.
An open-banking endpoint is reachable two ways, and only one is regulated
Reading somebody else’s account is an account information service: it needs a licensed provider and a qualified certificate, and this product is not one. Reading your own accounts under a direct agreement with your own bank is not — the bank issues you ordinary credentials, and it is the same interface, so the mapping here is already the right one. That is the route to ask your bank for, and it is what the screen means when it says a bank needs a direct agreement.
Practically: ask your relationship manager for corporate or business API access to your own accounts, not for open banking. Some banks reserve those programmes for larger customers, and if yours does, uploading a statement keeps working exactly as it does.
Importing a statement
CSV or Excel, whichever your bank gives you. Which format arrives is not really your choice — plenty of banks default to a workbook, and plenty of accountants forward the one they were sent. Which of the two it is is decided from the file itself rather than from its name, because an extension is something somebody typed and is wrong in both directions. A workbook is read exactly as far as its first sheet.
A PDF statement is not read, and that is deliberate
A PDF has no columns — only text at positions on a page — so reading one means guessing which number was in which column, and a guessed amount is worse than no amount: it imports cleanly, reconciles to nothing, and nobody can see why. Every bank that sends a PDF also offers CSV or Excel from the same screen.
CSV, Excel, ISO 20022 camt.053 or SWIFT MT940 — whichever your bank exports. The format is decided from the content rather than the file name, because an extension is something a person typed and is wrong in both directions: a workbook saved as .csv would come out as one column of nonsense, and a genuine CSV named .xlsx would be refused as corrupt. The importer then proposes which column is which and shows you what it made of the file before anything is written.
The awkward parts are handled rather than assumed away: the byte-order mark spreadsheets write at the start of a file, semicolon delimiters used everywhere decimal commas are, comma decimals themselves, day-first dates, and the debit-and-credit-in-two-columns layout that banks use instead of one signed amount. In a workbook, a date is a number and only its style says otherwise — without reading that, every date column arrives as five-digit integers and the import fails on every row at once.
One bad row never fails the file
Three hundred lines with one unreadable date import 299 and report the one with its line number. All-or-nothing means the import dies, nobody can tell which row did it, and the file gets bisected by hand — the same call the CRM import makes, for the same reason.
Re-importing the same statement changes nothing. Each movement is identified by the account, the date, the amount and the bank's own text, so the overlap between two exports is skipped rather than duplicated.
Matching
For an unexplained movement, candidate documents are suggested with the reasons attached — never applied on their own. The reasons are the point: a suggestion you cannot see the basis for is one nobody trusts twice.
| Signal | Weight |
|---|---|
| The amount is exact | Strongest |
| The document's reference appears in the bank's text | Strong |
| The amount is within a unit — a rounding or a fee | Moderate |
| The counterparty matches | Moderate |
| The date is close | Only ever a tiebreaker |
Date proximity never suggests on its own
It breaks a tie between candidates that already matched on something real. Left to suggest by itself, the monthly bank charge gets offered against whichever supplier invoice happens to be nearest in the calendar — which was a live bug, and the fix is that a candidate scoring nothing on the real signals scores nothing at all.
Settling a movement records a payment for it. A movement larger than what is outstanding settles only the outstanding part, so a transfer covering three invoices does not overpay the first one it is applied to.
Explaining
Not every movement has a document. Bank charges, interest, a transfer between your own accounts, a director's loan — these are explained in words rather than matched, and an explained movement stops being an open item.
Where the ledger is switched on you also choose which account it belongs to, and explaining it posts it. The two are deliberately separate: the words are what make a movement explained, and an account is what makes it posted. "I know what this is and I do not know where it goes yet" is a real state, and forcing an account would have somebody pick whichever was least wrong.
Detaching a settlement puts the balance back and removes the payment it created. Detaching an explanation returns the movement to unexplained. Neither leaves anything behind.
What is standing
Money out and money in are reported separately, never netted
They are different problems. Money out with no purchase invoice is the taxable one — that is the 20% in Georgia and the audit finding in the UAE. Money in with no sales invoice says an invoice was never raised at all, which no amount of chasing debtors will surface, because the debt was never recorded. A single net figure hides both.
Exposure is reported per currency and never converted, for the same reason documents are not: a figure converted at today's rate is not the figure anybody has to explain.