Canonical JSON input
Send transmute's canonical JSON back to /v1/convert to build or edit a message as JSON and render it as MT, ISO 20022 or a bank file. Covers the accepted shapes, the published JSON Schema, targets and errors.
JSON in, any format out
/v1/normalize turns any supported message into one canonical JSON shape. The same JSON is also an input format, with the format id canonical. You can:
- normalize a message, change something in the JSON, and convert it to the format you need, or
- build a statement or payment as JSON in your own code and render it as MT, ISO 20022 (MX) or a file format, without writing either syntax yourself.
curl -sS -X POST "https://api.transmute.403fin.io/v1/convert?from=canonical&to=mt940" \
-H "Authorization: Bearer $TRANSMUTE_KEY" \
-H "Content-Type: text/plain" \
--data-binary @statement.json
{
"result": ":20:STMT0001\r
:25:DE89370400440532013000\r
:28C:00042/001\r
:60F:C260601EUR1000,00\r
:61:2606020602C250,00NTRFNONREF//BANKREF1\r
:86:INVOICE 4711\r
:62F:C260602EUR1250,00",
"warnings": [],
"meta": { "detectedType": "canonical", "detectedVersion": null, "targetType": "mt940", "durationMs": 1 }
}
from=canonical is optional. Auto-detection recognizes canonical JSON when the body starts with { followed by a quoted member name (or }). MT envelopes ({1:, {2:, ...) are still detected as MT.
Sending it
| Request form | How the JSON travels |
|---|---|
| Raw body | Content-Type: text/plain, the JSON document as the body, from/to as query parameters |
| JSON envelope | Content-Type: application/json, the document JSON-encoded as a string in message |
| File upload | The document as the file part, as for any other format |
application/json always means the request envelope ({"from", "to", "message", "options"}), so raw canonical JSON goes as text/plain. To build an envelope from a file with jq:
jq -n --rawfile doc statement.json '{from: "canonical", to: "camt.053", message: $doc}' |
curl -sS -X POST https://api.transmute.403fin.io/v1/convert \
-H "Authorization: Bearer $TRANSMUTE_KEY" \
-H "Content-Type: application/json" \
--data-binary @-
Two accepted shapes
The full normalize response, exactly as /v1/normalize returned it. warnings and meta describe the run that produced the document and are ignored. If result.envelope is present (from normalize with envelope=include), it feeds the envelope=apphdr / envelope=fin convert option.
{
"result": {
"type": "statement",
"statement": { "account": { "iban": "DE89370400440532013000" }, "...": "..." }
},
"warnings": [],
"meta": { "detectedType": "mt940", "detectedVersion": "", "durationMs": 2 }
}
The bare document, which is the object under result.<type>. This is the shape to build yourself:
{
"messageRef": "STMT0001",
"account": { "iban": "DE89370400440532013000" },
"statementNumber": "00042",
"legalSequence": "001",
"openingBalance": {
"type": "OPBD",
"amount": { "amount": "1000.00", "currency": "EUR" },
"creditDebit": "CRDT",
"date": "2026-06-01"
},
"closingBalance": {
"type": "CLBD",
"amount": { "amount": "1250.00", "currency": "EUR" },
"creditDebit": "CRDT",
"date": "2026-06-02"
},
"entries": [
{
"amount": { "amount": "250.00", "currency": "EUR" },
"creditDebit": "CRDT",
"status": "BOOK",
"bookingDate": "2026-06-02",
"valueDate": "2026-06-02",
"bankTxCode": { "domain": "PMNT", "family": "RCDT", "subFamily": "ESCT", "proprietary": "NTRF" },
"references": { "acctSvcrRef": "BANKREF1" },
"remittance": { "unstructured": ["INVOICE 4711"] }
}
]
}
This is the quickstart MT940 after normalization. Rendering it as mt940 gives back the original text block.
Money, dates and the schema
- Amounts are decimal strings, never JSON numbers:
{"amount": "1250.00", "currency": "EUR"}. The amount is a plain decimal (optional leading-, digits, optional.and digits). Exponents, thousands separators and numbers are rejected. Parse amounts with a decimal type in your code too. - Currencies are three upper-case letters.
- Dates are
YYYY-MM-DD; timestamps are RFC 3339.
The full model is published as a JSON Schema (draft 2020-12) at the root of the API host, next to the OpenAPI document:
curl -sS https://api.transmute.403fin.io/canonical.schema.json
It needs no API key, is never metered and is served as application/schema+json. Its $id is that URL. The top level accepts either the full normalize envelope or one bare document (statement, payment, initiation, status report, return, investigation, charges request or statement request). Every object is closed (additionalProperties: false), so a typo fails schema validation instead of being ignored. The schema is generated from the same model the service runs on, and every normalize output validates against it.
You can also check a document with POST /v1/validate and from=canonical. Problems come back as findings with the codes canonical.syntax (not one well-formed JSON object) or canonical.schema (doesn't fit the model).
Which targets a document can render to
The target picks what kind of document is expected:
Document (result.type) | Targets |
|---|---|
statement | mt940, mt950, mt942, mt900, mt910, camt.053, camt.052, camt.054, bai2, csv-xero, csv-generic, csv-quickbooks, csv-sage, ofx, qfx, qbo, accounting-transaction |
payment (customer transfer) | mt103, pacs.008, accounting-transaction |
initiation (credit transfer) | mt101, pain.001 |
initiation (direct debit) | mt104, pain.008 |
status | pacs.002, pain.002 |
request (statement request) | mt920, camt.060 |
That's 27 canonical pairs in total. Other document types (returns, investigations, charges, financial-institution transfers) can be normalized but not rendered from JSON. GET /v1/formats lists the live convertsTo for canonical.
Errors
| Problem | Response |
|---|---|
| Not valid JSON, trailing data after the object, duplicate keys, nesting deeper than 64 levels | invalid-input 400 |
| A member the model doesn't know, a wrong JSON type, an amount that isn't a plain decimal string | invalid-input 400 |
A well-formed document of the wrong kind for the target: a statement sent to mt103, a financial-institution or cover payment sent to pacs.008, a direct-debit initiation sent to pain.001, a credit-transfer initiation sent to pain.008 | unsupported-conversion 422 |
from=canonical on /v1/normalize (the input is already normalized) | unsupported-conversion 422 |
Error details name member paths and expected shapes, never values from your document. For example: Invalid canonical JSON: unknown member "$.type" (not part of the canonical model; see /canonical.schema.json) or Canonical document is a statement; target mt103 needs a customer payment (kind customer or absent).
Round trips
The canonical shape is stable public API: members are only ever added, never renamed or retyped. Converting the normalize output of a message gives the same output as converting the original message directly. Only values transmute generates at conversion time, such as a defaulted creation timestamp, can differ. This is checked continuously over the conversion test corpus and generated samples. In practice you can normalize, inspect or adjust, and then convert without losing anything the canonical model carries.
Anything the source carried that has no canonical slot was already reported by a warning when you normalized. Keep those warnings if you need a complete audit trail, because they aren't repeated when the JSON is converted.