Skip to content

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.

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.

Terminal window
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.

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 integrations usually run two loops:

Catalog syncPOST /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.

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.