/api/public/v1/healthno key neededService status. No API key required.
curl https://bizbrada.com/api/public/v1/health{
"status": "ok",
"service": "bizbrada-api",
"version": "1.0.0"
}GET /api/public/v1/sandbox/healthPush sales and expenses from your own till, POS or app into BizBrada, raise invoices, and read the books back. Same records, same tax position, no double entry.
Not a developer? See what you can connect in plain English — POS, online store, your own app.
# 1. Confirm the API is up
curl https://bizbrada.com/api/public/v1/health
# 2. Read your books
curl "https://bizbrada.com/api/public/v1/transactions?limit=5" \
-H "Authorization: Bearer bb_YOUR_KEY"
# 3. Record a sale
curl -X POST https://bizbrada.com/api/public/v1/transactions \
-H "Authorization: Bearer bb_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "kind": "sale", "amount_minor": 250000, "occurred_on": "2026-01-14", "description": "Two cartons" }'A JSON over HTTPS API. Every request is scoped to one business — the business the key belongs to — so there is no tenant parameter to pass and no way to read another business's records.
https://bizbrada.com/api/public/v1Included on Scale. Available on Growth as a paid add-on. Not available on Free or Starter. See pricing.
Create keys in Settings → API. A key is shown once, at creation. We store only a hash of it, so we cannot show it to you again — if it is lost, revoke it and make a new one.
curl https://bizbrada.com/api/public/v1/transactions \
-H "Authorization: Bearer bb_YOUR_KEY"GET endpoints.
POST, PATCH and DELETE endpoints.
Manage webhook subscriptions.
Retry-After header.fx_rate and fx_rate_date; we freeze the Naira value on the record, and tax and reports read that Naira value.limit (1–100, default 20) and offset. Responses carry meta: { limit, offset, total }.YYYY-MM-DD). Timestamps are ISO 8601 in UTC.{ data: ... }; errors return { error: "message" }.Idempotency-Key header on any write — see Idempotency. On transactions you can also supply your own id on create.Networks drop. When a create or update times out you rarely know whether it landed. Send an Idempotency-Key header and you can retry the exact same call without risking a duplicate record in the books.
Optional, but recommended on every write
Send Idempotency-Key on POST, PUT, PATCH and DELETE. Any unique string works — a UUID or your own order reference. Requests without the header behave exactly as before.
Retries replay, they do not rewrite
The first call runs normally and we store its status and body for 24 hours against your API key. A retry with the same key and the same body returns that stored response with the header Idempotency-Replayed: true.
One key, one request
Reusing a key with a different body returns 409. Retrying while the first call is still running also returns 409 — wait a moment and try again.
Failures free the key
If we return a 5xx, the key is released so the same key can be retried safely. Keys expire after 24 hours.
# Send the same key when you retry a create or update
curl -X POST https://bizbrada.com/api/public/v1/transactions \
-H "Authorization: Bearer bb_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sale-2026-01-14-000431" \
-d '{ "kind": "sale", "amount_minor": 250000, "occurred_on": "2026-01-14", "description": "Two cartons" }'
# A repeat of the same call returns the original response, with:
# Idempotency-Replayed: trueEvery endpoint below is mirrored under /sandbox. Call it with no API key and it replies with the same sample response you see in these docs — so you can prove your client, your headers and your JSON parsing all work before you hold a real key, and without touching anybody's books.
/api/public/v1 for /api/public/v1/sandbox in any path. Path ids can be anything — /sandbox/invoices/test works.{ sandbox: true, endpoint, response }. The real payload is in response; the live endpoint returns that shape on its own.GET /api/public/v1/sandbox lists every path the sandbox answers.curl https://bizbrada.com/api/public/v1/sandbox/transactions
# Directory of everything the sandbox answers
curl https://bizbrada.com/api/public/v1/sandboxEach endpoint card below has a Try it button that runs the sandbox call in your browser.
Check that the API is reachable before you wire anything else up.
/api/public/v1/healthno key neededService status. No API key required.
curl https://bizbrada.com/api/public/v1/health{
"status": "ok",
"service": "bizbrada-api",
"version": "1.0.0"
}GET /api/public/v1/sandbox/healthSales, expenses and purchases. Amounts are in minor units (kobo for Naira) and every record also stores its Naira value.
/api/public/v1/transactionsscope: readList transactions, newest first by date recorded.
| Name | Type | Notes |
|---|---|---|
limit | integer 1–100 | Page size. Defaults to 20. |
offset | integer | Rows to skip. Defaults to 0. |
from | YYYY-MM-DD | Only records on or after this date. |
to | YYYY-MM-DD | Only records on or before this date. |
curl "https://bizbrada.com/api/public/v1/transactions?limit=2&from=2026-01-01" \
-H "Authorization: Bearer bb_YOUR_KEY"{
"data": [
{
"id": "0f0c3f2a-6d1e-4a0e-9a1b-8a3f5d2c1e77",
"business_id": "9d2b1c44-1f4a-4a1d-8c9d-2e6b0f3a7b21",
"kind": "sale",
"amount_minor": 250000,
"base_amount_minor": 250000,
"currency": "NGN",
"fx_rate": 1,
"fx_rate_date": null,
"occurred_on": "2026-01-14",
"description": "Two cartons of tomato paste",
"counterparty": "Mama Chidi Stores",
"payment_method": "transfer",
"account_id": null,
"contact_id": null,
"invoice_id": null,
"tax_treatment": "vatable",
"reconciliation_status": "unmatched",
"source": "import",
"created_at": "2026-01-14T09:12:44.120Z",
"updated_at": "2026-01-14T09:12:44.120Z",
"deleted_at": null
}
],
"meta": { "limit": 2, "offset": 0, "total": 148 }
}GET /api/public/v1/sandbox/transactions/api/public/v1/transactionsscope: writeRecord one transaction. Send your own id (a UUID) to make the call idempotent — repeating it will not create a duplicate.
| Name | Type | Notes |
|---|---|---|
id | uuid | Optional. Your own id, used for idempotent retries. |
kindrequired | sale | expense | purchase | other | What kind of record this is. |
amount_minorrequired | integer | Amount in minor units, e.g. 250000 = ₦2,500.00. |
currency | 3-letter code | NGN, USD, GBP or EUR. Defaults to NGN. |
fx_rate | number | Rate to Naira on the day. Defaults to 1. |
fx_rate_date | YYYY-MM-DD | Date the rate applies to. |
occurred_onrequired | YYYY-MM-DD | Date of the transaction. |
descriptionrequired | string ≤ 500 | What it was for. |
counterparty | string ≤ 200 | Who you sold to or bought from. |
payment_method | string ≤ 100 | Cash, transfer, POS, etc. |
account_id | uuid | Chart of accounts entry. |
contact_id | uuid | Customer or supplier. |
tax_treatment | vatable | exempt | zero_rated | wht_applicable | Defaults to vatable. |
source | quick_entry | ocr | import | recurring | Defaults to import. |
curl -X POST https://bizbrada.com/api/public/v1/transactions \
-H "Authorization: Bearer bb_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "sale",
"amount_minor": 250000,
"occurred_on": "2026-01-14",
"description": "Two cartons of tomato paste",
"counterparty": "Mama Chidi Stores"
}'{
"data": {
"id": "0f0c3f2a-6d1e-4a0e-9a1b-8a3f5d2c1e77",
"kind": "sale",
"amount_minor": 250000,
"base_amount_minor": 250000,
"currency": "NGN",
"occurred_on": "2026-01-14",
"description": "Two cartons of tomato paste",
"tax_treatment": "vatable",
"source": "import"
}
}POST /api/public/v1/sandbox/transactions/api/public/v1/transactions/{id}scope: readFetch one transaction.
curl https://bizbrada.com/api/public/v1/transactions/0f0c3f2a-6d1e-4a0e-9a1b-8a3f5d2c1e77 \
-H "Authorization: Bearer bb_YOUR_KEY"{ "data": { "id": "0f0c3f2a-...", "kind": "sale", "amount_minor": 250000 } }GET /api/public/v1/sandbox/transactions/sample-id/api/public/v1/transactions/{id}scope: writeChange a transaction. Send only the fields you want changed. Changing amount, currency or rate recalculates the stored Naira value.
| Name | Type | Notes |
|---|---|---|
kind | sale | expense | purchase | other | Optional. |
amount_minor | integer | Optional. |
currency | 3-letter code | Optional. |
fx_rate | number | Optional. |
occurred_on | YYYY-MM-DD | Optional. |
description | string ≤ 500 | Optional. |
tax_treatment | vatable | exempt | zero_rated | wht_applicable | Optional. |
curl -X PATCH https://bizbrada.com/api/public/v1/transactions/0f0c3f2a-6d1e-4a0e-9a1b-8a3f5d2c1e77 \
-H "Authorization: Bearer bb_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "description": "Two cartons — corrected" }'{ "data": { "id": "0f0c3f2a-...", "description": "Two cartons — corrected" } }PATCH /api/public/v1/sandbox/transactions/sample-id/api/public/v1/transactions/{id}scope: writeRemove a transaction. The record is kept in your audit trail.
curl -X DELETE https://bizbrada.com/api/public/v1/transactions/0f0c3f2a-6d1e-4a0e-9a1b-8a3f5d2c1e77 \
-H "Authorization: Bearer bb_YOUR_KEY"{ "deleted": true, "id": "0f0c3f2a-6d1e-4a0e-9a1b-8a3f5d2c1e77" }DELETE /api/public/v1/sandbox/transactions/sample-idYour chart of accounts, read-only over the API.
/api/public/v1/accountsscope: readList the chart of accounts.
| Name | Type | Notes |
|---|---|---|
limit | integer 1–100 | Page size. Defaults to 20. |
offset | integer | Rows to skip. Defaults to 0. |
curl "https://bizbrada.com/api/public/v1/accounts?limit=50" \
-H "Authorization: Bearer bb_YOUR_KEY"{
"data": [
{
"id": "3a91f0b2-2c11-4c8f-9b6a-77c0d1c0aa10",
"business_id": "9d2b1c44-...",
"name": "Sales",
"code": "4000",
"type": "income",
"is_default": true,
"archived_at": null,
"created_at": "2025-11-02T10:00:00.000Z",
"updated_at": "2025-11-02T10:00:00.000Z"
}
],
"meta": { "limit": 50, "offset": 0, "total": 18 }
}GET /api/public/v1/sandbox/accountsCustomers and suppliers. Needed before you can raise an invoice.
/api/public/v1/contactsscope: readList contacts alphabetically.
| Name | Type | Notes |
|---|---|---|
kind | customer | supplier | both | Filter by contact type. |
limit | integer 1–100 | Page size. Defaults to 20. |
offset | integer | Rows to skip. Defaults to 0. |
curl "https://bizbrada.com/api/public/v1/contacts?kind=customer" \
-H "Authorization: Bearer bb_YOUR_KEY"{
"data": [
{
"id": "c4a1f0a9-11b2-4e6c-9d3a-0f7c2b8e1d54",
"kind": "customer",
"name": "Mama Chidi Stores",
"email": "chidi@example.com",
"phone": "+2348030000000",
"address": "12 Balogun Street, Lagos",
"tin": "12345678-0001",
"is_corporate": false,
"payee_class": "unincorporated"
}
],
"meta": { "limit": 20, "offset": 0, "total": 34 }
}GET /api/public/v1/sandbox/contacts/api/public/v1/contactsscope: writeCreate a customer or supplier.
| Name | Type | Notes |
|---|---|---|
kindrequired | customer | supplier | both | Contact type. |
namerequired | string ≤ 200 | Business or person name. |
email | Optional. | |
phone | string ≤ 50 | Optional. |
address | string ≤ 500 | Optional. |
tin | string ≤ 50 | Tax identification number, if you have it. |
is_corporate | boolean | Defaults to false. |
payee_class | company | individual | unincorporated | Drives WHT rates. Defaults to company. |
curl -X POST https://bizbrada.com/api/public/v1/contacts \
-H "Authorization: Bearer bb_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "kind": "customer", "name": "Mama Chidi Stores", "payee_class": "unincorporated" }'{ "data": { "id": "c4a1f0a9-...", "kind": "customer", "name": "Mama Chidi Stores" } }POST /api/public/v1/sandbox/contactsInvoices with line items. VAT and WHT are calculated server-side from the line items and the rate you set — you never post totals yourself.
/api/public/v1/invoicesscope: readList invoices, newest issue date first.
| Name | Type | Notes |
|---|---|---|
status | draft | sent | part_paid | paid | cancelled | Filter by status. |
from | YYYY-MM-DD | Issued on or after. |
to | YYYY-MM-DD | Issued on or before. |
limit | integer 1–100 | Page size. Defaults to 20. |
offset | integer | Rows to skip. Defaults to 0. |
curl "https://bizbrada.com/api/public/v1/invoices?status=sent" \
-H "Authorization: Bearer bb_YOUR_KEY"{
"data": [
{
"id": "b71c2f88-3e4a-4a10-8f2c-9c1e5a7d2b03",
"number": "INV-0042",
"seq": 42,
"status": "sent",
"contact_id": "c4a1f0a9-...",
"issue_date": "2026-01-10",
"due_date": "2026-01-24",
"currency": "NGN",
"fx_rate": 1,
"subtotal_minor": 1000000,
"vat_minor": 75000,
"wht_minor": 50000,
"total_minor": 1075000,
"amount_due_minor": 1025000,
"paid_minor": 0
}
],
"meta": { "limit": 20, "offset": 0, "total": 42 }
}GET /api/public/v1/sandbox/invoices/api/public/v1/invoicesscope: writeCreate a draft invoice with its line items.
| Name | Type | Notes |
|---|---|---|
contact_idrequired | uuid | Who the invoice is for. |
issue_daterequired | YYYY-MM-DD | Invoice date. |
due_date | YYYY-MM-DD | Optional payment due date. |
currency | 3-letter code | Defaults to NGN. |
fx_rate | number | Rate to Naira. Defaults to 1. |
wht_rate | number 0–100 | Withholding tax rate to deduct. Defaults to 0. |
notes | string ≤ 2000 | Shown on the invoice. |
itemsrequired | array (1–100) | description, quantity, unit_price_minor, vat_rate, account_id. |
curl -X POST https://bizbrada.com/api/public/v1/invoices \
-H "Authorization: Bearer bb_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "c4a1f0a9-11b2-4e6c-9d3a-0f7c2b8e1d54",
"issue_date": "2026-01-10",
"due_date": "2026-01-24",
"wht_rate": 5,
"items": [
{ "description": "Consulting — January", "quantity": 1, "unit_price_minor": 1000000, "vat_rate": 7.5 }
]
}'{
"data": {
"id": "b71c2f88-...",
"number": "INV-0042",
"status": "draft",
"subtotal_minor": 1000000,
"vat_minor": 75000,
"wht_minor": 50000,
"total_minor": 1075000,
"amount_due_minor": 1025000
}
}POST /api/public/v1/sandbox/invoices/api/public/v1/invoices/{id}scope: readFetch one invoice with its line items.
curl https://bizbrada.com/api/public/v1/invoices/b71c2f88-3e4a-4a10-8f2c-9c1e5a7d2b03 \
-H "Authorization: Bearer bb_YOUR_KEY"{
"data": {
"id": "b71c2f88-...",
"number": "INV-0042",
"status": "sent",
"items": [
{
"id": "e2d1...",
"description": "Consulting — January",
"quantity": 1,
"unit_price_minor": 1000000,
"vat_rate": 7.5,
"line_total_minor": 1000000
}
]
}
}GET /api/public/v1/sandbox/invoices/sample-id/api/public/v1/invoices/{id}scope: writeChange status, due date or notes.
| Name | Type | Notes |
|---|---|---|
status | draft | sent | part_paid | paid | cancelled | Optional. |
due_date | YYYY-MM-DD | Optional. |
notes | string ≤ 2000 | Optional. |
curl -X PATCH https://bizbrada.com/api/public/v1/invoices/b71c2f88-3e4a-4a10-8f2c-9c1e5a7d2b03 \
-H "Authorization: Bearer bb_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "paid" }'{ "data": { "id": "b71c2f88-...", "status": "paid" } }PATCH /api/public/v1/sandbox/invoices/sample-id/api/public/v1/invoices/{id}scope: writeCancel an invoice. Invoices are never hard-deleted — the number stays in your books.
curl -X DELETE https://bizbrada.com/api/public/v1/invoices/b71c2f88-3e4a-4a10-8f2c-9c1e5a7d2b03 \
-H "Authorization: Bearer bb_YOUR_KEY"{ "ok": true }DELETE /api/public/v1/sandbox/invoices/sample-idThe shape of each object the API returns. Fields marked | null may be absent.
Register an HTTPS endpoint in Settings → API and choose the events you want. We show the signing secret once; keep it on your server.
| Event | Sent when |
|---|---|
transaction.created | A new record enters the books. |
transaction.updated | An existing record is changed. |
invoice.created | An invoice is raised. |
invoice.updated | Status, due date or notes change. |
invoice.paid | An invoice is fully settled. |
filing.status_changed | A return moves through preparation, review or filing. |
Each delivery is a POST with a JSON body and an x-bizbrada-signature header: an HMAC-SHA256 of the raw body, hex encoded, using your signing secret. Verify it with a constant-time compare before you trust the payload. Reply 2xx quickly; anything else is retried with backoff.
import { createHmac, timingSafeEqual } from "crypto";
// Raw body — verify before JSON.parse.
const signature = req.headers["x-bizbrada-signature"];
const expected = createHmac("sha256", process.env.BIZBRADA_WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
const ok =
signature.length === expected.length &&
timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!ok) return res.status(401).send("Invalid signature");Payment callbacks come in on the same versioned base. GET /api/public/v1/webhooks lists every receiver and the exact URL to paste into a provider dashboard. Card payments are handled at POST /api/public/v1/webhooks/flutterwave, which checks the verif-hash header and then re-verifies the transaction with the provider before anything is applied. The older /api/public/hooks/flutterwave path still answers.
To confirm your wiring, send anything to /api/public/v1/webhooks/test. It echoes the method, the body it read and whether your verif-hash header would have been accepted — without reading or writing a single row.
curl -X POST https://bizbrada.com/api/public/v1/webhooks/test \
-H "content-type: application/json" \
-H "verif-hash: $FLUTTERWAVE_WEBHOOK_HASH" \
-d '{"event":"charge.completed","data":{"tx_ref":"test-ref"}}'Errors always come back as JSON with a single error string. We never leak another business's data in an error message.
{ "error": "Invalid transaction: amount_minor is required" }{ "error": "Missing or invalid API key" }{ "error": "Insufficient scope. This endpoint requires the 'write' scope." }{ "error": "Resource not found" }{ "error": "Rate limit exceeded. Slow down or increase your rate limit in Settings > API." }{ "error": "Internal server error" }