R2 Connect API
Accounting integration
How to move a business’s income, taxes and collections from R2 into an accounting system, with no manual entry.
The most common use of this API: the business’s accountant stops re-typing what is already in R2. You need two scopes, read:orders and read:payments.
Which resource for what#
| You need | Use | Why |
|---|---|---|
| Accrued income | /orders | The order records the sale, with its tax breakdown, whether collected or not. |
| Collected income | /payments | The payment records the money coming in, with method and exact date. |
| Accounts receivable | balance from /orders | It is the outstanding balance already computed by R2. |
| Taxes payable | taxes[] from /orders | It carries each tax separately, with its rate and amount. |
Order and payment are not the same
In R2 an order exists even if it was not paid, is paid later, is paid in cash on arrival or is paid in installments. Do not assume an order implies money received: that is what payments are for. Confusing the two is the costliest mistake in an accounting integration.
Incremental sync#
Store the date of your last run and request only what is new. Leave a one-day overlap so you do not miss records created while the previous run was in flight.
javascript
// Daily reconciliation run
const from = new Date(lastRun - 24 * 60 * 60 * 1000)
.toISOString().slice(0, 10); // 1-day overlap
const orders = await readAll("orders", { created_from: from });
const payments = await readAll("payments", { created_from: from, status: "succeeded" });
for (const order of orders) {
await saleEntry({
reference: order.order_number,
date: order.created_at,
subtotal: order.subtotal,
taxes: order.taxes, // each with name, rate and amount
total: order.total,
receivable: order.balance, // 0 if already settled
});
}
for (const payment of payments) {
await collectionEntry({
reference: payment.order_number,
date: payment.succeeded_at, // accounting date of the charge
amount: payment.amount,
method: payment.method, // card, cash, transfer…
reconcile: payment.external_id, // processor reference
});
}Things to watch#
- Use
order_numberas the reconciliation key: it is stable and the business sees it in their dashboard. - Ignore
cancelledorders for income, but record them if your accounting requires a cancellation trail. - An order with status
refundedrequires a reversing entry, not deleting the original. - Amounts already carry decimals: do not round again, or you will break cents against the processor’s statement.
- Reprocessing the same period must be idempotent on your side; use
order_numberand the paymentidto avoid duplicate entries.