Help centre / Developers

Your systems,
straight into Brillrec.

Send ledger and bank lines from your own systems, tell Brillrec when a period is complete, and follow each reconciliation.

Open Brillrec
01 / Getting started

Keys and access.

The 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.

PermissionAllows
entries:writeList bank accounts, send ledger and bank lines, declare periods complete.
runs:readRead 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.

02 / Accounts

Find the account id.

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).

03 / Sending lines

Send lines as they happen.

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"}
]}
FieldMeaning
roleledger (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_idOptional, but they make matching much better. session_id is the NIBSS NIP session id.
deletedtrue 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.

04 / Closing a period

Say when a period is complete.

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"}}
  • The check: the previous closing balance plus the lines you sent must equal your closing balance. If agrees is false, a line is missing or wrong: look at difference, fix the lines and declare again.
  • An empty period counts as “no activity” only once it is declared, with a closing balance equal to the last one.
  • Once you declare any period for a side, Brillrec waits for each period’s declaration before it loads that period, so a reconciliation never starts on half-sent data. The closing balance also fills in the opening and closing balances.
  • A period can’t be declared before it ends. Declaring it again replaces the earlier declaration. GET /v1/accounts/12/periods?role=bank lists them.
05 / Following reconciliations

Status and totals.

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.

06 / Webhooks

Be told when something happens.

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.

07 / Examples

Copy, then adapt.

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());
08 / Errors

What each answer means.

StatusMeaning
200Done. For lines, check rejected.
400The 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).
401The 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.
403The key lacks the permission, or the plan doesn’t include the API.
404Not found, or not in your organisation.
413More than 8 MB in one request: send smaller batches.
429More than 120 requests a minute (30 in a sandbox): wait a minute and retry.
09 / Sandbox

Build against a sandbox.

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.

  1. An administrator opens Settings → API, webhooks & sandbox, chooses Create sandbox and confirms that no real customer data will go in it. Brillrec makes Your Company (Sandbox).
  2. It starts with two bank accounts fed by the API, and last month’s cash-book and bank lines already sent and declared complete, with the usual differences: bank charges, a cheque not yet presented, a lodgement in transit.
  3. Invite your developers to the sandbox only, from its Users & roles. They never see your live organisation. Your own administrators can open it at any time.
  4. In the sandbox, create a key: it starts rk_test_. Use the same addresses as live; the key decides where the lines go.
  5. Generate test lines adds a month of made-up lines to an account whenever you need more.
AreaIn a sandbox
Limits2,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 offBank and accounting connections, SFTP, statements by email, daily reconciliations, the close tracker, reminder emails and billing.
DownloadsEvery sign-off pack, Excel and CSV file is marked SANDBOX - TEST DATA - NOT FOR AUDIT.
ClearingTest data is cleared after 90 days without use (people, keys and webhooks stay), or at any time with Clear test data.
Your planThe 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.

Check before going live

  1. Send a few lines, then send the same batch again: the answer should show them replaced, not added twice.
  2. Send one bad line on purpose and check it comes back under rejected.
  3. Delete a line with "deleted": true.
  4. Declare the period with a closing balance and check agrees is true.
  5. Check the reconciliation Brillrec starts for that period: lines, signs and balances.
  6. Add a webhook to a test address and check your signature check accepts it.
  7. Then create a live key (rk_live_) in your live organisation and switch your system over. Nothing from the sandbox is carried across.
Still stuck?

Ask us.

Email hello@brillrec.com with what you were doing and what you saw, and we will help.

Email Brillrec
Brillrec demonstration

Let’s look at your workflow.

Tell us a little about your organisation. We prepare an email draft for you to review and send.

Prefer to try it yourself? Start your 14-day free trial.

Step 1 of 2 · Your details

Your details
Email us directly