Unified error handling turns every failure into one predictable shape
Raw APIs disagree about where an error lives, what it is called and whether it is worth retrying. Maesn returns one envelope with a standardised category, catches invalid input before it is sent and passes the target system's own error through intact.
"errors": {
"type": "target_system_error",
"statusCode": 400,
"downstreamErrors": [{
"statusCode": 500,
"message": "Unknown currency"
}]
}What is unified error handling
Integrations are judged on how they behave when something goes wrong, not when everything works. The happy path is the easy half: you can build it against one system in an afternoon. The other half is knowing, at three in the morning, whether a failed sync means a malformed payload, a customer who revoked access, an endpoint that never existed for that system, or a provider having a bad hour, and which of those is worth trying again.
Unified error handling is the layer that makes that answerable. One envelope, one standardised set of categories and one documented retry policy, applied across every system, so your failure handling is written once. It leans on the same normalisation as the common data model: because Maesn knows what a valid object looks like, it can reject a bad one before any system sees it.
Every system fails in its own dialect
Failure is the least standardised behaviour of any API, and the one you meet under the most pressure. Four questions, four different answers per system.
One envelope, six useful fields
Errors arrive in a single top-level object with the same fields every time. Two of them are the reason this is more than cosmetic normalisation.
A category, not a sentence
Every error carries a standardised type, so your code branches on the kind of failure instead of matching strings.
Caught before it is sent
Maesn checks field formats, required values and data types first, so invalid input fails fast with the field named.
Downstream errors kept
When a target system rejects a request, its original message and status come through untouched instead of being summarised away.
Retry guidance per code
Which responses are worth retrying, which are not and how long to wait is documented rather than left to trial and error.
"errors": {"type": "invalid_parameters","statusCode": 400,"message": "Invalid parameters, please checkthe validationErrors array.","context": {"targetSystem": "snelstart","unifiedApi": "Accounting"},"validationErrors": ["Contact type must be COMPANY."]}
The two that carry the weight are statusCode and downstreamErrors, and one is normalised precisely so the other does not have to be. HTTP is specific about which code belongs to which situation, and systems implement that as loosely as they implement everything else: a validation failure, which no amount of retrying will fix, comes back from some systems as a 500. A 500 says the opposite, that something broke on their side and the same request may well succeed in ten minutes. Maesn answers with the code the situation actually calls for, so your retry logic reads a category instead of a mistake.
Correcting the code would normally cost you what sits underneath it, and that part is often the only useful one: which field was rejected, what the system expected instead, or a status of its own that means something only there. So the original is not replaced. It travels alongside the corrected one in downstreamErrors, which is why your application can branch on the category while a developer reads the system's own words, rather than the two being traded against each other.
Not every problem answers with an error. A call can succeed and still not do everything you asked, and that arrives as meta.warnings on the response: a field the target system does not use, or a value it ignores because it does not support it, such as a currency outside the ones it handles. It is written for the developers integrating rather than for the end user, and it is what separates a write that worked from a write that worked the way you meant.
Ten types, five decisions
The type field is the part worth building against. Ten standardised values cover every failure the Unified API produces, and each one implies a different response: fix and resend, ask the customer to reconnect, stop trying because the endpoint does not exist for that system, or back off and try later. Grouping them that way turns error handling from a pile of special cases into four or five branches you write once.
What it deliberately does not do is flatten the detail. A rate limit from a target system arrives as its own type rather than being folded into a generic failure, because the right reaction differs from a server error, and because the limit belongs to the target system and is yours to respect. Pacing runs on the asynchronous endpoints, so on a synchronous call the type is the whole signal. An expired connection arrives as its own type too, because the fix is not a retry but a customer walking back through authentication.
Your request
Fix it and resend
Authorization
The customer reconnects
Not available
Do not retry, it will not appear
The target system
Read the downstream error
Temporary
Back off and retry
What to try again, and what never to
Retrying the wrong error wastes traffic and hides a real bug. Retrying the right one is often the difference between a failed sync and a slow one.
Two layers are at work here and it is worth keeping them apart, starting with the fact that the table above is written for your code rather than describing ours. Maesn's layer is the classification: the failure arrives with a category, with the system named and with the original response attached, and invalid input is caught before it ever reaches a system. The retry is yours. Maesn retries and paces on the asynchronous endpoints, and on a synchronous call the target system's 429, 500 and 503 pass straight through for your application to act on. That same queue releases one tenant's requests in sequence, which is what keeps two token refreshes from overlapping and costing your customer a re-authentication.
When you do need to know what actually happened on the wire, the request, the response and every event around them are recorded, so debugging an intermittent failure is a matter of looking it up through logging and monitoring rather than reproducing it.
One failure path, every system
- Parse a different error shape per system
- Guess which failures are retryable
- Lose the original message on the way
- Discover invalid input from the system
- One envelope with a real category
- Retry guidance per response code
- Downstream errors passed through
- Invalid input caught before sending
Error handling is where integration projects quietly lose their margin. Not in the first build, but in the second year, when every new system adds another failure dialect to a support runbook nobody wants to own, and a customer asks why their invoice did not sync. Because the envelope, the categories and the retry policy are Maesn's rather than each vendor's, that runbook is written once and stops growing. The system your customer picks next changes which logo is on the connection, not how your code handles a bad day. The runbook that stops growing is also the one customer success teams have to work from when a customer is already on the line.
Common questions
What does a Maesn error look like?
Why does the standardised type matter?
What are validation errors?
Do I lose the original error from the system?
Which errors should I retry?
What happens when a target system has an outage?
How would I know a value was ignored rather than rejected?
What if a connection expires?
How do I tell an unsupported endpoint from a broken one?
Build once on the Unified API.
See how unified error handling works for your integration, or dive into the technical reference.











