Diff
POST /v1/diff compares two messages by meaning, not by text. Both sides go through the canonical model, so an MT message and an ISO 20022 message can be compared field by field.
Why a semantic diff
A text diff of an MT103 and the pacs.008 produced from it shows that every line differs, because the two syntaxes have nothing in common on the wire. POST /v1/diff reads both messages into transmute's canonical model, the same format-neutral JSON that Normalize returns, and compares the two documents field by field. Two messages that carry the same business content compare equal whatever their syntax.
Use it to:
- check a conversion against the original before you switch a payment flow from MT to ISO 20022
- compare your own converter's output with transmute's during a migration
- see exactly what changed between two versions of the same statement or payment
The request
application/json only. Each side carries a message and, optionally, its format:
| Field | Required | Meaning |
|---|---|---|
left.message | yes | The first message as a string: MT, ISO 20022 XML, or canonical JSON |
left.from | no | Format id of the left message. Omit it to auto-detect |
right.message | yes | The second message |
right.from | no | Format id of the right message |
options.srVersion | no | Standards Release used to read both sides |
The two sides do not have to be the same format. Because the request is JSON, the messages must be JSON-escaped; jq does that for you. This example compares an MT103 with the pacs.008 that Convert produced from it:
jq -n --rawfile a payment.mt103 --rawfile b payment.pacs008.xml \
'{left: {message: $a}, right: {message: $b}}' |
curl -sS -X POST https://api.transmute.403fin.io/v1/diff \
-H "Authorization: Bearer $TRANSMUTE_KEY" \
-H "Content-Type: application/json" \
--data-binary @-
The request body may be up to twice the single-message cap (4 MiB in total), since it carries two messages. A larger body is a message-too-large 413.
The response
{
"sameType": true,
"left": { "type": "payment", "detectedType": "mt103", "documents": 1, "warnings": 0 },
"right": { "type": "payment", "detectedType": "pacs.008", "detectedVersion": "pacs.008.001.08",
"documents": 1, "warnings": 0 },
"added": [
{ "path": "endToEndId", "value": "NOTPROVIDED" },
{ "path": "instructedAmount", "value": { "amount": "1000.5", "currency": "EUR" } }
],
"removed": [],
"changed": [],
"meta": { "durationMs": 2 }
}
| Field | Meaning |
|---|---|
sameType | true when both sides are the same kind of canonical document (payment, statement, and so on) |
left, right | What each side was read as: canonical document type, detectedType, detectedVersion for ISO 20022 input, the number of canonical documents, and the count of normalization warnings |
added | Paths only the right side has, each with its value |
removed | Paths only the left side has, each with its value |
changed | Paths both sides have with different values, each with left and right |
Each list is sorted by path. When the two messages match, all three lists are empty. Paths use the canonical model's field names, with array positions in brackets, for example entries[0].amount.
How values are compared
The comparison follows the meaning of each field, not its spelling:
- Money compares as an exact decimal amount together with its currency.
100.5 EURequals100.50 EUR;100.50 EURdoes not equal100.50 USD. Amounts are never converted to floating point. A money difference is reported once, at the amount's path, with both amount-and-currency objects. - Exchange rates compare as exact decimals.
- Timestamps compare as instants, so the same moment written with different UTC offsets is equal.
- UETRs (the unique end-to-end transaction reference used to track a payment) compare without regard to letter case.
- Everything else compares exactly.
- Arrays compare position by position: the first entry with the first entry, and so on. An entry present on one side only shows up in
addedorremoved.
Changing the sample MT940 statement's entry from C250,00 to C251,00 gives one change:
"changed": [
{ "path": "entries[0].amount",
"left": { "amount": "250.00", "currency": "EUR" },
"right": { "amount": "251.00", "currency": "EUR" } }
]
Expected differences between MT and ISO 20022
Some fields exist in one syntax and not the other. When the ISO 20022 side requires such a field, the conversion has to supply a value; when the MT side has no slot for it, the value cannot travel. An MT message and the ISO 20022 message carrying the same business content can therefore differ on these paths, and only these:
| Document | Paths that can differ | Why |
|---|---|---|
payment (MT103 or MT202 against pacs.008 or pacs.009) | endToEndId | MT103 has no end-to-end id; pacs.008 requires one |
instructedAmount | pacs.008 fills it from the settled amount when the MT has no :33B: | |
instrId | The MT reference feeds more than one ISO 20022 id | |
chargeBearer | The ISO 20022 code SLEV has no MT equivalent | |
statement (MT940 or MT950 against camt.053) | account.currency | MT :25: has no currency |
createdAt | The ISO 20022 creation time has no MT field | |
initiation (MT101 against pain.001) | createdAt | pain.001 requires a creation time; MT101 has none |
batches[].paymentInfoId | pain.001 requires a payment information id | |
initiatingParty | pain.001 requires one; it is filled from the debtor |
A difference outside this list, for example a changed amount, date, account or party name, is a real difference worth investigating.
Errors
| Status | When |
|---|---|
400 invalid-input | The body is not JSON, a side is missing its message, or a message cannot be read |
413 message-too-large | The body is over twice the single-message cap |
422 unsupported-format | A from value is not a known format |
The detail of any error about one side starts with left: or right:.
Privacy
A diff has to quote values to be useful, so added, removed and changed contain amounts, names and identifiers from your messages. They exist only in the HTTP response. transmute does not store or log them, and the usage record for the call holds the format ids and a count only.
Access and billing
Requires the normalize scope, since a diff is two normalizations and a compare. A diff is metered like a validation: one usage event per call, whatever the size of the two messages. The usual billing rules and quota headers apply.