Connect your own programs to TaDa Books. An API key lets a program read your books - invoices, bills, payments, contacts, accounts and ledger entries - and, if you allow it, add customers and vendors, write invoices and bills, record the money against them, and post a payroll provider's report. A webhook tells a program the moment something changes.
Both live in Connections (in the menu), on the API and webhooks row. 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 a reading key keeps working if the person who made it leaves; a key that writes stops writing once the person who made it no longer manages these books.
Making an API key
Choose New API key, give it a name after what will use it ("Reporting dashboard"), switch on what else it may do besides reading - Post payroll, Create invoices, bills and payments - and tick the companies it may use. The key is shown once: copy it then. TaDa Books 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 TaDa Books address>/api/v1:
`` curl https://tadabooks.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}/classes, /jobs, /items | what a document line can name; jobs filter by customer | | GET /api/v1/companies/{id}/bill-payments | bills you paid and which bills each paid; vendor | | 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 |
Customers and vendors also take name or email (exact, any case), so a program can find a contact it made before. Each account carries special for the accounts TaDa Books keeps itself (undeposited_funds, accounts_receivable, accounts_payable...), whatever you renamed them to.
- Lists come newest first, 50 at a time (
limitup to 100). WhenhasMoreis true, passnextCursorback ascursorfor 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-Afterheader. - Filters: each list takes only its own (
customerfor invoices and payments,vendorfor bills,accountfor 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.
The reference file, and a place to try it
The whole API, every route, field and answer, is published as an OpenAPI file at https://<your TaDa Books address>/api/v1/openapi.json. It holds no data, so it needs no key. Load it into an API client or a code generator and the requests are written for you. The API reference box under API and webhooks shows the address, with Copy and Download.
To try the API without touching real books, choose Open the demo on the sign-in page. Its two sample companies come with invoices, bills and ledger entries, and an API key made there (Connections, API and webhooks) reads and writes them like any other key. The demo and everything in it are deleted 48 hours later.
Writing through the API
A key made with Create invoices, bills and payments can write six things, each through the same checks the screens use - sales tax by address, the books lock, bill approvals, your numbering - and each saying "API key" and the key's name as who made it:
| Route | Creates | |---|---| | POST /api/v1/companies/{id}/customers, /vendors | a contact: name, and email, phone, address, notes, currencyCode; a vendor is1099; a customer taxExempt with taxExemptReason | | POST /api/v1/companies/{id}/invoices | an invoice: customerId, issueDate, lines (description, quantity, rate, accountId, and classId, jobId, itemId, taxable); optional number (the next one when left out), dueDate, terms ("Net 30" or its key), memo, discount, shipping, and payment for a sale paid at checkout | | POST /api/v1/companies/{id}/bills | a bill: vendorId, billDate, lines (amount, accountId, ...); optional number, dueDate, terms, memo | | POST /api/v1/companies/{id}/payments | money received: customerId, paymentDate, method, applications (invoiceId, amount); it goes to Undeposited Funds unless you name depositToAccountId | | POST /api/v1/companies/{id}/bill-payments | bills paid: vendorId, paymentDate, method, paymentAccountId, applications (billId, amount) | | POST /api/v1/companies/{id}/journal-entries | a journal entry: date, lines (each accountId and a debit or a credit, with optional description, classId, contactId - needed on Accounts Receivable and Payable lines - and heldForId on a trust account); optional number, memo. Debits must equal credits. The answer is the entry as GET /transactions/{id} reads it |
`` curl -X POST https://tadabooks.example.com/api/v1/companies/{id}/invoices \ -H "Authorization: Bearer kb_live_..." \ -H "Idempotency-Key: order-10482" \ -H "Content-Type: application/json" \ -d '{ "customerId": "...", "issueDate": "2026-03-01", "terms": "Net 30", "lines": [{ "description": "Cases", "quantity": 2, "rate": "125.00", "accountId": "..." }] }' ``
- Every write needs an
Idempotency-Keyheader (8 to 100 characters - an order number or a UUID). Sending the same key again within 24 hours answers what the first one did and writes nothing more, so a retry after a dropped connection never posts twice. The same key with a different body is refused (422). If the first is still running you get 409in_progress: wait and send it again. - A refused write (a locked date, a wrong id) writes nothing and keeps nothing, so once you fix the cause you can send the same request with the same key.
- Ids come from the read routes; one that is not this company's is refused with the field's place ("lines[2].accountId is not an account of this company").
- With bill approvals on, a bill a key writes waits for approval like any bill entered by someone who cannot approve, and a key cannot pay it until a person approves it. A key never approves.
- A write answers 201 with the record exactly as its read route returns it. Up to 100 lines a document.
- A key can only create. Editing, voiding and deleting stay on the screens.
Posting payroll through the API
A key made with 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 TaDa Books. 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; TaDa Books 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 TaDa Books 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 TaDa Books 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.