The contract between their fields and ours.
Every integration is a mapping you design against a real record pasted from the other system — and a dry run that names four things before you trust it: what would land, what is missing, what they send that nothing reads, and which transforms fail on the actual value. “Silently dropped” is the sentence this screen exists to delete.
Bilingual EN / FR · Published API contract · WCAG 2.2 AASix things make an integration
An integration here is not a switch you turn on and hope about. It is a contract between somebody else's fields and yours — written down, checked against evidence, and refused until it holds.
An entity
Vendors, parts, assets or work orders. Their record on one side, ours on the other, and the contract in between.
A direction
Inbound or outbound, recorded on the contract and shown on the register beside the system it talks to.
A real sample
One actual record, pasted from the other system. It is the dry run's food — everything below is computed from exactly this.
Field mappings
Their field as it appears in the sample, our field chosen from what the entity offers, and one of five transforms applied live.
Required fields, marked
An inbound vendor without a name is not a vendor, it is a row of noise — so the required ones carry a badge on the row and a refusal at the gate.
A state
Draft until every named blocker is cleared. Enabling is gated on exactly the arithmetic the dry-run panel is already showing you.
Four lists, and none of them is optional
Paste one real record and the panel answers immediately: what would land, with the value the transform actually produced; what is mapped but absent from the sample; what the other side sends that nothing on your side reads; and which transforms failed on the real value rather than the type they were promised.
- The value, not the transform's name — “
currency = CAD ← WAERS” shows what uppercase did to the actual string, so a wrong transform is visible rather than plausible. - Mapped but missing is reported, never skipped — a mapping reading a field they do not send is a column of blanks in production, discovered a month later.
- What they send that nothing reads is named — this is the list that catches the field somebody added to the export last quarter and told nobody about.
- Failing transforms carry the value —
"not recorded" is not a date, rather than “type mismatch on field 7”. - It recomputes on every keystroke — edit the sample or change a transform and the four lists move under your hand, so it fails here instead of in production.
Draft until every named blocker is cleared
A contract you cannot check is a contract you find out about in production, usually from somebody in another department. So enabling is refused until the dry run comes back clean, and every refusal names its own field rather than announcing that something, somewhere, is invalid.
- The sample must be JSON — otherwise nothing can be checked against it, and the panel says exactly that rather than showing four empty lists.
- Every required field must be mapped — by name, so the fix is obvious rather than a hunt through a schema.
- Every mapping must find its field — in the sample you pasted, which is the only evidence anybody has.
- Every transform must survive the real value — the one that actually arrived, not the one the specification promised.
- Unread fields never block — what they send and nobody consumes is information worth having, not an error worth stopping for.
A published contract, and a client generated from it
Beneath the mapping designer there is a real API: a typed client built from an OpenAPI document, speaking the same envelope the product already uses internally. Moving a screen onto the server changes where truth lives, not how the screen asks for it.
A refusal over the wire looks like a refusal in memory
A success comes back as a value; a refusal comes back as a reason and the fields it names — and a 422 from the server surfaces as the same typed result an internal command returns. That is what lets a screen move onto the server without being rewritten around a new error convention.
- A 401 arrives in the same envelope — so the shell can tell “your session ended” from “something broke”, which are different sentences to show somebody.
- The server being away is a state, not an exception — “unreachable” is a reason like any other, and the console renders it as one instead of a stack trace.
- The token is read fresh on every request — a client holding the value it was built with keeps sending a token that was revoked ten minutes ago.
- Transport is injected, not imported — tests hand it a fake and the browser hands it the real thing, which is also what keeps it usable from a script or a field app.
- Blockers come back as a list — the accept endpoint refuses with the same blockers the register enforces, so the API and the screen cannot disagree about why.
Re-point a live site without a rebuild
Where the interface believes the server lives is decided in three places, in order — so a deployed site can be pointed at a different API by editing one file, rather than rebuilding and redeploying the whole bundle to change a URL.
- This browser's own choice first — set on the server console for one developer against one branch, and never seeded into anybody else's session.
- Then a config file beside the page — the one that matters in production: a static site is re-pointed by editing a single file in storage.
- Then a value baked in at build time — for deployments that would rather pin it than ship a config file.
- The development default is localhost, on purpose — a deployed site with no configuration must fail to find an API rather than accidentally find somebody else's.
- Identity has its own address — sign-in and the register API are different services, and sharing one base URL would point the login screen at a server with no login endpoints.
Readings come in. Nothing goes back out.
A point from a building-automation system or a controller, mapped onto a meter, with a linear transform you can read on the row. The boundary is deliberate, and it is printed on the screen rather than buried in a datasheet: readings come into Zenith, and nothing here sends a command or a setpoint back to a device.
A device reading obeys every rule a typed one does
Readings arrive through the meter register's own command — the same door a person typing on a round goes through. So a reading that runs backwards on a cumulative meter, or arrives implausibly large, is refused by the register rather than waved through because a machine sent it.
- The transform is on the row, not in a stored procedure — the panel reports kPa and the meter is in Pa, so the scale is 1000 and you can see it, argue with it, and change it.
- Refused attempts are kept, loudest — with the reason the register gave, in the register's own words.
- Inbound only, and the screen says so — a badge on the page, because a boundary nobody can see is a boundary somebody will assume away.
- The run log is already the door — the same log a real dispatcher will write through, so history does not restart the day transport lands.
What is designed, and what is the server's half
An integration module is the easiest thing in a maintenance system to sell on a diagram. This one draws the line where its own code draws it — and prints it at the foot of the register rather than leaving you to find out in month four.
- Transport names are recorded, and honest about it — REST, file drop and webhook sit on the contract; the dispatcher that speaks them is the server's half.
- The API underneath is not a mock — a real typed client over a published contract, already carrying sign-in, sessions and refusals.
- Nothing is dressed up as connected — a screen that has not moved onto the server says so, on the screen.
- The contract is the part that has to be right first — a schedule pointed at a mapping nobody checked just fails faster and more often.
Everything around the mapping
Five transforms
As-is, uppercase, lowercase, date to ISO, and number — each applied to the sample live, so the one you picked is judged on the value that actually arrived.
Twenty target fields
Five for each entity, with the non-negotiable ones badged on their own row rather than discovered at the gate.
One query contract
Search, filters, sort and paging as a single shared document — the same one the registers already speak, so a screen asks the server exactly what it asked memory.
Edits apply in place
Change a transform and the dry run updates underneath it. There is no save button to forget and no draft state to reconcile.
A health check with an answer
It reports which identity store replied — the live one or the seed — so “am I looking at production?” stops being a guess in a support call.
Generated, not hand-written
The client comes from the OpenAPI document by a generator, so the published contract and the code that calls it cannot quietly drift apart.
Sign-in that survives a change
Sessions, groups, site membership and a forced password change all arrive through the same client — with the token re-read on every call rather than captured once.
Your words, everywhere
Direction, entity and status all read through your own vocabulary, so a contract describes itself in the terms your organization already uses.
Two languages, all the way down
Every label, hint, empty state and blocker exists in English and French — including the sentence that tells you a contract cannot be enabled yet.
Checkable before anything moves
Integrations fail quietly, months after anybody was watching, and the post-mortem is always the same: a field nobody mapped, a value nobody validated, a message nobody kept. Everything here is built against those three specifically.
Evidence, not assumption
A contract is judged against one real record you pasted, not against a specification somebody wrote about the record.
One envelope, either side
A refusal in memory and a refusal over the wire have the same shape, so nothing is rewritten as a screen moves onto the server.
Bilingual by birth
Full en-CA / fr-CA parity on every screen, hint, empty state and refusal — plurals handled properly, not by concatenation.
Accessible to everyone
WCAG 2.2 AA as a build rule: real table semantics, states announced in words, and focus that moves to the field an error refers to.
Before you book the demo
Do you have a SAP connector?
Is there a real API, or just mappings?
What formats can you accept?
Can I map nested fields, or arrays?
customer.address.city and repeating groups are not
supported, and a payload that is an array of records cannot be dry-run at all. If your
export nests, the honest answer for now is to flatten it on the way out.
Are there default values, lookup tables or custom transforms?
CLSD to your Closed. A mapping layer with an expression
language is one nobody can read at three in the morning — and the dry run only stays
legible because every row is one field and one rule. Value translation is a fair ask and
it is on the list.
What happens when the server is unreachable?
Can a deployed site be pointed at a different API?
Do you write back to a BMS, or send setpoints?
Do device readings skip the usual validation?
What about scheduling, retries and run history?
Is a contract versioned or audited?
How would we start?
Bring one row from the other system.
A single vendor from SAP, one work order export, a line from a BAS point list. Paste it in and we'll map it in front of you — and the dry run will tell you on the spot which of their fields nothing at your end reads, and which of yours nothing supplies.
