maesn
For developers

How to integrate with Xentral: Every endpoint has its own version, and the version sets the query

Calling Xentral the first time is easy. Keeping the call working is the part that costs you, because Xentral versions each endpoint on its own and a new version deprecates the one before it. Three generations answer under one token, and more than the payload moves between them: the filter syntax, the paging contract and how firm that contract is in the first place.

Lennart Svensson, CTO and Co-Founder at Maesn
Lennart Svensson
CTO and Co-Founder · · Updated
Illustration for How to Integrate with Xentral: The Version Sets the Query
The context

Three generations answer under one token

One personal access token reaches all of Xentral, and Xentral means that literally: the current interface, the older REST one and the older Standard one all accept the same credential. That is convenient on the first day and it is the reason the version question never fully goes away.

The vendor recommends the current generation and says why: “the older REST and Standard APIs can’t handle all endpoints when using personal access tokens.” It then lists thirteen paths that a token cannot reach in the legacy interfaces, twelve under /shopimport/ plus /v1/reports, and closes the list with “This list makes no claim to completeness.”

Versioning itself is in the path, as /api/v1/…, /api/v2/… and /api/v3/…. The sentence that decides how you plan is the one about scope: “not our complete API has a version, instead each individual endpoint has its version. We will introduce new versions of endpoints but not of the complete API.”

So there is no single number to pin your integration to. There is a version per endpoint, and a fourth shape besides: the versionless /api/… form still answers, and Xentral marks it as deprecated and due for removal. Which objects a connection reaches through us, cell by cell, is on the Xentral API page.

Two rules about change complete the picture, and they pull in opposite directions. Releasing a new version of an endpoint means “the previous version automatically gets deprecated”, so the version you are on ages by someone else releasing the next one. And inside a version, “Adding items to a payload is not considered a breaking change”, so a stable version can still grow fields under you.

Neither of those is unusual on its own. Together with a per-endpoint version they mean a Xentral client is never simply on v2 or v3, it is on a set of versions that move individually.

The three generations, the thirteen unavailable paths and the completeness caveat from Xentral’s authentication reference; the path form, the per-endpoint scope, the deprecated versionless form, the automatic deprecation and the breaking-change definition from its versioning reference, both checked 17 August 2026.

The problem

Filtering and ordering change shape between the generations

Here is where a per-endpoint version stops being a footnote. Xentral documents its query language twice, on two pages, one for the first two generations and one for the third. Those are not two descriptions of one thing. The page parameter, the sort syntax, the operator set and the date format are different on each.

The filter shape survives the jump, which is what makes the rest easy to miss: both dialects take filter[n][key], filter[n][op] and filter[n][value], and both AND-combine the entries. Everything around that changed.

The same query features, as documented on Xentral's two reference pages.
What you setv1 and v2v3
Page numberpage[number]page
Page sizepage[size]perPage
Default page size1015
Maximum page size50Specified per endpoint
Sort directionorder[0][dir]=descsort=-field
Operators documented1114
Nested fieldsNot documentedDot notation
Date valueFull timestamp with offsetYYYY-MM-DD
Total in the responseNot documentedOnly with X-Pagination: table

The operator sets are the part that changes behaviour rather than syntax. The newer dialect adds contains and notContains for strings and isNull and isNotNull for any field, and it accepts nested keys in dot notation, so totals.gross.amount and dunningSettings.blocked are filterable. On the older dialect neither the null checks nor the nested paths are documented at all.

The overview pages are not the contract, and they say so

Three things on the older page do not agree with each other. Its operator table lists eleven entries and contains is not among them, while its own worked example uses filter[0][op]=contains twice.

Its between example passes from: 1, to: 10 as JSON and then translates that to a query string ending in to=1. And the table marks greaterThan as taking a numeric value, while the date example on the same page passes it a full timestamp.

Xentral resolves this itself, one sentence above the table: “It is specified per endpoint which filtering operations are possible, and which filtering keys are allowed.” That is the practical instruction. Read the endpoint you are calling, and treat the overview as orientation.

The same request, written for either side of the splithttp
# the shape documented for the first two generations
GET /api/products
?filter[0][key]=date
&filter[0][op]=greaterThan
&filter[0][value]=2026-08-01T00:00:00+02:00
&order[0][field]=createdAt
&order[0][dir]=asc
&page[number]=1&page[size]=50
 
Accept: application/vnd.xentral.default.v1+json
 
# and the shape documented for the third
GET /api/v3/invoices
?filter[0][key]=documentDate
&filter[0][op]=greaterThanOrEquals
&filter[0][value]=2026-08-01
&sort=-documentDate,customerNumber
&page=1&perPage=50
 
X-Pagination: table

Both blocks follow Xentral’s own examples, including the two date formats. The line breaks inside the query strings are for reading. Four differences are visible at once: the paging parameters, the sort syntax, the date format and the header that decides whether a total comes back.

None of this is hard to write once. It is hard to keep correct across a set of endpoints that version individually, because the dialect a call needs is a property of the endpoint rather than of your client. Through Maesn the query surface is one shape for every system, which is what unified pagination and filtering describes, and the two Xentral dialects sit behind it.

The parameters, defaults, maximum, operator table, ordering form and date examples for the first two generations from Xentral’s filtering reference; the operator set, dot notation, sort syntax and defaults for the third from its v3 filtering reference, both checked 17 August 2026. The three internal disagreements are quoted from the older page itself.

The mechanics

Three paging strategies, and the default one hides the total

The newer generation offers three paging models, and the choice between them travels in a request header instead of a query parameter. That placement is the reason it matters: a header is the thing a client forgets to send.

X-Pagination takes simple, table or cursor. Xentral is explicit about what happens when it is absent:simple applies. And simple is the mode that returns no total, deliberately, because it skips the COUNT(*) query that would produce one.

Selected by a request headerXentral’s own values
X-Pagination: simpleDefault
Parameters
pageperPage
Returned in meta
current_page, per_page, links
Total in the response
No
Style and cost
offset paging, performance good

Applies when the X-Pagination header is absent. No COUNT query runs, so no total comes back.

X-Pagination: table
Parameters
pageperPage
Returned in meta
current_page, per_page, total, last_page, links
Total in the response
Yes
Style and cost
offset paging, performance moderate

The only mode that reports a total. Xentral names the price: an additional COUNT query.

X-Pagination: cursor
Parameters
cursorperPage
Returned in meta
next_cursor, prev_cursor
Total in the response
No
Style and cost
cursor paging, performance best

Next and previous only. Jumping to a page is not possible, which is why Xentral points it at exports.

The three paging modes of the third generation, with the parameters, the response metadata and the performance note Xentral publishes for each. Checked 17 August 2026.

Read that against how a first version of a sync usually gets written. You ask for page one, read a total, divide by your page size and loop that many times. Against the default mode there is no total to divide, so the loop either runs once or runs on a value that was never there.

The fix is a decision rather than a workaround, and there are two reasonable ones. Send X-Pagination: table when a person is going to look at a count, and accept the extra query it costs. Or keep the default and stop when a page comes back short, which is the loop that works in all three modes.

For a first backfill the third mode is the one Xentral points at. Cursor paging avoids offset queries, which is why the vendor’s own comparison rates its performance highest, and it gives that up in exchange for direction: navigation is next and previous only, with no jumping to an arbitrary page. That is a good trade for an export and a bad one for a table a user clicks through.

One detail belongs next to the paging and is easy to read past. Every response, in every mode, arrives inside the same envelope of meta, data, extra and links, where data is an array for a collection and extra is where Xentral puts pagination and totals. A client that reads the body as the record rather than as the envelope gets one level too high.

The three modes, the default when the header is absent, the parameters, the response metadata, the count-query cost and the performance comparison from Xentral’s v3 filtering reference (the same page as above); the response envelope from its introduction, both checked 17 August 2026.

The problem

The maturity of an endpoint travels in the media type

Xentral tells you how finished an endpoint is, and it tells you in the one place a client sets and forgets. Media types are built as application/vnd.xentral.VARIANT.vVERSION+DATAFORMAT, and the version segment carries a suffix when the endpoint is not stable yet.

The three maturity levels, as Xentral labels them inside the media type.
LevelVersion segmentWhat Xentral says about it
Stablev1Solely a version number in the media type
Early accessv1-betaStill in development, or a new version whose contract can still change
Alphav1-alphaReleased, but the data you get is dummy data

The third row is the one to sit with. An alpha endpoint is released, it authenticates and it answers. What comes back is not your customer’s data, and Xentral puts that plainly: “this endpoint has been released, but the data you get is dummy data, meaning you can start working on building your integration, but we are still working on these endpoints.”

Nothing about the status code separates that from a working read. A test that asserts a 200 and a non-empty body passes against an alpha endpoint, and it passes for as long as the endpoint stays there.

The same header is also how one endpoint offers several shapes of the same record. A product read is documented with a default representation, a minimal one and a CSV variant, all on the same path, selected by Accept. Asking for a combination Xentral does not publish is not a fallback either, it is a 406.

So this system answers with at least two error classes that a client will meet before it meets a real bug: a 406 when the media type is wrong and a 429 when the request budget is spent, at 100 requests a minute regardless of plan. Reducing that kind of variation to one shape is what unified error handling is for.

Beta carries a second marker as well, and it is the one you see without reading a media type. A large number of endpoint descriptions in the reference open with their own warning that the endpoint is in beta and that “breaking changes can occur at any time without prior notice”.

So the maturity label and the endpoint description are two separate places to look, and both are read per endpoint like everything else on this system.

The media type structure, the three variants on one endpoint and the 406 from Xentral’s media types reference; the three maturity levels and the dummy-data sentence from its versioning reference (as above); the request budget from its rate limiting reference, all checked 17 August 2026.

Change detection

152 event types, and none of them for customers or suppliers

Two things are true about events on this system at the same time, and a description that carries only one of them is wrong in one direction or the other.

Xentral publishes an event catalogue, and it is a large one. Counting the operations on its webhook reference gives 152 distinct types across 44 domains, from salesOrder.created to invoice.released to datev.exported.

Delivery is an HTTP POST to a destination URL of at most 255 characters and the payload arrives as {"type": "com.xentral.EVENTTYPE.vX", "body": {…}}. The whole feature sits behind a flag: “The webhooks feature is currently in testing and available behind a feature flag”, with access arranged by email rather than switched on in the interface.

Through Maesn, no Xentral object has an event enabled today. That is the second true thing, and it is a statement about what is switched on rather than about what the platform can do.

Counted on Xentral’s webhook reference152 types, 44 domains
Domains with six or more event types
  • product12
  • production9
  • purchaseOrder9
  • return9
  • salesOrder9
  • creditNote8
  • deliveryNote8
  • invoice8
  • offer8
  • proformaInvoice8
  • liability6

These eleven hold 94 of the 152. The other 33 domains hold 58 between them and none of them more than four.

What a connection reads here today
  • CustomersNo event family

    The one object you can create, and it cannot be updated

  • Invoicesinvoice.*

    Eight event types, from created to archived

  • Invoice linesNo event family

    No event family of its own in the catalogue

  • SuppliersNo event family

    No event family of its own in the catalogue

Through Maesn no Xentral object has an event enabled today. Both lines are true at the same time, and the second block is the one a party sync has to plan around.

Above, the event domains Xentral publishes, counted on its own reference across 152 types and 44 domains. Below, what a connection reads here today. Checked 17 August 2026.

The measurement that decides your design is neither of those two numbers, though. It is which domains the catalogue contains. Searching it for the party layer returns nothing: there is no customer domain, no supplier domain and no contact or address domain. The nearest match is dropshippingSupplier.orderCreated, which is an order rather than a supplier record.

Put that next to what is readable. Of the four objects a connection reads today, exactly one has an event family in Xentral’s catalogue, and it is invoice with eight types. Customers, the single object you can create here, has none at all.

What that means for a party sync

A scheduled read is not a stopgap for customers and suppliers on this system. It is the only change-detection route either side documents for them. If the feature flag were switched on tomorrow and every type enabled, a customer record would still not be able to tell you it had changed.

That is a different answer from “events are coming”, and it arrives before you design around a future push rather than after. For invoices the picture is the other one, which is why the two cases do not belong on one code path.

One more property of the catalogue matters if you ever do consume it directly. Six of the 152 types accept two different payload shapes under the same type string: created and updated on salesOrder, product and invoice. One shape is a slim notification, two or three fields and no more allowed. The other is a full document, and Xentral names it in the spec as the fat event.

So a consumer keyed on the type alone is keyed on something that does not determine the body. Where a system does send events the subscription and delivery side is its own topic, and unified webhooks covers it.

What runs here instead is a read on a timestamp, and the shape of it is documented rather than improvised. You store when you last fetched, pass it back as lastModifiedAt, and get only what changed since.

The delta read, in the form our own guide documents itjs
const response = await axios.get(url, {
params: {
lastModifiedAt: lastModifiedAt,
},
headers: {
"X-API-KEY": apiKey,
"X-ACCOUNT-KEY": accountKey,
},
});

Two headers and one parameter, the same for Xentral as for every other connected system. The interval is yours to choose and the guidance is to “Poll with the lowest viable frequency”, which on this system has a published ceiling behind it at 100 requests a minute.

The event count, the domains, the two payload shapes, the destination URL limit, the payload envelope and the testing-phase sentence counted and quoted from Xentral’s webhook reference, checked 17 August 2026. The delta read and the polling guidance are our freshness guide. Which objects are enabled comes from our own coverage data.

The mechanics

A key with no expiry, and one way to take it back

There is no handshake on this system. An administrator inside your customer’s instance opens Account settings, then Developer settings, then Personal Access Tokens, names the token in at most 50 characters and copies the value. That copy is the only moment the value is visible: “After you close the window you won’t be able to see the token again.”

What that token then is, in Xentral’s own words: “Personal access tokens enable API-based access to Xentral with unlimited permissions and without expiration date.” No scope and no expiry. What follows in the reference is advice rather than a control: “it is good practice not to share tokens publicly and not to hardcode them into external applications.”

The consequence that gets underestimated is not the breadth of the permission, it is the absence of a deadline. A token that expires forces someone to touch it, and that moment is where a team notices where the value has ended up. Nothing on Xentral ever becomes due, so the noticing has to be scheduled by you or it does not happen.

Xentral also permits the shape that makes this worse, explicitly: “With personal access tokens you can use the API with multiple applications at the same time.” One value in four products is allowed, and it is how one value ends up in four products. The way back is a single door and it is one-way: “You can’t restore a deleted token.”

So the practice this system rewards is one token per consumer, named after that consumer, because revocation is all-or-nothing per token and the name in the list is the only thing that tells you which one you are about to switch off.

The second value at connection time is the instance. A Xentral customer answers on their own host in front of xentral.biz, so apiKey and tenantId are both required, and the id can be prefilled into the page your customer sees when you already know it.

Xentral is also on the headless list, so the pair can be posted straight to an endpoint that returns the account key. For a credential that never expires, keeping that exchange inside your own interface is usually the version a security review prefers.

The creation steps, the admin requirement, the 50-character name, the single view, the unlimited-permissions sentence, the multiple-applications sentence and the non-restorable deletion from Xentral’s authentication reference (as above), checked 17 August 2026. The two required credentials, the prefill and the headless route are our own Xentral documentation.

What you get

One create, and a test project inside the live instance

The write side of this connection is one object today, and the limit is ours rather than Xentral’s. Customers is switched on for creating, and no object is switched on for updating or deleting yet. So through Maesn you can add a customer and not change it afterwards, which is worth designing around now and worth asking about before you assume it is permanent: where the vendor exposes the operation, switching it on is a request rather than a rebuild.

On the read side four objects answer today, and which four is itself informative. Xentral sells order management, stock and multichannel commerce, and what is switched on is the receivables end: customers, invoices, invoice lines and suppliers.

Purchase orders, goods receipts, journals and journal entries are not enabled, while sales orders and items are available on request. A finance use case and a logistics one therefore start from different conversations.

Fifty-five object and operation combinations sit on request across the matrix, which is a different state from absent. Invoices, for one, are readable now and their writing is in that group rather than ruled out. Objects that do arrive come through the common data model, so a Xentral invoice reads like every other invoice you already handle.

None of the above is a rank. Several systems in the catalogue have no updatable object, and it is a property of this connection rather than a place in a table.

Xentral's own answer to how you should test this

The vendor’s testing guide does not describe a sandbox. It describes creating a project inside the live instance, copying its id and tagging test records with it, so that a filter on the project id separates them afterwards.

It also says why that is the current advice, in one sentence: “In the future, we plan to add more granular access control to API tokens, preventing users from accidentally writing data (such as sales orders) into the production environment … some data types, like sales orders, cannot be deleted.”

Three facts meet in that quote and they are all Xentral’s. The token has no permission scope, granular control is a plan rather than a feature, and some records cannot be removed once written. A test that goes wrong against a live instance is therefore not always reversible.

There is also no free Xentral trial to register for, which turns this from an inconvenience into a scheduling problem: the token only exists inside a real instance, and creating it needs an administrator.

Building the first version of an integration therefore means asking a customer for admin access to their live system before you have anything to show them. That is the gap a sandbox closes, and Maesn provides one for every supported system including those without a trial.

The test-project method, the future-plan sentence and the note that some records cannot be deleted from Xentral’s testing guide, checked 17 August 2026. Object states from our own coverage data. The sandbox and the absent trial are our sandbox documentation.

Where our part ends

What stays on your side

Most of what makes this system expensive to build against is absorbable, because most of it is variation rather than limitation. Five things stop being yours:

  • The dialect. Which paging parameters, which sort syntax, which operator set and which date format an endpoint wants is resolved behind one interface, so a per-endpoint version is not a per-endpoint branch in your client.
  • The maturity check. You do not pick a media type, so you do not pick an alpha endpoint by accident and read dummy data at 200.
  • The instance. The id is collected with the key and turned into the host on every request, so no call site of yours builds a customer-specific URL.
  • Custody of the key. It is held against the connection rather than copied into each product that needs it, which leaves one place to revoke from.
  • The error shapes. A 406 from a media type and a 429 from the request budget arrive in the same form as every other system’s.

Three things do not move, and two of them are decisions rather than work:

  • How often you look. Change detection here is a scheduled read, so the interval, how far back each pass reaches and how much re-reading you accept stay yours. The ceiling behind that choice is 100 requests a minute, and Xentral marks the figure as still being evaluated.
  • Which objects you need switched on. The on-request group is large and it is a conversation rather than a roadmap. Having the list before your design is fixed is cheaper than after.
  • Whether your product corrects records in place. Nothing is updatable here today. Accounting practice expects corrections as new documents anyway, so check that against your own use case early rather than designing a write path around it.

And one thing sits with your customer rather than with either of us: the administrator right that makes the token screen reachable at all. That is a sentence in your onboarding instructions, and it is better written before somebody reports that the connection cannot be created.

Object states from our own coverage data, which records what is enabled today rather than what Xentral can technically reach. The request budget and its provisional status from Xentral’s rate limiting reference (as above), checked 17 August 2026.

FAQ

Frequently asked questions

Which Xentral endpoint version should I call?

Whichever version that endpoint carries, because Xentral versions each endpoint separately instead of the interface as a whole. The path holds it, as /api/v1, /api/v2 or /api/v3. The older versionless form still answers and Xentral marks it deprecated, so a new build should not start there.

Why does the same filter work on one Xentral endpoint and fail on another?

Because the operators are defined per endpoint rather than globally. Xentral publishes two filter references, one for the first two generations and one for the third, and the sets differ: isNull, isNotNull and notContains appear only in the newer one. Its own wording is that each endpoint specifies which operations are possible.

How do I get a total record count out of Xentral?

By asking for it. The third generation picks its paging mode from the X-Pagination header, and the default mode returns no total at all because it skips the count query. Send X-Pagination: table and the response carries total and last_page, at the cost of that extra query.

What does a 406 from Xentral mean?

That the media type you asked for does not exist on that endpoint. Xentral builds media types as application/vnd.xentral.VARIANT.vVERSION+DATAFORMAT, and one endpoint can offer several: a default representation, a minimal one and a CSV variant. Asking for a combination it does not publish returns 406.

Why does a Xentral endpoint return data that does not look real?

Because it may be in alpha. Xentral labels maturity inside the media type, and for alpha it states that the endpoint has been released but the data you get is dummy data. The status is 200 either way, so that label is the only signal that the response is not your customer's.

Can I be notified when a Xentral customer record changes?

Not through an event. Xentral publishes 152 event types and none of them covers customers, suppliers or contacts, and through Maesn no Xentral object has an event enabled. What detects the change is a scheduled read: the lastModifiedAt filter through Maesn, or a date filter on the endpoint directly.

How do I revoke a Xentral personal access token?

By deleting it, which is the only route Xentral documents. The token carries no expiry date, so nothing withdraws it on its own, and a deleted token cannot be restored. Everything still holding that value stops working the moment you delete it, which is the argument for one token per product.

Build once on the Unified API.

Xentral puts the version on the endpoint, and the filter dialect, the paging contract and the maturity label travel with it. The next system will draw those lines somewhere else, or not draw them at all. Build against one interface and each difference becomes a field you read instead of a branch you maintain.