All updates

Inbound mappings: normalizing what your partners send you

Adrian Duyzer

Adrian Duyzer

feature

When a partner sends you an EDI document, Tediware translates it to JSON and hands that JSON to your system. Here’s a fragment of a translated 204, the ship-to party on a delivery stop:

"party_identification_N1_loop": [
  {
    "party_identification_N1": {
      "entity_identifier_code_01": "ship_to_ST",
      "name_02": "DC 7034",
      "identification_code_qualifier_03": "global_location_number_gln_UL",
      "identification_code_04": "0012345045535"
    },
    "geographic_location_N4": {
      "city_name_01": "SMYRNA",
      "state_or_province_code_02": "DE",
      "postal_code_03": "19977"
    }
  }
]

ship_to_ST is the translator being helpful: it decodes coded elements into a readable label with the bare code stuck on the end, so you get ship_to_ST rather than ST, case_CA rather than CA. The parties come back as an array of occurrences that you have to pick through by qualifier, since a document can carry a ship-to, a bill-to and a shipper in the same list, in whatever order the partner emitted them.

None of that is a problem while you have one partner. You read the translation, you write your ingestion code against it, you’re done. Then the second partner shows up sending the same document type with a different set of segments present, possibly on a different X12 release, and your reader grows a branch. Then a third one shows up


Inbound mappings move that normalization into Tediware. You define a canonical shape once, and each partner gets a JSONata mapping from their translation into it, assigned on that partner’s inbound transaction setting. Your ingestion code targets one structure no matter who sent the document.

The canonical shape

An inbound mapping’s target is an implementation you own, something like “Canonical 204”. That’s the same machinery outbound mappings already use: the Validate pane runs your mapped output against the implementation’s JSON schema, and the AI assistant uses that schema as the target when it writes the transformation.

The canonical shape is X12-shaped. This feature buys you consistency across partners, and an arbitrary reshaping into your own domain model is out of scope for now. What it does change, relative to raw translation JSON, is the stuff that makes translation annoying to consume: code elements carry bare codes, so the fragment above becomes "entity_identifier_code_01": "ST" and "identification_code_qualifier_03": "UL"; segments the canonical implementation defines as occurring once come through as a single object, so a read like date_time_G62[0].date_02 becomes date_time_G62.date_02; and anything your system doesn’t consume can simply not survive the mapping.

You don’t have to build the canonical implementation from scratch, and I’d rather you didn’t. Authoring one by hand takes real X12 knowledge and tempts you into specifying fields no partner actually sends. Duplicate a live partner’s implementation and rename the copy, and every field in it is a field you genuinely receive. One canonical implementation covers partners on different releases too: inbound mapping selection matches on the transaction set code, so a canonical 204 built from a 5010 partner is a valid target for a partner sending 4010.

Once you’ve got your initial canonical implementation built, adding to it and modifying it is easy: just edit the implementation.

You might not need this

Inbound mappings are optional, and plenty of teams should stay without them indefinitely. Partners implementing the same document type frequently translate to similar or identical shapes, and you can onboard several before you hit a difference that matters. When the difference is one renamed key, handling it in your own code is simpler than maintaining a mapping per partner.

The case for adopting them is when partner shapes have genuinely diverged in ways your code branches on, or your partners span X12 releases, or every new partner means new parsing code and you’d like your ingestion contract to stop depending on anyone else’s choices. Starting without mappings doesn’t lock you out of any of that later.

The honest caveat, which I’d rather say up front than have you discover during a cutover: turning on your first mapping changes the shape your system receives. That’s a one-time change to your ingestion code and it’s the last time the shape changes, since after that new partners are absorbed by their mappings. The path we recommend is to promote your first partner’s implementation into the canonical one and write your ingestion code against the mapped shape from the start, so the cutover never happens at all.

What API consumers see

Following the documented artifact rule, the "usage": "output" artifact on a result is the delivered document. On a mapped inbound flow that artifact is now the mapped document. Previously it was the extractor’s carried-forward output, meaning you downloaded the unmapped translation and a working mapping looked exactly like a mapping that had never run. The translation is still there on the same result, saved as the "usage": "translation" artifact, which is what you want for reproducing a bad mapping.

Delivery always happens, including when the mapping is broken, because the partner’s data has already arrived and we shouldn’t stop it reaching you. And the canonical shape is checked, not assumed: every mapped document is validated against the canonical implementation’s schema before delivery, the same check outbound documents get before they’re sent to a partner. A mapping can fail two ways, and both set mappingFailed: true on the result’s detail with a mappingError telling you which. If the transformation fails to run ("kind": "expression"), the delivered output is the untransformed translation. If it runs but produces output that doesn’t match the canonical shape ("kind": "validation"), the delivered output is that mapped document, with the schema violations listed in mappingError.errors. So the contract is one flag: unflagged means the output passed validation and is the canonical shape; flagged means route it to an exception path instead of parsing it as one. Infrastructure problems, 5xx responses and timeouts from the transform service, still raise and go to the error path, so a transient outage doesn’t quietly ship you unchecked documents as successes.

Getting the codes back with $code

Since the canonical shape carries bare codes and the translation carries decoded ones, every inbound mapping was reimplementing the same string split. There’s now a $code function registered in JSONata, in the editor and at runtime both:

$code("ship_to_ST")   →  "ST"
$code("case_CA")      →  "CA"
$code("ST")           →  "ST"

It returns everything after the last underscore, X12 codes never containing one. A value that arrives bare comes back unchanged, so it’s safe on codes the translator couldn’t decode. Use it only on qualifiers and code elements: applied to free-form text or a reference value that happens to contain an underscore, it will happily truncate the value for you.

In the editor

Direction is chosen when you create a mapping, and inbound requires an implementation, since the canonical target is the point of the feature. After that the editor is the editor, with a few differences. Inbound mappings have a direction badge, and the mappings list now marks direction with a blue or green square in front of the name.

The source pane is where inbound differs most. An inbound source has to be something the translation pipeline could actually have produced, so you can’t have it generated for you and there are two ways to get a real one. From sample EDI takes a pasted or uploaded sample file from your partner, translates it, and drops the bare transaction set in as your sample, with a picker if the file holds more than one set. Create source appears wherever you view a result’s JSON payload, so a real processed document becomes a source in a couple of clicks. Sources are capped at 25KB, which fits a realistic single document, though a fat one will need its repeated line items trimmed down to a representative few.

The AI mapping assistant knows which direction it’s working in. On an inbound mapping its context explains how translated loops render and tells it to select entries by qualifier rather than by position, since occurrence order and count vary between documents; it’s told to use $code where the target wants bare codes, and that $buildHL and $placeholder are outbound tools it has no business reaching for here. Placeholders don’t apply inbound at all (they mark data an outbound source can’t supply), so extraction is skipped. The Output pane shows JSON only, an inbound mapping’s output being JSON by definition.

These are schema-to-schema transformations, which is about as mechanical as mapping work gets, so the assistant’s first drafts tend to be good.

Try it

Create a mapping, choose Inbound, pick your canonical implementation, and load a source with From sample EDI using a sample file from the partner. Assign the mapping on that partner’s inbound transaction setting and run a document through.

The full writeup lives in the Mappings help, and the consumer side is in the Inbound EDI API docs. If you set one up and it behaves oddly, or the canonical shape isn’t giving you what you hoped, email me at adrian@tediware.com.