maesn
Product insight

Handle every customisation outside the model, still inside your integration

Real installations carry things a shared schema cannot. Raw Data returns the untouched payload next to the normalised one, Authenticated Passthrough calls any native endpoint directly, and both keep the base URL, the credentials and the tenant identifiers on Maesn's side.

Outside the modelstill reachable
A field outside the modelrawData
A native endpointpassThrough
An object type of its ownpassThrough
maesn keeps you authenticated
Two documented switches
GET  /contacts?rawData=true
POST /accounting/passThrough
Trusted by winning software teams
HubSpotTipaltiPaywiseRallyQredNordhealthFindityFintoClockinHEROHolviLanes & PlanesHubSpotTipaltiPaywiseRallyQredNordhealthFindityFintoClockinHEROHolviLanes & Planes
The concept

What is customisation handling

A common data model is an intersection. It carries what systems genuinely share, which is what makes one invoice object work across all of them, and it therefore cannot carry what only one customer's installation has. That is not a gap in the model, it is the definition of one. Every serious integration meets the moment anyway: a field somebody added years ago that a report depends on, an object type the vendor never standardised, a native call that does something no unified endpoint offers.

Customisation handling is the answer to that moment, and there are exactly two documented mechanisms. Raw Data is for reading more than the model returns, described in the docs as access to data that is not covered by the unified responses. Authenticated Passthrough is for making calls the unified API does not make, described as calling system endpoints even if they have not been explicitly implemented. One widens the response, the other widens the surface.

Both are escape hatches, and an escape hatch is judged by what it makes you rebuild. These two remove the normalisation and nothing else: the authentication, the base URL and the identifiers that say which tenant you mean all stay where they were. Going off the common data model for one field therefore does not mean going off Maesn, and the model stays the default path for everything else in the same codebase.

The problem

The usual escape hatch drops you back to zero

Most integration layers offer a way around themselves. The question is the price, because the moment you leave the unified endpoint, four things are usually yours again.

The base URL
per systemper versionper install
The credentials
OAuth refreshtoken storagea second login
The tenant identifier
realmIdcompanyIdenvironmentName
The protocol
RESTGraphQLSOAP envelopes

None of those four is hard on its own. Together they are a second integration living next to the first, and it is the one nobody documents. The base URL differs per system and sometimes per installation, which means it cannot be a constant. The tenant identifier has a different name and a different source in every system: a realm ID on QuickBooks, an environment name on Business Central. And the protocol is not even always REST: some systems answer GraphQL, others still speak SOAP, and both need shapes a REST client does not produce.

The credentials are the item that reads smallest and costs most. They belong to the customer, and the access and refresh tokens behind a Maesn connection are not yours to read, so a call you make on your own needs an authorisation of its own. The same customer clicks connect a second time, signs in to a system they already connected, and from then on maintains two connections to it, one of which exists because of a single endpoint. Storing those tokens and refreshing them is then also yours, and that is the half of this cost that usually gets mentioned.

So the useful question is not whether a way around the abstraction exists. It is which parts of it you keep when you use one, and which single part you knowingly give up.

Raw Data

The full payload, next to the normalised one

One query parameter on a GET request. The unified object still arrives, and the record as the system returned it arrives beside it.

One response to GET /contacts?rawData=true
data

The normalised object, the same on every system.

"id":"123"
"name":"John Doe"
"email":"test@email.com"
rawData

The record as the system returned it, untouched.

"id":"123"
"name":"John Doe"
"email":"test@email.com"
"extraField":"extra data"

Setting rawData=true does not switch the response into a different mode, it adds to it. Your existing code keeps reading data exactly as before, and the one field you were missing is in rawData next to it. That is the difference between an escape hatch and a fork: nothing has to be rewritten to look at what the system actually said.

That also makes it a debugging tool, which is the use nobody plans for and everybody ends up needing. When a value looks wrong in your product, having both halves of the same response side by side tells you immediately whether the source system sent it that way or whether it changed on the way through. Without that, the same question turns into a support ticket and a guess.

It is available on all GET endpoints and for all target systems, which also means it composes with everything else those endpoints do. Because it is the same request, the same filtering, ordering and paging still apply, so you can ask for the last day of changes and get the untouched payload for exactly those records.

Authenticated Passthrough

One endpoint, whatever the system speaks

Passthrough is one POST /accounting/passThrough carrying three things: the relative path, the method and, for writes, the body. All five methods are supported, so a native read, create, update or delete is the same call with a different verb. There is no separate credential, no separate host and no second client to configure.

The part worth looking at is what happens for systems that do not speak REST. A GraphQL system takes no path at all and your query travels in the body. A SOAP system takes no path either and you send the body contents base64 encoded, which Maesn then wraps in the envelope that system expects. Three transport worlds, one request from where you sit.

What the system speaks
RESTfields in bodypath + method
GraphQLbody.queryno path at all
SOAPbody.xmlbase64, body only

For SOAP you send only the contents of the body element; the envelope, the access token and the company code are added around it.

one passThrough call
What you send
POST /accounting/passThrough

One endpoint, two headers and a relative path. The transport differences stay on Maesn's side of the call.

The word doing the work in the feature name is authenticated. The customer connected their system once through unified authentication, and that connection is what carries a native call too. You send your two Maesn headers; the target system's own token, the access token inside a SOAP envelope and the identifiers that tell it which tenant you mean are added on this side of the call.

That is the part a call built outside cannot borrow. The tokens are not yours to read, so your own endpoint needs its own authorisation, and the customer signs in a second time to reach a system they have already connected. Passthrough is the escape hatch that never asks for it: one consent, one connection, and the native call travels on that.

The trade

You give up the shape, not the connection

A native response arrives in the system's own form, with its own field names, and the mapping for it is yours. Everything underneath the shape stays where it was.

What you do not have to rebuild
The base URLMaesn stores the base URL of every system and joins your relative path to it. You send /employees, not a full URL.path
The credentialsThe call carries the same two Maesn headers as any other request, and the target system's own token is added on our side.X-API-KEY
The tenant identifierValues a system needs to know which tenant you mean are stored at authentication, so a QuickBooks realm or a weclapp id is filled in for you.realmId
The SOAP envelopeSend the body contents base64 encoded and the envelope, the access token and the company code are wrapped around it.body.xml
The GraphQL entrySystems that speak GraphQL take no path at all: the query travels in the body and still reaches the right endpoint.body.query

Being precise about this is more useful than promising that customisation is free. What you give up when you leave the unified endpoint is normalisation: one response shape, one set of field names, one way to read every system. What you keep is everything in the table above, which is the part that takes the longest to build and never stops needing maintenance.

Two things do not change either. A native call travels on the customer's own connection, so it can do what that connection can do and no more. And the target system's own rate limit still applies to that traffic, the same published limits that asynchronous processing lists per system, so a batch of native calls spends the same budget as any other request.

Why it matters

The edge case stops being a second project

Building it yourself
  • A second integration for the edge cases
  • Base URLs and tokens per system again
  • Tenant identifiers threaded by hand
  • SOAP and GraphQL handled separately
With Maesn
  • One switch for the full payload
  • One endpoint for any native call
  • Authentication stays Maesn's job
  • Every protocol behind one request

Integration projects are rarely lost on the standard case. They are lost on the one customer whose setup is not standard, because that is where a clean abstraction usually turns into a direct integration built beside it, with its own credentials, its own host and its own bugs. Before long there are two integrations to maintain for every system, and only one of them is documented.

With one query parameter for the full payload and one endpoint for any native call, the exception stays inside the same integration. Your customer gets the field they asked about, and you do not sign up for a parallel code path to deliver it.

There is a second effect that shows up in your own backlog. Without a way to reach past the model, every unusual requirement arrives as a request to you: add this field, expose that object, support this one system differently. Those requests accumulate faster than they can be built, and each one competes with the roadmap. When customers can reach the data themselves, the request stops being yours to schedule, which is worth more than the feature it replaces. A roadmap that stops absorbing one-off field requests is the clearest thing product and engineering teams get back here.

Customisation Handling FAQ

Common questions

What is Authenticated Passthrough?

A single endpoint that lets you call a target system directly when the call you need is not part of the unified API. You send a POST to /accounting/passThrough with the relative path, the HTTP method and, for writes, a body. The request is authenticated with your Maesn keys and carried through to the system, so nothing in the system is off limits just because Maesn has not unified it.

What is Raw Data?

A query parameter. Add rawData=true to a GET request and the response keeps its normal unified shape and carries the untouched payload from the target system in a rawData field beside it. It works on all GET endpoints and for all target systems, so reaching one field the model does not cover never means abandoning the model for that resource.

Do I have to choose between the two?

No, and they answer different questions. Raw Data is for reads where the unified object is the right object but you need something extra that came back with it. Passthrough is for calls the unified API does not make at all, whether that is an endpoint, an object type or a write the system does its own way.

Can we use Raw Data to debug a wrong value?

That is one of its better uses. Because the unified object and the untouched payload arrive in the same response, you can compare them directly and see whether a discrepancy came from the source system or from the normalisation on the way through. That turns a question that would otherwise become a support ticket into something you can answer yourself in one request.

How does this help us as a software vendor, not just our customers?

It takes requests off your backlog. Without a route past the model, every unusual requirement arrives as a change request for your team: expose this field, add that object, handle one system differently. Those pile up and compete with your roadmap. When a customer can reach the data themselves, the edge case stops being something you have to schedule.

Which HTTP methods does passthrough support?

GET, POST, PUT, PATCH and DELETE. A body is required for POST, PUT and PATCH requests, and for REST systems the fields go directly into that body object.

Do I need the full URL of the target system?

No. You send the relative path, for example /employees or /units, and Maesn joins it to the base URL it stores for that system. The documentation lists that base URL per system so you can see exactly which part of a native URL to leave out. If you are not using the Interactive Authentication Flow, some systems additionally expect environmentName or companyId as query parameters.

What about systems that are not REST?

They work through the same endpoint. GraphQL systems take no path at all: your query goes into the body as a query field. SOAP systems take no path either: you send the contents of the body element as a base64 encoded xml field, and Maesn wraps the envelope around it, including the access token and the company code.

Is the passthrough response normalised?

No, and that is the honest trade. You get the system's own response, in its own shape, with its own field names, so any mapping is yours to do. What you keep is everything below the shape: one endpoint, your Maesn headers, the stored base URL, the tenant identifiers and the protocol wrapping.

Are there system specific quirks I should know about?

Several, and they are documented per system. BuchhaltungsButler accepts only POST requests even when you are reading, with the operation expressed in the path. Moneybird expects the path in a resource and format notation. QuickBooks realm IDs and the weclapp and Xentral identifiers are stored when the customer authenticates, so you never pass them yourself.

Do the target system's rate limits still apply?

Yes. A native call through passthrough is still traffic arriving at that system, and the system's own limits apply to it like any other request. The published limit for each connected system is documented, so you can reason about how much of it a batch of native calls will use.

Build once on the Unified API.

See how customisation handling works for your integration, or dive into the technical reference.