The Integration Surface
Every page in Integrate an External System assumes what this page says:
the shape of the API your access key calls, what happens after a write has already answered, what
BigLedger does not give you, and the reads that prove an integration worked. Read it once before you
build, and again when a document answered 200 and nothing reached the books.
What the integration surface is
BigLedger has one REST API, served over HTTPS, returning JSON. The platform’s own screens call it too. Paths follow one shape, with more variation than the shape suggests:
/core2/{tnt|platform}/{dm|ms|cm}/<module>[/<sub-module>]/<resource>/<access-endpoint>The segment that matters is the access endpoint. It names the kind of caller a route was built
for: backoffice-ep for a staff user in the BigLedger shell, login-ep for a signed-in user acting
on their own records, login-entity-ep for a customer or supplier, public-ep where a resource is
meant to be open, and etl-ep for server-to-server integrations. That last one is your surface.
It reads your key before anything else.
How big it is, and where that figure comes from. As at backend commit 4623d9e7ae, whose route
table was regenerated on 7 October 2026, 1,205 routes carry the etl-ep segment: 1,058
endpoint methods across 177 controllers, covering about 320 resources. That takes in
customers, suppliers and employees, items and inventory, companies, branches and locations,
pricing, membership, payment gateways, and every business document type through one generic
document endpoint. The platform’s own screens call a larger set, 9,494 backoffice-ep routes, with a signed-in user’s
token. The figures are counted from the controllers’ mapping annotations and move
whenever the backend adds a controller, so read them as a size.
Two cautions about the shape. The scope and layer segments are usually there, but 65 of those routes
sit directly under /core2/dm/… or /core2/ms/…, and one uses the cm layer. Most paths also carry
a sub-module, such as erp/fi/fi-items or erp/gen-doc/{docType}. So copy the path from the
reference rather than composing it. A composed path that does not exist answers 417, not 404.
Where a resource is complete, it has the same six operations: create (POST …/etl-ep), update
(PUT …/etl-ep), delete (DELETE …/etl-ep/{guid}), list (GET …/etl-ep), read one
(GET …/etl-ep/{guid}) and filter (GET …/etl-ep/query). About a third of resources have all six,
so check the reference before you assume one. Push and pull both run over the same endpoints, and
webhooks give you the outbound event path.
Whether your key is accepted is decided by the route’s handler, not by the word in its path. Many
routes built for a signed-in user also take a key when no token is sent, and a few public-ep routes still want a
credential. Which credential works where
has the table.
A key is a user. It carries no scope of its own and acts as exactly the user it was issued for. To stop an integration you delete its key or remove that user’s permissions; those are the two levers.
What happens behind a write
None of this shows in a response, and it decides whether your figures reach the books.
- Creating a document stores it. It does not post it. The create saves the document, numbers
it and, for most document types, queues the create webhook. It starts no posting. A
posting_statusyou send is stored as sent, so a document created asFINALreadsFINALeverywhere while no journal is ever written for it (since mid-October 2026 it is still handed to e-invoicing). Create as a draft. - Finalise is a second call.
PUT …/gen-doc/{docType}/etl-ep/update-posting-status/{guid}runs the checks a document must pass atFINAL, saves it, queues one primary job and answers. The answer comes back before that job runs. A200means final and numbered, not the journal exists. - The primary job fans out per tenant. It queues the follow-on jobs your tenant subscribes to: journal, stock, cashbook, e-invoice, membership points and others. A company can narrow that list. That is why the same call posts more for one business than for another. How work actually runs traces the chain.
- Success is something you read back. The journal job writes
posting_journal = POSTEDon the document when it succeeds and writes nothing when it fails. A failure inside a follow-on job never comes back to your call. - Your header
guidis the retry guard. If you set the document’sguidyourself and keep it, a retry after a timeout is refused withGENERIC_DOC_HDR_OBJECT_GUID_ALREADY_EXISTS. That refusal means your first attempt landed. There is no idempotency header; this, plusclient_keyandclient_source, is the mechanism.
Two routes set FINAL, and they differ. The etl-ep route above is the one built for your key. The
screens use the sibling route without etl-ep, and that route also takes your key when
you send no token. They do not do the same work:
…/etl-ep/update-posting-status/{guid} | …/update-posting-status/{guid} (the screens') | |
|---|---|---|
A document already FINAL | Checked, saved and queued again | Refused with “already been posted to FINAL” |
Stock balance at FINAL | Not checked | Checked unless the body says not to |
| A foreign-currency document | Saved FINAL; the journal job declines it, so no journal | A base-currency copy is made and posted |
| Maintained for | Server-to-server callers | The screens, which change with the user interface |
Whether all of these differences are intended is a question BigLedger’s engineers have been asked
and have not yet answered, and which route these pages should recommend to you is still open. Until
then, on the etl-ep route read posting_status before you call it, check stock yourself if
it matters to you, and keep any foreign-currency invoice off it. The detail is in
What the etl-ep Finalise does differently.
What BigLedger does not give you
Stated plainly, so you can plan around it rather than search for it.
blg-akaun-ts-lib, blg-akaun-ng-lib) are Angular libraries for applets running inside the
BigLedger shell; they do not target the server-to-server endpoints. The Java client library is
published to a private repository. You write HTTP yourself, which is not onerous, because most
resources share the same six operations.429 the API returns is a database-overload signal:
the database has run out of connection slots, and your job is competing with the tenant’s own users.
Back off, retry with a growing wait, and cap your attempts. Requests stream and can run long; see
the limits in the Data API.When it goes wrong
| What you see | Usual cause | Where the fix is |
|---|---|---|
403 with a code starting AUTH_TOKEN_OR_ | The key itself: mistyped, deleted, expired, or the wrong secret | Authentication. Issue a new key only for these |
403 with CLIENT_AUTH_USER_NOT_AUTHORIZED | The key was accepted; its user lacks the permission, possibly only for the branch the document names | Ask the tenant administrator for the permission. A new key changes nothing |
417 with a message beginning No static resource | The path is not routed | Copy the path from the reference instead of composing it |
Finalise answered 200, and minutes later posting_journal is still blank | A follow-on job stopped after the response, usually on a default GL code the company lacks; on the etl-ep route, a foreign-currency document is the other cause | Data API. Calling Finalise again repairs nothing |
A retried create is refused with GENERIC_DOC_HDR_OBJECT_GUID_ALREADY_EXISTS | Your first attempt landed | Read the document back; do not create it again |
429 with CLIENT_OVERLOAD_DATABASE | The database is out of connection slots | Back off and retry later, and move bulk work out of business hours |
| A webhook you expected never arrives | A topic subscription gets one attempt per event, and a failed delivery is gone | Webhooks, plus a periodic pull to catch what was missed |
Checks that prove it worked
Three reads settle it, and none needs anyone watching a screen.
- The connection. A one-row read of a resource you will really use, such as
GET …/gen-doc/internal-sales-invoices/etl-ep/query?limit=1, answeringOK_RESPONSEproves the key, the tenant code and the permission together. Change one character of the secret and the same call must answerAUTH_TOKEN_OR_ACCESS_ID_TOKEN_NOT_MATCHED; that proves your client sends the key you think it does. - A posting. After Finalise, read the document back by its
guid.posting_statusreadingFINALis your call landing;posting_journalreadingPOSTEDis the journal written. - The day. Query yesterday’s records by your
client_source,FINAL, and adate_txnrange. The count and total should equal your own system’s, and no row should have a blankposting_journal. Somebody in finance owns this check, daily: for most feeds, nothing in BigLedger tells you one has stopped.
The integration in the rest of the product
- What must exist first. A tenant code, and a user in that tenant holding the permissions your
calls need; a tenant administrator creates that user, and the key you issue acts as exactly that
user. For any business document, the company’s
default GL codes must be set before the first
FINAL, or the journal job stops after the call has already answered. - What becomes possible. A document you set
FINALposts into the same books the screens do, through the same follow-on jobs: the journal in Financial Accounting, the stock movement in Inventory, and, where the tenant subscribes, the e-invoice in E-Invoice. Nobody re-keys your web orders. - The siblings, and when to use which. Use these endpoints for a continuous feed; use the
Batch and Import API for a one-off load. Use
webhooks to hear about a change quickly, rather than polling for it. Use
the E-Invoice API once a document is
FINAL, to follow what became of the e-invoice. Build an applet instead when a person should do the work inside BigLedger. Use the Platform Library when you are writing a Java backend on BigLedger’s libraries rather than calling the API from outside. - What breaks if it is wrong. A document set
FINALwithout its default GL codes readsFINALon every screen while the ledger is short by its amount until someone reposts it. Removing a user’s permissions stops every integration using a key issued to that user, which is why each integration gets a dedicated user. - What it is used alongside. Webhooks together with a periodic pull on
updated_date_from: the pull makes your integration correct, the webhook only makes it fast. And on the receiving side, Sales That Arrive From Another System is the morning check the finance team runs against your feed; agree the daily count with them.
Asking BigLedger for a hand-off
When something only BigLedger can give you stands between you and a working integration, open an issue at BigLedger-Support/public. Say which resources you need to read or write, and in which direction. Anyone can read the tracker, so keep your tenant code and your own records out of the post; take anything that has to name your tenant to your BigLedger contact (see Support). The three things worth asking for by name are a test tenant, an integration user with a named permission set, and, if you need a resource that is not documented here, the current route for it.
If a page in this section tells you to call something that does not answer, that is a bug in the documentation and we want to hear about it.