Read and write your accounting data over REST, and receive signed events when things change. This documents the interface as it is being built — see the availability note below before you plan around it.
Availability: the API and webhooks are implemented and under active testing, but are not yet switched on in a released build. This page is published so you can review the design and tell us if it will not work for you. Want a heads-up the day it ships? Submit the form on the Integrations page.
Every request is authenticated with an API key sent as a bearer token. Keys are created inside the app under Settings → Integrations → API Keys by an admin or manager.
curl https://your-install.local:3000/api/customers \
-H "Authorization: Bearer bb_live_7fa39c21e4b85d06f1c2a930"
An X-API-Key header is accepted as an alternative if bearer tokens are awkward in your client.
A key is bound to one company. Multi-company installs issue a separate key per company, and the key decides which books it touches. There is no company header — sending one that disagrees with the key returns 403.
The full key is shown exactly once, at creation. We store only a SHA-256 hash plus a short display prefix, so we cannot recover it for you. Lost it? Revoke that key and issue another.
Because BizBooks Pro is desktop software, the base URL is your own installation, not a service we host. On the same machine that is http://localhost:3000. Reaching it from elsewhere on your network is a decision you make deliberately — see the security note at the end.
Each key carries scopes. Grant the narrowest that does the job.
| Scope | Grants |
|---|---|
read | GET on every supported resource. |
write | Everything read allows, plus POST and PUT. |
A key without the required scope gets 403 with a message naming the scope it needed.
API keys can only reach the endpoints below. This is an explicit allowlist, not a filter — an endpoint existing elsewhere in the app does not make it reachable with a key, and adding one to the public surface is a deliberate decision on our side.
| Method | Path | Scope |
|---|---|---|
| GET | /api/customers | read |
| GET | /api/customers/:id | read |
| POST | /api/customers | write |
| PUT | /api/customers/:id | write |
| GET | /api/vendors | read |
| POST | /api/vendors | write |
| GET | /api/invoices | read |
| GET | /api/invoices/:id | read |
| POST | /api/invoices | write |
| PUT | /api/invoices/:id | write |
| GET | /api/bills | read |
| POST | /api/bills | write |
| GET | /api/accounts | read |
| GET | /api/items | read |
| GET | /api/estimates | read |
| GET | /api/transactions | read |
| POST | /api/transactions | write |
curl -X POST http://localhost:3000/api/customers \
-H "Authorization: Bearer bb_live_..." \
-H "Content-Type: application/json" \
-d '{
"customer_name": "Riverside Dental",
"email": "ap@riversidedental.example",
"phone": "555-0142",
"city": "Boise",
"state": "ID"
}'
Invoices post to the general ledger exactly as they would if you had keyed them in, including tax and multi-currency handling. A draft invoice does not post until it is issued.
curl -X POST http://localhost:3000/api/invoices \
-H "Authorization: Bearer bb_live_..." \
-H "Content-Type: application/json" \
-d '{
"customer_id": 42,
"invoice_date": "2026-08-04",
"due_date": "2026-09-03",
"status": "sent",
"items": [
{ "description": "Consulting - August", "quantity": 12, "unit_price": 145.00 }
]
}'
customer_name, invoice_date, total_amount.YYYY-MM-DD. Timestamps come back as ISO 8601 in UTC.145.00, never 14500.Content-Type: application/json on anything with a body.Errors use standard status codes with a JSON body carrying an error string written for a human reading a log.
| Status | Meaning |
|---|---|
400 | The request was malformed or failed validation. |
401 | Key missing, invalid, revoked or expired. |
403 | Key is valid but lacks the scope, or the endpoint is not on the allowlist. |
404 | No such record in this company. |
429 | Rate limited. Back off and retry. |
500 | Our fault. Safe to retry an idempotent request. |
The default rate limit is 100 requests per 15 minutes per IP, matching the rest of the application.
Rather than polling, register an endpoint and we will POST to it when something happens. Add endpoints under Settings → Integrations → Webhooks.
| Event | Fires when |
|---|---|
invoice.created | An invoice is created, by any route. |
invoice.updated | An existing invoice is modified. |
invoice.paid | An invoice is settled in full. |
customer.created | A customer record is added. |
customer.updated | A customer record changes. |
vendor.created | A vendor record is added. |
bill.created | A bill is entered. |
payment.received | A customer payment is recorded. |
transaction.created | A journal entry is posted. |
Subscribe to * to receive everything, including events added later.
POST /your-endpoint
X-BizBooks-Event: invoice.created
X-BizBooks-Event-Id: evt_9c1f04ab77e2
X-BizBooks-Timestamp: 1786000000
X-BizBooks-Signature: 4f1c...<64 hex chars>
{
"id": "evt_9c1f04ab77e2",
"type": "invoice.created",
"created_at": "2026-08-04T15:12:09.441Z",
"data": {
"id": 1180,
"invoice_number": "INV-1042",
"customer_id": 42,
"total_amount": "1740.00",
"status": "sent"
}
}
Every delivery is signed with the secret shown once when you created the endpoint. Verify it before trusting the payload — otherwise anyone who learns your URL can post fake accounting events at you.
Sign the string {timestamp}.{raw body} with HMAC-SHA256 and compare to the header:
const crypto = require('crypto');
function verify(req, rawBody, secret) {
const timestamp = req.headers['x-bizbooks-timestamp'];
const signature = req.headers['x-bizbooks-signature'];
// Reject anything older than five minutes to blunt replay attacks.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'utf8'),
Buffer.from(expected, 'utf8')
);
}
Sign the raw body, not a re-serialized object. Parsing the JSON and stringifying it again reorders keys and changes whitespace, and the signature will never match. Capture the body as a string first.
X-BizBooks-Event-Id for idempotency. A retry reuses the same id, and a machine that was asleep may receive a burst at once. Treat a repeated id as already handled.https://.Both are being built on this API. Triggers will cover new invoice, invoice paid, new customer and new bill; actions will cover creating invoices and customers and recording payments. If you would rather not wait, the API and webhooks above give you everything those apps use.
BizBooks Pro runs on your hardware, so an API key is only useful to something that can reach your machine. That is a genuine security advantage — there is no public endpoint for an attacker to find. It also means that if you want an outside service to call in, opening that path is your decision, and you should treat an API key with the same care as the login it acts on behalf of.
Questions, or something here that will not work for your use case? Tell us — this is being finalized now, which is exactly when feedback is worth most.