Files & FormatsCanonical JSON Input

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 formHow the JSON travels
Raw bodyContent-Type: text/plain, the JSON document as the body, from/to as query parameters
JSON envelopeContent-Type: application/json, the document JSON-encoded as a string in message
File uploadThe 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
statementmt940, 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
statuspacs.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

ProblemResponse
Not valid JSON, trailing data after the object, duplicate keys, nesting deeper than 64 levelsinvalid-input 400
A member the model doesn't know, a wrong JSON type, an amount that isn't a plain decimal stringinvalid-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.008unsupported-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.