Files & FormatsBAI2 & Accounting CSV

BAI2 and accounting CSV

Convert BAI2 cash-management files to and from MT and ISO 20022 statements, and render statements as CSV for Xero, QuickBooks, Sage or your own column layout, plus OFX, QFX and QBO.

BAI2 in both directions

BAI2 is the comma-separated balance-reporting file many US banks deliver. In transmute it's the statement format bai2 (kind file), and it works in both directions:

  • a BAI2 file converts to every MT and ISO 20022 statement format (mt940, mt950, mt942, mt900, mt910, camt.053, camt.052, camt.054), to the accounting files below, and to accounting-transaction;
  • every statement source, including canonical JSON, converts to bai2.

A body whose first record starts with 01, is detected as BAI2, so from is optional. BAI2 is plain text: send it as text/plain or in the JSON envelope's message.

curl -sS -X POST "https://api.transmute.403fin.io/v1/convert?to=camt.053" \
  -H "Authorization: Bearer $TRANSMUTE_KEY" \
  -H "Content-Type: text/plain" \
  --data-binary @- <<'EOF'
01,AAAADEFF,BBBBDEFF,260602,0900,FILE01,,,2/
02,BBBBDEFF,AAAADEFF,1,260602,,USD,2/
03,0123456789,USD,010,100000,,,015,125000,,/
16,195,25000,Z,BANKREF1,INV4711,INCOMING WIRE/
88,INVOICE 4711/
49,250000,4/
98,250000,1,6/
99,250000,1,8/
EOF

The result is one camt.053 statement for account 0123456789 in USD: opening balance 1000.00, closing balance 1250.00, and one 250.00 credit booked as an incoming wire (PMNT/RCDT/DMCT, proprietary code 195) with the remittance lines INCOMING WIRE and INVOICE 4711.

How a BAI2 file is read

RecordWhat it carriesWhere it goes
01 file headersender, receiver, creation date and time, file idfile id becomes the statement reference; creation time becomes createdAt; sender and receiver ids have no statement slot (DROP_FIELD info)
02 group headeroriginator, as-of date, currencya BIC-shaped originator becomes the account servicer; the as-of date dates the balances and entries
03 accountaccount number, currency, balance and summary codesone statement per 03 record; balances by the code table below
16 transactiontype code, amount, funds type, bank reference, customer reference, textone booked entry each: bank reference becomes acctSvcrRef, customer reference becomes endToEndId, text becomes remittance lines
88 continuationmore fields or text for the record before itfolded into that record; after a 16, each 88 is one more remittance line
49, 98, 99 trailerscontrol totals and record countschecked, never copied

Things to know:

  • Implied decimals. BAI2 amounts have no decimal point. The number of decimals is the currency's minor units: 25000 is 250.00 in USD, 25000 in JPY, and 25.000 in KWD.
  • Currency comes from the 03 record, else the 02 group, else USD with a MISSING_REQUIRED_DEFAULTED warning.
  • Direction comes from the type code. transmute maps about 70 common detail codes to an ISO 20022 bank transaction code and an MT :61: type (for example 195 incoming wire, 475 cheque paid, 661 account analysis fee). An unlisted code takes its direction from its range: 100-399 credit, 400-699 debit, with CODE_UNMAPPED_PROPRIETARY (info). A code outside both ranges carries no direction, so that entry is dropped with a lossy DROP_FIELD. The original code always travels on as the proprietary bank transaction code.
  • Value dates. Funds type V supplies the entry's value date; otherwise the as-of date is used. Other availability details have no canonical slot (DROP_FIELD info).
  • Trailers are verified. Each trailer's control total and record count are compared with what was read. A mismatch or missing trailer is AMOUNT_MISMATCH (warning). Totals are never copied into the statement.

Balance and summary codes on the 03 record:

CodeBecomes
010opening balance (OPBD)
015closing balance (CLBD)
030current intraday balance, as the closing balance typed ITBD
045closing available balance (CLAV)
100 / 400the stated total credits / debits and their counts, copied as given, never summed

Any other summary code is dropped with a lossy DROP_FIELD. Limits: 1 MiB per file, 4096 bytes per physical record, 100,000 records.

Writing BAI2

Converting a statement to bai2 writes one file with one 02 group and one 03 account per statement. Here's the quickstart MT940 as BAI2:

01,NOTPROVIDED,NOTPROVIDED,260602,0900,STMT0001,,,2/
02,,NOTPROVIDED,1,260602,,EUR,/
03,DE89370400440532013000,EUR,010,100000,,,015,125000,,/
16,108,25000,,BANKREF1,,INVOICE 4711/
49,250000,3/
98,250000,1,5/
99,250000,1,7/
  • Balances go to 010/015 (or 030 for an interim closing balance) and the first available balance to 045. A stated transaction summary is copied to 100/400.
  • Each entry's type code is derived from its bank transaction code. When nothing matches, the default is 399 (credit) or 699 (debit) with CODE_UNMAPPED_PROPRIETARY.
  • Sender, receiver and originator ids the source doesn't have are written as NOTPROVIDED with MISSING_REQUIRED_DEFAULTED.
  • The trailers are computed from the records just written.
  • BAI2 has no field for the account owner, counterparty, reversals, statement and sequence numbers, or a balance date that differs from the as-of date. Each of these is reported with a lossy DROP_FIELD.
  • A , or / inside a text field would break the record, so it's replaced with . and reported as CHARSET_DROP.
  • An amount with more decimals than the currency allows can't be written as implied decimals. The conversion fails instead of rounding.

Accounting CSV targets

Every statement source (MT, ISO 20022, BAI2 or canonical JSON) renders to four CSV formats for accounting import. Each statement becomes one CSV file (RFC 4180 quoting, CRLF line ends, UTF-8 without BOM). Amounts use a dot decimal with no thousands separator.

TargetLayoutDefault dateFormat
csv-xeroDate, Description, Debit, Creditdmy
csv-quickbookscsvTemplate quickbooks-3col (default): Date, Description, Amount with money out negative; or quickbooks-4col: Date, Description, Credit, Debitmdy
csv-sageDate, Reference, Description, Amount (signed)dmy
csv-genericyour own columns (csvColumns) or a built-in template (csvTemplate)ymd

dateFormat (dmy gives 02/06/2026, mdy gives 06/02/2026, ymd gives 2026-06-02) overrides the default for any CSV target.

curl -sS -X POST "https://api.transmute.403fin.io/v1/convert?to=csv-quickbooks&csvTemplate=quickbooks-4col" \
  -H "Authorization: Bearer $TRANSMUTE_KEY" \
  -H "Content-Type: text/plain" \
  --data-binary @statement.mt940
Date,Description,Credit,Debit
06/02/2026,INVOICE 4711,250.00,

csv-generic: choose your columns

options.csvColumns is a list of up to 64 columns. Each one is a field id, or Header=field to set your own header:

{
  "to": "csv-generic",
  "message": "{1:F01AAAADEFFXXXX0000000000}{2:I940BBBBDEFFXXXXN}{4:
:20:STMT0001
...",
  "options": { "csvColumns": ["Booked=date.booking", "amount.signed", "currency", "description", "Ref=reference.bank"] }
}
Booked,amount.signed,currency,description,Ref
2026-06-02,250.00,EUR,INVOICE 4711,BANKREF1

On raw requests, repeat the query parameter once per column: ?csvColumns=Booked%3Ddate.booking&csvColumns=amount.signed.

FieldCell
datebooking date, else value date
date.booking, date.valuethat date, empty when absent
amount.signedamount, - for debits
amount.debit, amount.creditabsolute amount on debit or credit rows, empty otherwise
currencyISO 4217 code
descriptioncounterparty name, remittance lines, structured references and extra information joined (500-character cap, TRUNC_REMITTANCE)
reference.endToEnd, reference.bankend-to-end reference, bank's reference
counterparty.name, counterparty.iban, counterparty.bicthe other party
btc.proprietaryproprietary bank transaction code
btc.domaindomain/family/subFamily
balance.opening, balance.closingthe statement's opening or closing balance, repeated on every row

There is no running-balance column: transmute never calculates money.

Without csvColumns, csv-generic uses csvTemplate: standard (default: Date, Value Date, Amount, Currency, Description, Reference, Bank Reference, Counterparty, Counterparty IBAN, Code) or any of quickbooks-3col, quickbooks-4col, sage.

The following are invalid-input 400: an unknown field or template, csvColumns and csvTemplate together, a template that doesn't belong to the target (csv-quickbooks takes only the two QuickBooks layouts, csv-sage only sage), or csvColumns on csv-quickbooks or csv-sage. Targets that aren't CSV ignore both options.

A text cell that starts with =, +, -, @ or a control character is prefixed with a quote so a spreadsheet can't run it as a formula. This applies to your own headers too and is reported as CSV_FORMULA_NEUTRALIZED. Amount cells are numbers and aren't changed.

The statement's transaction summary has no CSV column and is reported as DROP_FIELD.

OFX, QFX and QBO

Statements also render to ofx (OFX 1.02 SGML by default, or 2.2 XML with ofxVersion: "220"), qfx (Quicken) and qbo (QuickBooks Web Connect). QFX and QBO are always 1.02 SGML. intuBid sets the Intuit institution id in QFX and QBO (default placeholder 3000).

Notes

  • All file targets are output-only. Nothing converts from csv-*, ofx, qfx or qbo. To read payment spreadsheets, see Payment spreadsheets.
  • csvColumns and csvTemplate apply to synchronous /v1/convert. Batch items don't carry them. dateFormat, ofxVersion and intuBid work on batch items too.
  • Every warning code above is in the warning registry. The live list of pairs is GET /v1/formats.