The Custom API accountancy integration supports balance sheet syncing, just like Breww's built-in integrations with Xero and QuickBooks Online. This lets you keep your balance sheet and COGS in your accountancy platform up-to-date as costs move through Breww from purchase to production to sale.
If you're not yet familiar with how balance sheet syncing works in Breww, read Balance sheet syncing first. The concepts (Breww-controlled accounts, the initial offsetting journal entry, drink-type overrides, etc.) all apply to the Custom API integration in the same way.
This page covers what's different when you're using the Custom API integration: the developer-facing endpoints your sync code calls to fetch journal entries and confirm them back to Breww.
How it differs from a built-in integration
With Breww's built-in Xero and QuickBooks Online integrations, Breww posts journal entries directly to your accountancy platform on a schedule. With the Custom API integration, your own sync code is responsible for that step, and the flow is client-driven:
-
Your code calls a
GETendpoint. At the moment of that call, Breww builds the set of journal entries that need to be synced and returns them, together with aconfirmation_id. -
Your code formats those entries for your accountancy platform and uploads them.
-
Your code calls a
POST /confirm/endpoint, echoing theconfirmation_idback, to tell Breww whether the upload succeeded or failed.There is no scheduled sync for the Custom integration, and no realtime push when individual invoices or inventory receipts are finalised. Nothing is built until you call
GET. This is intentional — the middleware owns the cadence.
Enabling balance sheet syncing
-
Open your Custom API accountancy integration in
Integrations→Accountancy. -
Make sure your chart of accounts is set up — see Chart of accounts (nominal codes). The full list of areas you need accounts for is in Balance sheet syncing.
-
From the integration detail page, find the
Balance sheet syncingrow in theData syncingsection and clickEnable. -
Step through the setup wizard. The final step asks you to pick an offsetting account (typically a dedicated suspense/clearing account) — this is where Breww's one-off opening adjustment will be posted, so your Breww-controlled accounts start with the right balances.
Once enabled, the
GETendpoint will start returning journal entries the first time you call it.
Any customer or supplier touched by a sync cycle (for example a supplier on an inventory receipt that's being posted to GRNI) must already have its accountancy_provider_id set in Breww before the first sync attempt. The GET endpoint will return 422 with a list of unmapped entities if it finds any. See "Required default accounts and entity mapping" below.
The endpoints
All requests follow the same authentication scheme as the rest of the Custom API integration. You'll need your integration's auth_id and an API token — see Custom API accountancy integration for details.
Fetching pending journal entries
GET https://breww.com/api/accountancy-sync-balance-sheet-journal-entries/<auth_id>/
At the moment you call this endpoint, Breww calculates which records in your account have drifted since their last successful sync, builds the journal entries needed to bring your accountancy platform back in line, and returns them.
A successful response looks like:
{
"confirmation_id": "019e20e8-c74e-757e-bc5d-4dc3f7d10b33",
"journal_entries": [
{
"id": null,
"date": "2026-05-13",
"reference": "Breww: Invoice 26",
"currency": "GBP",
"lines": [
{
"description": "Cost of goods sold for Invoice 26",
"amount": "12.34",
"account": {
"id": "1003",
"nominal_code": "5000",
"name": "Cost of goods sold",
"type": "Expense",
"status": "Active",
"is_bank_account": false,
"currency": "GBP"
},
"entity": null
}
]
}
]
}
Notable fields:
-
confirmation_id— a UUID identifying this specific batch of entries. You must echo it back when you confirm, otherwise the confirm will be rejected (see "Theconfirmation_idcontract"). -
journal_entries[].id— alwaysnullfor the Custom integration. Breww has no journal-entry ID to give you because it doesn't know what ID your accountancy platform will assign. See "Updates are sent as adjustments, not edits". -
journal_entries[].lines[].amount— a signed decimal in the account's natural balance direction. See the next section — this is the most important piece for a middleware author to get right. -
journal_entries[].lines[].entity— an object identifying a customer or supplier (withprovider_id,name,reference,type) when the line relates to one. May benull. -
journal_entries[].lines[].account.type— one ofAsset,Liability,Equity,Income,Expense. Drives howamountis translated into debit/credit (see below).The response body has no per-row
uploaded_to_accountancy_providerstatus flag — the other Custom-API accountancy-sync endpoints use that filter, but balance sheet syncing works in confirmation rounds, not row-by-row status.
Confirming or discarding a batch
POST https://breww.com/api/accountancy-sync-balance-sheet-journal-entries/<auth_id>/confirm/
{
"confirmation_id": "019e20e8-c74e-757e-bc5d-4dc3f7d10b33",
"success": true
}
Send success: true once you've successfully uploaded every entry from the batch to your accountancy platform. Breww will commit the deferred *_last_synced_* field updates so those records won't be re-included in the next GET.
Send success: false if your upload failed for any reason. Breww will discard the pending batch without committing the field updates. The next GET rebuilds a fresh batch (with a new confirmation_id) reflecting the latest state in Breww.
Always confirm or discard. If your middleware crashes mid-upload and never calls the confirm endpoint, the pending batch remains in place — subsequent GET calls return the exact same body and the same confirmation_id indefinitely (see "The confirmation_id contract"). Confirming with success: false from your error-handling path keeps the cycle moving.
The body is a single object — not a list. There are no external IDs to post back, because Breww doesn't track which of your accountancy-side entries corresponds to which delivered entry.
The signed-amount convention
This is the single most important detail for a middleware integration. Breww does not send traditional debit/credit pairs. Each line carries a signed amount in the natural balance direction of the line's account.type:
| Account type | amount > 0 means |
amount < 0 means |
|---|---|---|
Asset |
Debit (asset increases) | Credit (asset decreases) |
Expense |
Debit (expense increases) | Credit (expense decreases) |
Liability |
Credit (liability increases) | Debit (liability decreases) |
Equity |
Credit (equity increases) | Debit (equity decreases) |
Income |
Credit (income increases) | Debit (income decreases) |
A balanced goods-received-not-invoiced entry, for example, looks like this in the payload:
Accrued Liabilities (Liability): +919.80 <- liability up
Raw Materials (Asset): +919.80 <- asset up
Both lines have the same sign because both balances are moving up in their natural direction. Your middleware should translate each line into the debit/credit shape expected by your accountancy platform using the rules in the table above. Every entry Breww emits is internally balanced when translated this way.
The confirmation_id contract
Each GET response includes a confirmation_id. Your subsequent POST /confirm/ must echo the same confirmation_id back.
Specifically:
-
While a pending batch exists, repeated
GETcalls return the same body and the sameconfirmation_id. They are idempotent. -
Once you
POST /confirm/— with eithersuccess: trueorsuccess: false— the pending batch is consumed. The nextGETbuilds a fresh batch with a newconfirmation_id, reflecting any drift that has accumulated. -
Confirming with the wrong (or stale)
confirmation_idreturns409 Conflict. The remedy is to re-issueGET, take the newconfirmation_idfrom the response, and confirm with that.This mechanism makes the GET/POST cycle safe against double-confirmations, retries that race against new sync cycles, and stale pending batches in the middleware.
Updates are sent as adjustments, not edits
The Custom integration does not support updating previously-synced journal entries. If a brewery user changes a record that was already synced (for example by editing a batch's ingredient costs), Breww will not send an updated version of the original entry. Instead, the next sync cycle delivers a new adjustment entry containing only the delta.
That's why every entry's id field is null in the payload: there is no Breww-side journal-entry identity for your middleware to update against. Treat each delivered entry as additive — post it as a new journal entry on your accountancy side.
Required default accounts and entity mapping
Default account configuration
Before the GET endpoint will return anything, your integration's chart of accounts must have all of the following defaults set. The endpoint returns 400 with a list of any that are missing.
-
default_sales_account -
default_stock_item_account -
default_goods_in_additional_cost_account -
default_finished_goods_account -
default_cogs_account -
default_wip_account -
default_wip_losses_account -
default_stock_adjustment_account -
default_batch_other_cost_account -
default_cm_cost_account -
accrued_liabilities_accountThese are set during the balance sheet syncing setup wizard. You can review and change them later on the integration's Accounts & tracking page.
Per-supplier and per-customer mapping
Any supplier touched by an inventory receipt being synced, and any customer touched by a sale being synced, must already have its accountancy_provider_id set in Breww. If Breww finds an unmapped entity during the build, it accumulates a user-facing message and returns 422 with all of them in one response, so the brewery user (and your middleware) can see everything that needs fixing in a single round-trip.
Map customers and suppliers either through the Breww UI (Map customers & suppliers action on the integration detail page) or via the existing accountancy-sync-customers and accountancy-sync-suppliers endpoints described in Custom API accountancy integration.
Error responses
| Status | Meaning |
|---|---|
200 |
On GET: the journal_entries array is included (it may be empty if there's been no drift). On POST /confirm/: the batch was confirmed or discarded successfully. |
400 |
One or more required default accounts are missing. The response body lists which. |
404 |
On GET: the auth_id doesn't belong to a Custom accountancy integration for your brewery. On POST /confirm/: there's no pending batch to confirm. |
409 |
The confirmation_id in the POST body doesn't match the current pending batch. Re-issue GET and use the new id. |
422 |
User action is required before syncing can continue. The response body contains errors (a list of user-facing messages) and suggested_solution. Typical causes: the wizard hasn't been completed (sync_activity_to_balance_sheet is false), or a touched customer/supplier is missing its accountancy_provider_id. Surface these to the brewery user rather than retrying blindly. |
Where this fits in your sync workflow
The balance-sheet endpoint is a separate step in your scheduled sync loop, alongside the customer payments, orders, credit notes, and supplier invoices endpoints described in Custom API accountancy integration. The recommended order is:
-
Map customers and suppliers.
-
Upload customer payments.
-
Upload orders, credit notes, and supplier invoices.
-
Upload payment and credit note allocations.
-
Fetch and confirm balance sheet journal entries.
Journal entries don't have ordering dependencies with the other document types — they reference accounts (via the codes you've configured in your chart of accounts), not invoices or payments. You can fit the
GET/POST confirmround-trip in at any point in the loop.
Troubleshooting
The GET endpoint returns an empty journal_entries list
This is normal when nothing has drifted since the last successful confirmation. The cycle still completes — you'll still get a confirmation_id, and you should still POST /confirm/ with success: true (with no entries to upload, this is essentially a no-op).
Repeated GET calls return the same body forever
You haven't confirmed the pending batch. Call POST /confirm/ with the confirmation_id from the response — either success: true if you've uploaded the entries, or success: false to discard the batch and pick up a fresh one on the next GET.