Bai2.Net 1.1.0

dotnet add package Bai2.Net --version 1.1.0
                    
NuGet\Install-Package Bai2.Net -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Bai2.Net" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Bai2.Net" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Bai2.Net" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Bai2.Net --version 1.1.0
                    
#r "nuget: Bai2.Net, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Bai2.Net@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Bai2.Net&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Bai2.Net&version=1.1.0
                    
Install as a Cake Tool

Bai2.Net

Zero-dependency BAI2 (Bank Administration Institute, version 2) statement parser for .NET. Reads cash-management and lockbox files into a typed model, and reconciles every control total for you.

NuGet   MIT   Zero dependencies   Native AOT clean

Why

BAI2 is still the dominant machine-readable format US banks use to report balances and transactions. If you move money in the United States, sooner or later a bank hands you a BAI2 file. The options today are thin: pull in a stale hobbyist parser, or hand-roll the record hierarchy and get the control totals subtly wrong. Bai2.Net is the small, correct primitive, and a sibling to Mt940.Net and Camt.Net so the whole bank-statement cluster parses the same way.

The headline feature is reconciliation. A BAI2 file states a control total at the account, group, and file level, and a record count at each. Bai2.Net recomputes all of them from the records and tells you when they disagree, so a truncated or tampered file fails loudly instead of quietly under-reporting cash.

Install

dotnet add package Bai2.Net

Use

using Bai2;

Bai2File file = Bai2Statement.Parse(content);

FileHeader header = file.Header;
Account account = file.Groups[0].Accounts[0];

foreach (Transaction t in account.Transactions)
{
    // Money is exposed twice: exact minor units, and a decimal convenience.
    Console.WriteLine($"{t.TypeCode} {t.AmountMinor} ({t.Amount:0.00}) {t.FundsType}");
}

AmountMinor is a long in the currency's smallest unit (for example cents). Amount is the same value as a decimal in major units. Money is never a double.

Currency minor-units exponent

Amount is derived from AmountMinor by dividing by ten to the currency's minor-units exponent. The default exponent is 2 (divide by 100), which matches USD, EUR, GBP and most currencies. Pass minorUnitsExponent to override it:

// JPY has no minor unit (exponent 0): Amount equals AmountMinor.
Bai2File jpy = Bai2Statement.Parse(content, minorUnitsExponent: 0);

// A three-decimal currency (for example BHD, KWD).
Bai2File bhd = Bai2Statement.Parse(content, minorUnitsExponent: 3);

AmountMinor is always exact and never affected by the exponent, so control-total reconciliation is unchanged. The override is available on Parse(string, ...), Parse(Stream, ...) and TryParse.

Reconcile the control totals

Bai2File file = Bai2Statement.Parse(content);
file.Validate(); // throws Bai2ValidationException if any total or count does not add up

// Or reconcile as part of parsing:
Bai2File reconciled = Bai2Statement.Parse(content, validate: true);

Reconciliation uses checked arithmetic, so amounts that would overflow long when summed are reported as an error rather than silently wrapping.

Validate() checks that:

  • each account control total (record 49) equals the sum of that account's summary and transaction amounts,
  • each group control total (record 98) equals the sum of its account totals,
  • the file control total (record 99) equals the sum of its group totals, and
  • every declared record count and account count matches what was parsed.

Empty (omitted) counts are skipped rather than treated as zero.

Try-parse and errors

if (Bai2Statement.TryParse(content, out Bai2File? parsed))
{
    // parsed is non-null here
    parsed.Validate();
}

Parse throws Bai2ParseException on malformed structure; its message names the 1-based physical record that failed. TryParse returns false instead. Bai2Statement.Parse(Stream) reads UTF-8.

The model

Bai2File
  FileHeader   Header
  Group[]      Groups
    GroupHeader      Header
    Account[]        Accounts
      AccountSummary[]  Summaries      (balances and status items from record 03)
      Transaction[]     Transactions   (record 16, with 88 continuations folded into Text)
      AccountTrailer    Trailer
    GroupTrailer     Trailer
  FileTrailer  Trailer

The record hierarchy is 01 File Header, 02 Group Header, 03 Account Identifier, 16 Transaction Detail (with 88 continuations), 49 Account Trailer, 98 Group Trailer, 99 File Trailer. Continuation (88) records are folded into the record they extend before you see the model: for a transaction they append to Text; for an account identifier they extend the summary list.

Dates

Dates are DateOnly, parsed from the BAI2 YYMMDD fields. The two-digit year uses a fixed pivot: 00-69 map to the 2000s, 70-99 map to the 1900s. Times stay as the raw HHMM string, because BAI2 does not carry a time zone.

Limitations

  • Targets BAI2 (version 2). It parses the common record set above and the common type and funds-type codes; it does not interpret every bank-specific type code.
  • Amount conversion to the decimal convenience assumes a 2-digit currency exponent (divide minor units by 100) by default. For a currency with a different exponent, pass minorUnitsExponent to Parse/TryParse (for example 0 for JPY, 3 for a three-decimal currency). AmountMinor is always exact regardless of the exponent.
  • Funds types that carry sub-fields (V value-dated, D distribution list, S distributed) are recorded as their code; the trailing sub-fields are not expanded.
  • A trailer (49, 98, 99) with an empty control-total field is rejected with Bai2ParseException rather than being read as zero, since a blank control total is a truncated file, not a reconciled one. Trailer record and account counts remain optional.
  • Continuation (88) records are accepted only after an account identifier (03) or a transaction detail (16); a stray 88 after any other record is a Bai2ParseException.

License

MIT, Israel Iyonsi.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 121 8/21/2026
1.0.0 98 8/21/2026