Your books, programmable.
The same double-entry ledger that runs your workspace — invoices, bills, payments, stock, journal entries and reports — as a REST API, signed webhooks and an MCP server for AI agents. One token. Your permissions. Never more.
curl "https://app.nonari.io/api/v1/invoices?status=OVERDUE" \
-H "Authorization: Bearer $NONARI_TOKEN"{
"data": [
{
"id": "cmf3v9x2k0001qd08a1b2c3d4",
"number": "INV-0142",
"status": "OVERDUE",
"currency": "USD",
"total": "2350",
"amountDue": "1840",
"dueDate": "2026-09-01T00:00:00.000Z",
"contact": { "displayName": "Harbor Supply Co." }
}
],
"meta": { "page": 1, "perPage": 20, "total": 1, "hasMore": false }
}One ledger, whichever door you use.
Everything below reaches the same posting engine as the app: debits equal credits, locked periods stay locked, every change lands in the audit trail.
The REST API
262 endpoints to list, create, post, pay, void and report — with an OpenAPI 3.1 description to generate a client from.
https://app.nonari.io/api/v1Events · HMAC-SHA256Webhooks
57 events pushed to your URL the moment a document or record changes — signed, retried, and free of business data.
POST /webhooksMCP · OAuth 2.1AI agents
Point Claude or any MCP client at the connector: 33 tools that read and write the books, under the same permission checks as a person.
https://app.nonari.io/api/mcpFrom token to posted invoice in three requests.
Create a token
In the app, open Settings → API Access Tokens → New token. Choose Full access or Read-only, pick an expiry, and copy the token — it is shown once. Then export NONARI_TOKEN=nonari_…
curl https://app.nonari.io/api/v1/me \
-H "Authorization: Bearer $NONARI_TOKEN"Add a customer
Every write is validated before it touches the ledger. A bad body answers 422 with every invalid field listed in details.
curl -X POST https://app.nonari.io/api/v1/contacts \
-H "Authorization: Bearer $NONARI_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "displayName": "Harbor Supply Co.", "type": "CUSTOMER", "email": "ap@harbor.example" }'Invoice them — posted
"status": "SENT" posts the invoice on creation: receivable, revenue and tax, balanced, in one transaction. Leave it out to create a draft and post it later with POST /invoices/{id}/send.
curl -X POST https://app.nonari.io/api/v1/invoices \
-H "Authorization: Bearer $NONARI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contactId": "<id from the contact you just created>",
"issueDate": "2026-09-23T00:00:00.000Z",
"status": "SENT",
"lines": [
{ "description": "Consulting — September", "quantity": 10, "unitPrice": 120 }
]
}'The rules, stated once.
Authentication
Authorization: Bearer nonari_… on every request. A token is bound to one workspace and acts as the person who created it — the same permissions, the same branches. Revoke it and the next request fails.
Read-only tokens
A read-only token can view and export everything its creator can, and cannot create, edit, post, pay, void or delete anything. Its limits are checked on every request, against the creator’s current permissions.
Branches
In a multi-branch workspace, send X-Nonari-Branch: <branch id> (ids from GET /branches) to read and write inside one branch. Without it, reads are consolidated across the branches the token can see.
Responses and errors
Success is { "data": … }. Errors are { "error": { "code", "message" } } — 401, 403, 404, 409 for a locked period or a conflict, 422 with field-level details, 429 when rate-limited.
Pagination
Lists take ?page= and ?perPage= and answer with meta: { page, perPage, total, hasMore }. Filters are plain query parameters — every one is listed in the reference below.
Limits
600 requests a minute per token. Each response carries X-RateLimit-Remaining, X-RateLimit-Reset and an X-Request-Id to quote when you write to us.
Money and dates
Amounts come back as decimal strings — exact, never passed through floating point. Send amounts as JSON numbers and dates as ISO-8601.
Browsers
Token requests are CORS-enabled, so a browser-side integration can call the API directly. Cookies are never accepted cross-origin: a token is the only credential.
Every endpoint, with the permission it checks.
Paths are relative to https://app.nonari.io/api/v1. The same list, with request bodies, lives in the OpenAPI 3.1 description — import it into Postman or an SDK generator.
IdentityWho the token belongs to, and what it may do.3 endpoints
SalesInvoices, credit notes, quotes, sales orders and delivery notes.50 endpoints
PurchasesBills, debit notes, purchase orders and quotes, goods receipts and expenses.48 endpoints
BankingBank and cash accounts, their transactions, receipts, payments and transfers.25 endpoints
ContactsCustomers and suppliers.9 endpoints
ItemsProducts, services, non-inventory items and their categories.26 endpoints
InventoryStock movements, adjustments, transfers, locations and per-branch stock.32 endpoints
LedgerJournal entries, the chart of accounts, tax rates, currencies, projects, divisions, fixed assets and branches.47 endpoints
ReportsFinancial statements and operational reports, as JSON or CSV.18 endpoints
WebhooksSubscribe a URL to signed, real-time events.4 endpoints
Hear about every change, the moment it lands.
Subscribe a URL through the API or Settings → Webhooks. Events are fired from the audit trail, so a change made anywhere — the app, the API, the AI connector, a Shopify order — is announced the same way.
Reference ids only. The payload names the record; fetch it through the API with your own token. A leaked webhook body gives nothing away.
Signed. X-Nonari-Signature is sha256= plus the HMAC-SHA256 of the raw body, keyed with the subscription’s secret. Reject anything that does not match.
Delivered after commit. An event for a change posted inside a database transaction waits until that transaction has committed; if it rolls back, nothing is sent.
Retried. Answer any 2xx within 4 seconds. Anything else is retried twice, after about 30 seconds and about 5 minutes. X-Nonari-Delivery stays the same across retries, so you can de-duplicate on it.
curl -X POST https://app.nonari.io/api/v1/webhooks \
-H "Authorization: Bearer $NONARI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "ERP sync",
"url": "https://erp.example.com/hooks/nonari",
"events": ["invoice.created", "invoice.payment_received", "bill.created"]
}'
# → the response carries "secret" — shown once. Store it.POST /hooks/nonari
X-Nonari-Event: invoice.payment_received
X-Nonari-Delivery: 7c1d2a54-3b0e-4f5e-9a51-2f8f0f6f8a11
X-Nonari-Signature: sha256=5d41402abc4b2a76b9719d911017c592…
{
"id": "7c1d2a54-3b0e-4f5e-9a51-2f8f0f6f8a11",
"type": "invoice.payment_received",
"category": "invoice",
"createdAt": "2026-09-23T10:42:07.114Z",
"organizationId": "cm8qz0y3d0000lk08wks12345",
"data": { "object": "invoice_payment", "id": "cmf3wb1c40007qd08z9y8x7w6", "invoiceId": "cmf3v9x2k0001qd08a1b2c3d4" }
}import { createHmac, timingSafeEqual } from 'node:crypto'
// rawBody: the request body exactly as received, before JSON.parse
export function isFromNonari(rawBody, signatureHeader, secret) {
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(signatureHeader ?? '')
return a.length === b.length && timingSafeEqual(a, b)
}The 57 events
invoice
invoice.createdA sales invoice was created, as a draft or posted.invoice.updatedA sales invoice was edited.invoice.deletedA sales invoice was deleted.invoice.sentAn invoice was sent — posted to the ledger, or emailed or texted to the customer.invoice.voidedAn invoice was voided and its postings reversed.invoice.payment_receivedA payment was recorded against an invoice.invoice.payment_deletedA payment recorded against an invoice was removed.credit note
credit_note.createdA credit note was created.credit_note.updatedA credit note was edited.credit_note.deletedA credit note was deleted.sales quote
sales_quote.createdA sales quote was created.sales_quote.updatedA sales quote was edited.sales_quote.deletedA sales quote was deleted.sales order
sales_order.createdA sales order was created.sales_order.updatedA sales order was edited.sales_order.deletedA sales order was deleted.delivery note
delivery_note.createdA delivery note was created.delivery_note.updatedA delivery note was edited.delivery_note.deletedA delivery note was deleted.bill
bill.createdA supplier bill was created.bill.updatedA supplier bill was edited.bill.deletedA supplier bill was deleted.bill.voidedA bill was voided and its postings reversed.bill.payment_madeA payment was recorded against a bill.bill.payment_deletedA payment recorded against a bill was removed.debit note
debit_note.createdA debit note was created.debit_note.updatedA debit note was edited.debit_note.deletedA debit note was deleted.purchase order
purchase_order.createdA purchase order was created.purchase_order.updatedA purchase order was edited.purchase_order.deletedA purchase order was deleted.purchase quote
purchase_quote.createdA purchase quote was created.purchase_quote.updatedA purchase quote was edited.purchase_quote.deletedA purchase quote was deleted.goods receipt
goods_receipt.createdA goods receipt was created.goods_receipt.updatedA goods receipt was edited.goods_receipt.deletedA goods receipt was deleted.expense
expense.createdA expense was created.expense.updatedA expense was edited.expense.deletedA expense was deleted.contact
contact.createdA customer or supplier was created.contact.updatedA customer or supplier was edited.contact.deletedA customer or supplier was deleted.product
product.createdA product or service item was created.product.updatedA product or service item was edited.product.deletedA product or service item was deleted.journal entry
journal_entry.createdA journal entry was created.journal_entry.updatedA journal entry was edited or voided.journal_entry.deletedA journal entry was deleted.account
account.createdA chart-of-accounts account was created.account.updatedA chart-of-accounts account was edited.account.deletedA chart-of-accounts account was deleted.bank transaction
bank_transaction.createdA bank transaction, receipt or payment was created.bank_transaction.updatedA bank transaction, receipt or payment was edited.bank_transaction.deletedA bank transaction, receipt or payment was deleted.stock movement
stock_movement.createdA stock movement was recorded.stock_movement.deletedA stock movement was deleted.Let Claude keep the books — inside your rules.
Nonari is an MCP server. Connect Claude, or any MCP client, and it can run your reports, post entries and chase what is overdue — with exactly the permissions of the person who connected it.
Connect with OAuth. In Claude, open Settings → Connectors → Add custom connector and paste https://app.nonari.io/api/mcp. Click Connect, sign in, pick the workspace and approve. OAuth 2.1 with PKCE and dynamic client registration — no secret to handle.
Or use a token. Any client that can send a header can use the same personal access token as the REST API, including a read-only one.
Reports on request: profit & loss, balance sheet, trial balance, receivables aging, payables aging, inventory valuation, general ledger.
Read the books
Sales and purchases
Cash and bank
Ledger
Contacts, items and stock
Workspace
curl -X POST https://app.nonari.io/api/mcp \
-H "Authorization: Bearer $NONARI_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": { "name": "run_report", "arguments": { "type": "trial_balance" } } }'# “What did we make last quarter, and who still owes us?”
# “Post this month's rent: 2,400 from the operating account.”
# “Add Harbor Supply as a customer and invoice them 10 hours at 120.”Build on books that balance.
Every plan includes the API, webhooks and the AI connector. Create a workspace, mint a token and make your first request in minutes.