Work with EDIFACT as a developer

This developer reference explains how to implement and test an EDIFACT partner contract for Workspace. It is for integrators who build mapping definitions, fixture evidence, or a Partner API client. When complete, you have a reproducible test chain from unchanged EDIFACT bytes to the canonical model and back.

This page does not replace the agreed partner profile or the message description in the UN/EDIFACT directory. Start with Understand and introduce EDI/EDIFACT and set up the partner through Set up an EDIFACT trading partner.

Define the development boundary

Split the implementation into testable units:

UnitInputOutputIt does not own
Transport adapterByte stream and transport metadataUnchanged, completely stored bytesEDIFACT syntax or Commerce effects.
EDIFACT validationBytes and profile revisionValidated interchange and messagesPartner-specific business meaning.
MappingValidated message and immutable mapping revisionSalesOrderRequestV1 or EDIFACT outputTransport and price decisions.
Commerce applicationCanonical order and Commerce bindingOrder, review, or rejectionEDIFACT segments.
AcknowledgementStable syntax, mapping, or business statusCONTRL, APERAK, or ORDRSPTransport receipt.

This separation is the primary regression guard. Do not render a transport failure as APERAK, and do not treat HTTP 202 Accepted as business acceptance of an order.

Preserve the bytes

EDIFACT is a byte-stream contract:

  • Read to EOF and treat an interrupted stream as a failure.
  • Check and store the size and SHA-256 of the bytes actually received.
  • Do not normalize line endings, spaces, character sets, or service characters before the integrity check.
  • Detect UNA before parsing the remaining segments.
  • Honor the release character. ?', ?+, and ?: represent data, not separators.
  • Keep each interchange at or below 16 MiB.
  • For HTTP, use Content-Type: application/edifact or the documented application/octet-stream fallback.

When a client loads files from a text editor, check byte order marks, automatic character-set conversion, and appended line endings. Visually identical text can have a different SHA-256 value.

Validate the envelope before business segments

At minimum, validate these relationships before mapping:

  1. UNB and UNZ form one complete interchange.
  2. Control references and declared message count match.
  3. Every UNH has a corresponding UNT.
  4. UNT references the correct message and segment count.
  5. Type, version, release, and agency in UNH match the active revision.
  6. Syntax version and service characters match the directory contract.
  7. Sender and recipient resolve to the expected trading partner.

Use an EDIFACT-aware parser. A regular expression or strings.Split cannot model release characters, repetitions, components, and segment boundaries reliably.

Version mapping definitions

A profile revision contains a closed declarative mapping definition. For inbound ORDERS, sources are EDIFACT paths and targets are SalesOrderRequestV1 paths. For outbound ORDRSP, the direction is reversed.

An EDIFACT path follows this model:

text
SEGMENT[qualifierElement.qualifierComponent=value].element.component#repetition

Examples:

PathMeaning
BGM.2.1First component of the second BGM element.
DTM[1.1=137].1.2Second component of the first DTM element when its first component is 137.
QTY[1.1=21].1.2Quantity value from QTY qualified by 21.
LIN.3.1First component of the third LIN element.

An omitted component or repetition defaults to 1. Qualifiers and paths must exist in the selected message directory. A plausible-looking segment path that is not valid for ORDERS is rejected.

This shortened example shows the inbound definition format. It is not a complete production mapping:

json
{
  "version": 1,
  "directory": "D.96A",
  "messageType": "ORDERS",
  "direction": "inbound",
  "canonicalType": "SalesOrderRequestV1",
  "rules": [
    {
      "target": "schemaVersion",
      "operations": [{ "kind": "constant", "value": "1" }],
      "required": true
    },
    {
      "target": "externalOrderRef",
      "sources": ["BGM.2.1"],
      "operations": [{ "kind": "normalize", "format": "trim" }],
      "required": true
    },
    {
      "target": "orderedAt",
      "sources": ["DTM[1.1=137].1.2"],
      "operations": [
        {
          "kind": "date",
          "format": "20060102",
          "outputFormat": "2006-01-02T15:04:05Z"
        }
      ],
      "required": true
    },
    {
      "target": "lines[].orderedQuantity.value",
      "sources": ["QTY[1.1=21].1.2"],
      "operations": [
        { "kind": "decimal", "decimalMark": ".", "scale": 3 }
      ],
      "required": true
    }
  ]
}

The declarative contract supports only constant, normalize, date, decimal, coalesce, code_map, and unit_map. Unknown fields, scripts, unsupported paths, duplicate targets, or contradictions between direction, directory, message type, and canonical type are rejected.

Verify the canonical result

A valid inbound mapping produces SalesOrderRequestV1. This shortened but structurally complete example shows the shape:

json
{
  "schemaVersion": "1",
  "externalOrderRef": "PO-1001",
  "buyer": {
    "scheme": "GLN",
    "qualifier": "14",
    "value": "4000001000001"
  },
  "seller": {
    "scheme": "GLN",
    "qualifier": "14",
    "value": "4000002000008"
  },
  "orderedAt": "2026-07-16T00:00:00Z",
  "currency": "EUR",
  "lines": [
    {
      "lineNumber": 1,
      "item": { "gtin": "04012345000016" },
      "orderedQuantity": { "value": "2", "unit": "PCE" }
    }
  ]
}

Observe these invariants:

  • schemaVersion is "1".
  • Buyer, seller, external order reference, order date, and currency are unambiguous.
  • Every line has a positive, unique line number.
  • At least a GTIN, SKU, or customer catalog reference identifies the item.
  • Quantities are positive decimal strings without comma or exponent.
  • External prices and tax rates are input evidence; Workspace validates price, tax, and Commerce bindings authoritatively.

Compare expected JSON semantically as a closed canonical object. Duplicate keys, unknown fields, trailing JSON values, and an empty expected payload are invalid fixture expectations.

Build the fixture suite

Create a compact but meaningful suite for every partner profile:

CategoryMinimum case
PositiveComplete order with several lines and realistic identifiers.
OptionalityPermitted address, price, or date fields are absent.
QualifierThe same segment with another qualifier must not map accidentally.
EscapeData includes an escaped service character.
EnvelopeWrong reference, segment count, or message release is rejected.
MappingRequired reference or item identifier is absent.
CodesUnknown unit, currency, or partner identifier is handled explicitly.
LimitsMaximum agreed line count and payload size.
IdempotencyThe same interchange is retried after an uncertain network result.
OutboundORDRSP is generated byte-for-byte against the approved expected object.

Do not keep fixtures as unbound local truth. Store input and expected output as authorized Storage objects, bind their SHA-256 values to the profile revision, and start server-side verification. Create new evidence after a change; never reinterpret a terminal verification result.

Implement a robust Partner API client

The production client has two independent loops:

Submit ORDERS

  1. Produce exactly one complete interchange.
  2. Store the bytes durably and calculate SHA-256.
  3. Create one stable idempotency key for those exact bytes.
  4. Submit the bytes to /inbox.
  5. After an uncertain network result, retry with the same key and bytes.
  6. Store uploadId and interchangeId as technical correlations only.

Process the mailbox

  1. List due deliveries with a cursor.
  2. Claim exactly one entry.
  3. Read its payload to EOF with the claim token.
  4. Verify size, SHA-256, and the final integrity trailer.
  5. Store and process the bytes locally and idempotently.
  6. Acknowledge only then with the same claim token.

The complete HTTP contract, including scopes, headers, status codes, and retry semantics, is in Integrate the B2B Exchange Partner API.

Handle failures by stage

StageExampleDeveloper action
TransportTimeout or interrupted downloadDo not process bytes; continue under the same idempotency or claim contract.
SyntaxInvalid envelope or unknown syntax versionCheck the partner profile and original bytes; do not retry unchanged forever.
MappingRequired path is absent or a code cannot be translatedCreate a new revision and new fixtures; do not mutate the active revision.
ApplicationItem is missing or a Commerce gate requires reviewPerform business review; do not use mapping as a data-repair mechanism.
OutboundPartner destination is unavailableRetry delivery of the same stored bytes under the controlled policy.

Log the HTTP status, stable error code, your correlation, connection ID, and time. Never log an API key, claim token, credential, complete EDIFACT payload, or raw internal error.

Accept the implementation

Your implementation is ready when:

  • parser and mapping process the same fixtures reproducibly,
  • positive and negative server-side fixture evidence succeeds,
  • the real transport has passed complete integrity checks,
  • network retries create no second Commerce effect,
  • the client processes CONTRL, APERAK, and ORDRSP separately,
  • a new partner-profile version creates a new immutable revision, and
  • logs and error responses disclose neither secrets nor payloads.