Accounts: bank, POS and investments
Bank, POS and payments, and investment accounts, each reconciled period by period.
Read the guideSend ledger and bank lines from your own systems, tell Brillrec when a period is complete, and follow each reconciliation.
Open BrillrecThe API and webhooks are part of the Business plan; every paid plan can also try them in a sandbox. An administrator creates a key under Settings → API, webhooks & sandbox and chooses what it may do. Brillrec shows the key once: store it in your system’s secret store, never in code or emails. Revoke it there at any time.
| Permission | Allows |
|---|---|
entries:write | List bank accounts, send ledger and bank lines, declare periods complete. |
runs:read | Read reconciliation status and totals. |
Send the key with every request, over HTTPS, to https://app.brillrec.com (or your organisation’s own Brillrec address):
Authorization: Bearer rk_live_1a2b3c4d_…
Live keys start rk_live_; sandbox keys start rk_test_ and only work in the sandbox (keys made before October 2026 start rk_ and keep working). Limits: 120 requests a minute per key (30 in a sandbox), 5,000 lines and 8 MB per request.
GET /v1/accounts
{"accounts": [{"id": 12, "name": "GTBank main account", "bank": "GTBank",
"number": "0123456789", "currency": "NGN"}]}
Then, under Accounts → ⋯ → Data sources, set the account’s ledger side, bank side or both to Sent by your own system (Brillrec API).
POST /v1/accounts/12/entries?role=bank
Content-Type: application/json
{"entries": [
{"id": "TXN-000981", "date": "2026-09-30", "amount": "-1500.50",
"reference": "FT2609300981", "description": "NIP transfer to Ada Okafor",
"counterparty": "Ada Okafor", "session_id": "000013260930101500000000000981"},
{"id": "TXN-000982", "date": "2026-09-30T23:30:00Z", "amount": "250000"}
]}
| Field | Meaning |
|---|---|
role | ledger (your cash book) or bank (the bank statement), in the address. |
id (required) | Your own unique id for the line, up to 100 characters. Sending the same id again replaces the line, so a retry never creates a duplicate. |
date (required) | YYYY-MM-DD, or a date and time with a time zone such as 2026-09-30T23:30:00Z, which is placed on your organisation’s day (23:30 UTC on 30 September is 1 October in Lagos). |
amount (required) | In the account’s currency, as a string or number. Positive = money into the bank account, negative = money out, for both roles. |
reference, description, counterparty, session_id | Optional, but they make matching much better. session_id is the NIBSS NIP session id. |
deleted | true removes the line with that id. |
{"added": 1, "replaced": 1, "deleted": 0,
"rejected": [{"index": 4, "id": "TXN-000985", "reason": "date must be YYYY-MM-DD ..."}]}
A line that can’t be read is listed under rejected with the reason; the others are saved. Fix it and send it again.
Brillrec can’t tell “nothing happened” from “your system hasn’t sent the lines yet”. Once a period has ended and every line is sent, declare it, ideally with the account’s closing balance:
POST /v1/accounts/12/periods
{"role": "bank", "period_start": "2026-09-01", "period_end": "2026-09-30",
"closing_balance": "500000.00"}
{"role": "bank", "period_end": "2026-09-30", "complete": true, "closing_balance": "500000.00",
"check": {"agrees": true, "previous_closing": "498500.50", "lines_total": "1499.50",
"expected_closing": "500000.00", "difference": "0.00"}}
agrees is false, a line is missing or wrong: look at difference, fix the lines and declare again.GET /v1/accounts/12/periods?role=bank lists them.GET /v1/runs?limit=50
GET /v1/runs/341
{"id": 341, "name": "GTBank main account", "kind": "two_file", "status": "open",
"awaiting_sign_off": false, "daily_check": false, "period": ["2026-09-01", "2026-09-30"],
"currency": "NGN", "bank_account_id": 12, "lines": 1355, "matched": 1093, "explained": 4,
"unexplained": 12, "unexplained_value_kobo": 1612988, "ai_suggestions_pending": 1, ...}
Only status and totals: no lines, names or narrations. Amounts ending in _kobo are in the smallest unit of the currency.
Add a webhook under Settings → API & webhooks with an HTTPS address and the events you want: run.ready, run.signed_off, approval.requested, daily_check.done. Each event carries ids, status and totals only.
{"id": "evt_3f9a1c7e5b2d8046aa91c3e7", "event": "run.signed_off",
"created": "2026-10-05T10:12:00Z", "data": {"id": 341, ...}}
Check every request’s signature before trusting it. The header is X-Recon-Signature: t=<unix time>,v1=<signature>, where the signature is the hex HMAC-SHA256 of <t>.<raw body> with the webhook’s secret:
import hmac, hashlib, time
def verified(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300: # refuse replays
return False
mac = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(mac, parts["v1"])
Answer with any 2xx status within 10 seconds. A failed delivery is retried after 1, 5 and 30 minutes, then 2 and 6 hours; after 20 failures in a row the webhook is switched off and your administrators are told.
curl
curl -X POST "https://app.brillrec.com/v1/accounts/12/entries?role=ledger" \
-H "Authorization: Bearer $BRILLREC_KEY" -H "Content-Type: application/json" \
-d '{"entries": [{"id": "JV-1001", "date": "2026-09-30", "amount": "-1500.50", "description": "Supplier payment"}]}'
Python
import os, requests
r = requests.post("https://app.brillrec.com/v1/accounts/12/entries", params={"role": "ledger"},
headers={"Authorization": f"Bearer {os.environ['BRILLREC_KEY']}"},
json={"entries": [{"id": "JV-1001", "date": "2026-09-30", "amount": "-1500.50",
"description": "Supplier payment"}]}, timeout=30)
r.raise_for_status()
print(r.json())
JavaScript (Node 18+)
const r = await fetch("https://app.brillrec.com/v1/accounts/12/periods", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.BRILLREC_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ role: "bank", period_end: "2026-09-30", closing_balance: "500000.00" }),
});
console.log(await r.json());| Status | Meaning |
|---|---|
200 | Done. For lines, check rejected. |
400 | The request can’t be used; detail says why (for example an unknown account, a closed account, more than 5,000 lines, or a sandbox’s monthly limit reached). |
401 | The key is missing, wrong or revoked, a test key was used outside its sandbox, or the sandbox is paused because the organisation’s plan isn’t active. |
403 | The key lacks the permission, or the plan doesn’t include the API. |
404 | Not found, or not in your organisation. |
413 | More than 8 MB in one request: send smaller batches. |
429 | More than 120 requests a minute (30 in a sandbox): wait a minute and retry. |
A sandbox is a separate Brillrec organisation with made-up data, for your developers or your software house to build and test against the API without touching live data. It comes with every paid plan, one per organisation.
rk_test_. Use the same addresses as live; the key decides where the lines go.| Area | In a sandbox |
|---|---|
| Limits | 2,000 lines sent and 2,000 lines reconciled a month, 3 accounts, 200 AI review items a month, 5 people besides your administrators, 30 requests a minute. |
| Switched off | Bank and accounting connections, SFTP, statements by email, daily reconciliations, the close tracker, reminder emails and billing. |
| Downloads | Every sign-off pack, Excel and CSV file is marked SANDBOX - TEST DATA - NOT FOR AUDIT. |
| Clearing | Test data is cleared after 90 days without use (people, keys and webhooks stay), or at any time with Clear test data. |
| Your plan | The sandbox works while your organisation’s plan is active. If it lapses, or the organisation is deleted, the sandbox pauses or is deleted with it. |
replaced, not added twice.rejected."deleted": true.agrees is true.rk_live_) in your live organisation and switch your system over. Nothing from the sandbox is carried across.Bank, POS and payments, and investment accounts, each reconciled period by period.
Read the guideThe controls your administrator can set, in plain language.
Read the guideRequired and recommended columns for every file you upload.
Read the guideEmail hello@brillrec.com with what you were doing and what you saw, and we will help.
Email Brillrec