The API and webhooks

Your account

Connect your own programs to kBooks. An API key lets a program read your books - invoices, bills, payments, contacts, accounts and ledger entries - and, if you allow it, post a payroll provider's report. A webhook tells a program the moment something changes.

Both live in Books settings > Connections > API and webhooks. Only the books' owner level (the level that can give others access) can make or change them, and a key or endpoint that covers several companies can be changed only by someone who owns the books of every one of them; anyone else sees it marked Shared, read-only. They belong to the company's account, so they keep working if the person who made them leaves.

Making an API key

Choose New API key, give it a name after what will use it ("Reporting dashboard"), choose Read only or Read and post payroll, and tick the companies it may read. The key is shown once: copy it then. kBooks keeps only a fingerprint of it, so a lost key cannot be shown again - revoke it and make a new one.

The list shows each key's companies, when it was last used and how many requests it made in the last 30 days. Revoke stops it at once.

A key never widens on its own: a company added to the account later is not included, and a company moved to another account stops answering at once.

Using the API

Send the key in a header to https://<your kBooks address>/api/v1:

`` curl https://kbooks.example.com/api/v1/companies \ -H "Authorization: Bearer kb_live_..." ``

| Route | What it returns | |---|---| | GET /api/v1/companies | the companies the key may read | | GET /api/v1/companies/{id}/accounts | the chart of accounts | | GET /api/v1/companies/{id}/customers and /vendors | contacts (a tax ID is never sent, only whether one is on file) | | GET /api/v1/companies/{id}/invoices | invoices with their lines; filter with status, from, to, customer | | GET /api/v1/companies/{id}/bills | bills with their lines; status, from, to, vendor | | GET /api/v1/companies/{id}/payments | customer payments and the invoices they paid; from, to, customer | | GET /api/v1/companies/{id}/transactions | posted ledger entries with their lines; from, to, account | | GET /api/v1/companies/{id}/{list}/{recordId} | one record from any of the lists above; a customer read answers only a customer, a vendor read only a vendor |

  • Lists come newest first, 50 at a time (limit up to 100). When hasMore is true, pass nextCursor back as cursor for the next page.
  • Dates are YYYY-MM-DD; times carry their offset. Money is a string with two decimals ("1250.00"), never a rounded number.
  • Limits: 120 requests a minute per key. Past that, the answer is 429 with a Retry-After header.
  • Filters: each list takes only its own (customer for invoices and payments, vendor for bills, account for transactions); another list's filter is refused with a 400 rather than ignored.
  • Errors read { "error": { "code": "...", "message": "..." } }: 400 for a bad filter, 401 for a missing or revoked key, 404 for a company or record the key cannot see, 429 for too many requests.

Posting payroll through the API

A key made with Read and post payroll can send a payroll provider's report the same way the Payroll import screen takes a file:

`` POST /api/v1/companies/{id}/payroll-runs { "provider": "Gusto", "rows": [["Check Date", "Account", "Debit", "Credit"], ...] } ``

It uses the mapping you saved for that provider on the Payroll import screen, so import one of its reports there once first. Add ?dryRun=true to see what would post without posting. A pay date that is already posted, or that falls in a locked period, is refused, and sending the same report twice at the same moment posts it once. Each run is in the books' history under the key's name.

Webhooks

Choose Add an endpoint, enter an https address, tick the events and the companies, and copy the signing secret (shown once). An endpoint starts listening the moment it is added, and so does a company added to it later: nothing from before is replayed. The events:

  • an invoice, a bill or a customer payment is created, changes (paid, edited, voided) or is deleted;
  • a customer or vendor is added or changes;
  • a payroll run is posted.

Each delivery is a POST of JSON: { "id", "type", "occurredAt", "companyId", "data" }, where data is the record as the API returns it at that moment (and null, with "deleted": true, for a deletion). The id is unique per event: use it to ignore a repeat, because a delivery can arrive more than once and events are not guaranteed to arrive in order.

Checking it came from kBooks. The Kbooks-Signature header reads t=<seconds>,v1=<signature>, where the signature is the HMAC-SHA256 of <seconds>.<the body exactly as received> with your signing secret, in hex. Recompute it and compare; refuse anything older than five minutes. In Node:

`` const parts = header.split(",").map((p) => p.split("=")); const t = parts.find(([k]) => k === "t")[1]; const mine = crypto.createHmac("sha256", secret).update(${t}.${body}).digest("hex"); const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300; const ok = fresh && parts.some(([k, v]) => k === "v1" && v.length === mine.length && crypto.timingSafeEqual(Buffer.from(v), Buffer.from(mine))); ``

After Make a new signing secret, deliveries carry a second v1= signed with the old secret for 24 hours, so you can switch your receiver over without missing anything.

When they go out. Deliveries go out after each change while your books are in use. A delivery that fails (no answer within 10 seconds, or any answer that is not 2xx) is retried after 1 minute, 5 minutes, 30 minutes, 2, 6, 12 and 24 hours; a retry that falls due while nobody is using the books goes out the next time somebody does. Twenty failures in a row switch the endpoint off, with the reason shown, until you switch it back on. Deliveries waiting while it is off are kept, and go out once it is back on. Each delivery must answer within 10 seconds in all, however slowly the endpoint sends its reply.

Send a test POSTs a signed ping straight away. Delivery log lists every delivery with its answer, and Resend puts one back in the queue (a skipped one too); Deliver now checks for new events and sends what is due at once.

The address must be on the public internet, over https on the standard port; kBooks refuses to deliver to private network addresses and does not follow redirects.

Your AI assistant (MCP)

An AI assistant that speaks MCP (the Model Context Protocol) can read your books with one of your API keys. Its server address is shown in the Your AI assistant box under API and webhooks; it is your kBooks address followed by /api/mcp. In the assistant, add a remote MCP server at that address and send the key as Authorization: Bearer kb_live_.... In Claude Code, for example:

`` claude mcp add --transport http kbooks https://<your kBooks address>/api/mcp --header "Authorization: Bearer kb_live_..." ``

The server answers one JSON-RPC message per request; a batch (an array of messages) is refused.

The assistant can list your companies, accounts, customers and vendors, read invoices, bills, payments and ledger entries, and look at one invoice or bill in full. It can never create, change or delete anything: there is no tool for it to do so.

  • It sees only the companies its key names, like any program using the key.
  • It works only while AI assistance is switched on for your account (the Plan page). Switched off, every read is refused, and the refusal is recorded.
  • Every read is listed in your account's AI activity on the Plan page: which company, what it read, and through which key. Revoke the key to cut it off.