maesn
For developers

How to integrate with Sage Active: Eleven objects read, and corrections post as new documents

Sage Active answers eleven objects and accepts five back. It treats a posted document as final, the way an accounting ledger does, so a correction is issued as a new document rather than an edit. That is the one design decision to make early, and this guide covers it alongside the token, the company header and the cursor.

Lennart Svensson, CTO and Co-Founder at Maesn
Lennart Svensson
CTO and Co-Founder · · Updated
Illustration for How to Integrate with Sage Active: Reads and Corrections
The context

One endpoint, one header, three countries

Three structural facts decide how this integration is shaped, and none of them is about a field name.

The first is that there is one endpoint rather than many. Sage Active speaks GraphQL: queries to read, mutations to write and a response shaped by what you asked for. It also enforces a complexity limit and rejects queries that exceed it, so deeply nested data sometimes has to be split across several requests rather than fetched in one.

The second is that a token does not identify a company. After authentication you have access, and you still have to say whose books you mean, on every request. That value is fetched after the flow completes and chosen by the user, which makes it a post-connection step in your onboarding rather than a header you can hardcode.

The third is that France, Spain and Germany are three separate target systems rather than one system with a locale. The application you register in Sage Active carries a country type and its own callback URL, so serving all three markets means three registrations rather than one.

Different Sage products do not share an interface
Sage sells several accounting products, and what you read about one rarely transfers to another. Sage Active launched in 2023 for these three markets and speaks GraphQL. Tokens, headers and endpoints documented for a different Sage product are documentation for a different system.
How it works

Eight-hour tokens and the refresh scope

On Sage Active’s side: authentication is OAuth 2.0, and the access token is valid for eight hours. A refresh token is issued only if the initial authorisation requested the offline_access scope. Ask for that scope and an unattended integration keeps running past the eight hours without a person signing in again.

Two further scopes divide the access itself, one for reading and one for writing. Asking for only what you need is the ordinary discipline, and here it also documents your intent to the customer at the moment they approve the connection.

The company identifier interacts with all of this in a way worth knowing. The endpoint reference states that if you are not using the interactive authentication flow, the companyId query parameter has to be populated on your calls, and that you obtain the value from a companies endpoint after connecting. With that flow, the selection happens once during the connection instead.

All three country target systems run that interactive flow through Maesn, which is why unified authentication is the difference between one selection step at connection time and a header your request builder has to get right on every call for every tenant.

Token lifetime, the refresh scope and the read and write scopes are documented in Maesn’s material for this system; the companyId condition is quoted from the trial balance reference and the flow itself from the authentication guide, both checked 13 August 2026.

How it works

Eleven objects, read through a cursor

On Sage Active’s side: eleven object types come back, and the list reaches further than an accounting API usually does. It carries sales orders, offers and their lines alongside the ledger, so the order side and the books answer the same connection.

How you walk that data is the GraphQL convention rather than the accounting one. Pagination is cursor based: you ask for a number of items, the response carries a cursor and a flag saying whether more exists, and you pass that cursor back to continue. There is no page number, so you cannot jump.

For a first import that changes the failure mode rather than the effort. A sync that dies halfway cannot resume by asking for page seven, it resumes from the cursor it stored, so storing that cursor becomes part of the design instead of an optimisation. One way to filter and page is what turns this back into the same offset-and-limit shape you use for systems that never heard of a cursor.

Cursor pagination, in the shape the system returns itGraphQL
query {
invoices(first: 50, after: "<endCursor>") {
pageInfo { endCursor hasNextPage }
nodes { id number totalAmount }
}
}

hasNextPage is the loop condition and endCursor is the bookmark. Neither is a position, which is why a resumable sync stores the cursor rather than a count.

The problem

How a correction reaches Sage Active

On Sage Active’s side: five object types can be created, and a document that has been posted stays as it was written. There is no update call and no delete call for it.

That is the ledger model rather than a database model, and it is how accounting systems usually treat a posted document: the record of what was booked has to survive, so a correction is issued alongside it instead of overwriting it. Plan the correction path before the first write and it costs nothing; discover it afterwards and it is a rework of your write logic.

Object types by operationSage Active
Read
11 objects
The widest read surface of the systems in this guide.
Create
5 objects
Accounts, customers, invoices, items and suppliers.
Update
0 objects
A posted document stays as written, so send a correction.
Delete
0 objects
The ledger keeps its history, the same as on paper.

A deep read side hides a narrow write side. Of the eleven systems in the catalogue that cannot update anything, this one reads the most by a clear margin, which is exactly why the limit is easy to discover late.

What Sage Active answers and what it accepts back, per object type. Checked 13 August 2026.
Where the main objects sit, read against write
ObjectReadCreate
Invoicesyesyes
Customersyesyes
Suppliersyesyes
Accountsyesyes
Itemsyesyes
Journal entriesyeson request
Trial balanceyesno
Sales ordersyesno
Offersyesno

Counted from the generated coverage data, checked 13 August 2026. Ten systems can read journal entries and this is the only one of them that cannot also create them, which is worth knowing before a posting workflow is designed around it.

There is a second requirement on the write path that costs calls. Reference fields do not accept readable values: a unit of measurement has to be given as the internal identifier the system uses for it, not as a word. Those identifiers are not shared between tenants, so the lookup and the cache are per customer.

That is exactly the layer the common data model exists to remove. You send a readable value, the resolution happens behind the connection and the same write looks identical against a system that would have accepted the word in the first place.

A reference field wants an identifier, not a wordJSON
{ "unitOfMeasurementId": "KILOGRAM" } // rejected
 
{ "unitOfMeasurementId": 14 } // resolved
// the id is per tenant, so the mapping
// is fetched and cached per customer

The same pattern applies to other reference data, so a write path usually needs its lookups warm before the first call rather than after the first failure.

The problem

What the trial balance needs first

On Sage Active’s side: a trial balance is available as a report, which is worth knowing because most accounting APIs make you assemble one from journal entries. There is a parameter in front of it that is easier to handle at design time than at runtime.

The endpoint requires a fiscal year start date. The reference is explicit: “The query parameter fiscalYearStartDate is required and must be a valid date.” So far so ordinary, until you look for where that date comes from.

Fiscal years are not a readable object on this system. Not on request, not behind a flag: the coverage cell is a plain no. The report therefore asks for a value that the same system will not hand you, and the only places left to get it are your own configuration, your customer or a convention you agree once and store.

One report, one missing inputVerified at both ends
Trial balanceReadable

One of two systems in the catalogue that expose this report at all.

fiscalYearStartDateRequired

The reference states the parameter is required and must be a valid date.

Fiscal yearsNot readable

A hard no rather than on demand, so the date cannot be looked up from the same system.

The date has to come from somewhere else. Your own configuration, or the customer, or a convention you agree once. It is a small piece of setup that becomes a support ticket if nobody plans for it.

One report, one required parameter and where the value comes from. Both ends verified rather than assumed.

The requirement is quoted from the Sage Active tab of the trial balance reference, checked 13 August 2026; the unavailability is read from the generated coverage data, where the cell is no rather than on request. Both were checked separately rather than taken from an earlier report of the same finding.

Ask for the fiscal year at onboarding
It is one field, and it is cheaper to collect while a human is already configuring the connection than to discover when the first report call returns an error.
How it works

Freshness runs on a schedule

No object here carries an enabled event. There is no subscription to register, no callback to verify and no delivery semantics to reason about, so keeping data current is a scheduled read and that is the whole of it.

In the event model that is the pull half doing all of the work. It has one property that suits this system in particular: a pull catches up. Because the traversal here is cursor based and resumable, a worker that was down for a day continues from where it stopped rather than recomputing a position.

What that costs is your choice of interval, and on a system that cannot update anything the calculation is simpler than usual: nothing you already read is going to change in place. New documents appear, and superseding ones appear next to them.

What the object coverage looks like in full, including the combinations marked on request, is listed on the Sage Active API page rather than repeated here.

On demand is not a delivery date
Twenty-four objects here carry an on demand marking in the webhook column, and journal entry creation carries one too. That marking means technically feasible and not yet built, it is no commitment and it is least reliable in exactly that column. Design against what is enabled today and tell us what you need.
Where our part ends

What Maesn covers, and what stays with you

What we take off the list:

  • The query language. Queries, mutations, the framework conventions and the complexity limit stay behind the connection, and your side makes ordinary REST calls.
  • The company selection. The interactive flow collects it once, and the identifier is supplied on every later call without your request builder knowing.
  • The cursor and the identifiers. Cursor traversal becomes page and limit, and reference fields take readable values instead of per-tenant numbers.

What stays with you, and the first one is a product decision rather than a task:

  • What a correction means in your model. With no update and no delete, superseding a document is a concept your product has to carry. We can make the write identical to every other system; we cannot make this one editable.
  • The fiscal year start date. The report needs it and the system will not give it, so it comes from your configuration or your customer.
  • Three registrations for three markets. Each country is its own application with its own callback, and that is yours to set up.
FAQ

Frequently asked questions

Is Sage Active the same as Sage Accounting or Sage 100?

No. Sage sells several accounting products and they do not share an interface. Sage Active launched in 2023 for France, Spain and Germany and speaks GraphQL. Anything you read about tokens, headers or endpoints for another Sage product does not transfer to this one.

Can I update a record in Sage Active?

Not through any object today. Eleven object types are readable and five can be created, while update and delete are unavailable across the board. In practice a correction becomes a new document, so your model needs to track which record supersedes which.

Why does the trial balance call fail without a fiscal year date?

Because the parameter is mandatory. The reference states that fiscalYearStartDate is required and must be a valid date. The constraint behind it is that fiscal years are not a readable object here, so that date has to come from your own configuration rather than from a lookup.

How long does a Sage Active token last?

Eight hours, and a refresh token is only issued if the initial authorisation requested the offline_access scope. Without it an integration stops until someone signs in again, which is why that scope is effectively mandatory for anything running unattended.

Why does every request need an organisation identifier?

Because the token alone does not say whose books you are reading. The company has to be selected after authentication and then sent on each call. Through Maesn's interactive flow that selection happens once during connection and the value is supplied for you.

Can I jump to a specific page of Sage Active results?

No. Pagination is cursor based, so you request a number of items and follow the returned cursor to the next set. Reaching a position deep in a dataset means walking everything before it, which makes a resumable sync more valuable than a random-access one.

Build once on the Unified API.

Sage Active updates nothing, Fortnox meters 25 requests every five seconds and SnelStart approves before it issues a production key. Build against one interface and each of those becomes a row in a table rather than a project.