Integration patterns
Three integration shapes cover almost every partner build. All of them post into a real double-entry ledger, so the choice is less “how do I call the API” and more “what should the merchant’s books look like”.
Two platform rules shape every pattern:
- No future-dated transactions, and no postings into a locked/closed fiscal year. Backfill jobs must respect the business’s fiscal calendar.
- Every write you might retry needs an idempotency key — see below.
POS: daily summary vs. document push
Section titled “POS: daily summary vs. document push”A point-of-sale system can feed OneBooks at two granularities.
Daily summary — one balanced journal entry per business day, posting the day’s totals (takings, sales, tax). The merchant’s books show clean daily lines; individual receipts stay in your POS.
Document push — every sale becomes a real OneBooks invoice (with its payment). The merchant gets per-document drill-down, per-customer receivables and invoice-level tax reporting.
| Daily summary | Document push | |
|---|---|---|
| Ledger entries | 1 per day | 1+ per sale |
| Customer-level AR | No | Yes |
| Invoice-level tax reports (GSTR, e-invoicing) | No | Yes |
| API volume | Tiny | Proportional to sales |
| Best for | High-volume anonymous retail | Lower-volume, named-customer sales |
One POST /journal per day, keyed so a retry or a re-run can’t double-post.
Requires journal:write.
curl -s https://api.getonebooks.com/journal \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pos-day-2026-08-11" \ -d '{ "date": "2026-08-11", "description": "POS sales 2026-08-11 — Main St till", "reference": "POS-20260811", "lines": [ { "accountCode": "1200", "debit": 1130.00 }, { "accountCode": "4000", "credit": 1000.00 }, { "accountCode": "2100", "credit": 130.00 } ] }'Debits cash (1200) for gross takings; credits sales revenue (4000) and
tax payable (2100). Lines must balance exactly. Fetch the business’s real
account codes from GET /accounts (accounts:read) rather than assuming —
merchants can add child accounts (e.g. a per-till cash account under 1200).
Note: posting journal entries additionally requires the consenting user to be an owner or admin of the business — have the merchant’s owner account perform the OAuth consent.
Batch the day’s receipts into POST /invoices/bulk-import — up to 200 per
request, each with its payment inline. Requires invoices:write.
curl -s https://api.getonebooks.com/invoices/bulk-import \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "invoices": [ { "clientRef": "receipt-88213", "idempotencyKey": "receipt-88213", "customerId": "EXISTING_CUSTOMER_ID", "issueDate": "2026-08-11", "lineItems": [ { "description": "Flat white", "quantity": 2, "unitPrice": 4.50 } ], "payment": { "amount": 9.00, "paymentMethod": "CASH" } } ] }'Rows are processed independently: the response reports each row’s outcome
(created / duplicate / error, plus paymentId or paymentError), echoing
your clientRef. A failed row never aborts the batch — inspect the per-row
results, fix, and resend just the failures.
E-commerce: order → invoice + payment
Section titled “E-commerce: order → invoice + payment”An online order maps to a OneBooks invoice plus a payment for the captured
amount, in one bulk-import row:
idempotencyKey: your order ID — a webhook replay or retried job then returns the existing invoice (duplicate) instead of creating a second one.clientRef: also your order ID, echoed in the result for easy correlation.payment: the captured amount. Full total → invoice lands fully paid; partial capture → partially paid. Omit it for pay-later flows and record the payment when it settles.issueDate: the order date (not the sync date), so revenue lands in the right period.
Each row references an existing customerId. Most stores don’t mirror every
shopper into the merchant’s books: map guest checkout to a single “Online
store” customer set up once when the merchant connects your app, and reserve
real customer records for accounts that need receivables tracking (see the
customers endpoints in the API reference).
Refunds map to sales returns (credit notes) — see the sales-returns endpoints in the API reference.
ERP: master data + ledger sync
Section titled “ERP: master data + ledger sync”ERP integrations usually run two loops:
Catalog sync — POST /items/bulk-import (items:write) upserts by your
itemCode, so the same feed is safe to run repeatedly: existing codes update,
new codes create, and each row’s outcome comes back individually. Pass
"upsert": false to skip existing codes instead of updating them.
Ledger sync — post summarized GL activity as journal entries
(POST /journal with an Idempotency-Key per source batch, as in the POS
example), and read the chart of accounts (GET /accounts) to map your ERP’s
account codes onto the merchant’s. Keep the mapping per business — each
OneBooks tenant can customize its chart.
Journal entries are immutable once posted: corrections are reversals
(POST /journal/{id}/reverse), never edits — mirror that model in your ERP
rather than attempting in-place updates.
Idempotency
Section titled “Idempotency”Networks fail mid-request; your job runner will retry; webhook handlers will fire twice. The API supports two idempotency mechanisms, both scoped to the consenting business:
| Mechanism | Where |
|---|---|
Idempotency-Key request header |
POST /journal, POST /invoices, POST /payments, POST /invoices/{invoiceId}/payments, POST /payments/allocate |
Per-row idempotencyKey field |
POST /invoices/bulk-import (additionally namespaced per calling app, so two integrations can’t collide on the same key) |
Semantics: the first request with a key wins and the result is stored; any later request with the same key returns the original outcome instead of posting again — including when two requests race.
Choose keys that are stable identifiers of the business event, not of the
attempt: an order ID, a pos-day-2026-08-11 day stamp, a source batch number.
Never a random UUID per request — that defeats the purpose.