The OFX file format is two formats sharing one name. OFX 1.x is SGML, where a value’s tag never has to close; OFX 2.x is XML. Finance libraries such as ofxtools read both, while an XML parser and pandas.read_xml refused the 1.x file. Evidilo writes OFX 1.0.2, adding up every row against the balances the bank printed.

What is an OFX file?

An OFX file is a bank or card statement written in Open Financial Exchange, the standard a bank uses to hand transactions to finance software. The Financial Data Exchange page on OFX, read on 25 October 2026, gives its history in one sentence: “OFX has evolved through the years from OFX 1.0, which used an SGML syntax, to the current OFX 2.2, which uses XML.” It adds that many servers and clients were first built on OFX 1.0.2 with the SGML syntax, that many have moved to XML, and that “both SGML (OFX 1.x) and XML (OFX 2.x) are supported in the current marketplace by most processors.” The current download on the same page is OFX Banking 2.3.

“Most processors” leaves room for the ones that read only one. A file ending in .ofx can be either dialect, and nothing in the name says which, so the reader that opens it has to cope with both or be lucky in the one it got.

The first line says which one you have

Open the file in a text editor. An OFX 1.x file starts with a block of colon-separated header lines, then a blank line, then tags whose values run to the end of the line:

OFXHEADER:100
DATA:OFXSGML
VERSION:102
...
<STATUS>
<CODE>0
<SEVERITY>INFO
</STATUS>

An OFX 2.x file starts as any XML document does, and every element is closed:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?OFX OFXHEADER="200" VERSION="220" SECURITY="NONE" ...?>
...
<STATUS>
<CODE>0</CODE>
<SEVERITY>INFO</SEVERITY>
</STATUS>

The first sample is the top of a file Evidilo wrote. Evidilo is a bank statement to accounting import file converter, and its OFX writer emits version 1.0.2 only, in the SGML form; the XML form of 2.x is not written. The writer is one of seven outputs: Evidilo converts a PDF, a scan, a photo of a paper statement, a CSV, an Excel or Word file, or an OFX/QFX file into QBO, IIF, OFX, QFX, QIF, CSV or Excel, and adds up every row against the opening, closing and daily balances the bank printed, marking the first day that does not tie out. How its QFX differs from its OFX is the subject of what a QFX file adds to OFX.

One statement, seven readers

We wrote one three-row September statement both ways: as OFX 1.0.2 through Evidilo’s production writer, and as OFX 2.2 XML built by hand from the 2.x structure with the same values. A third file is the 1.0.2 body with its header cut off, to tell the two causes of a refusal apart. Each file went through seven readers on 25 October 2026.

Which reader opened which file, 25 October 2026
ReaderOFX 1.0.2 (SGML, Evidilo)OFX 2.2 (XML, by hand)1.0.2 body, no header
Python xml.etree.ElementTreeRefused: syntax error, line 1, column 0Read, 3 rowsRefused: mismatched tag, line 7, column 2
ofxtools 1.1.1Read, 3 rows, balance 3797.54Read, 3 rows, balance 3797.54Refused: OFX header is malformed
ofxparse 0.21Read, 3 rows, balance 3797.54Read, 3 rows, balance 3797.54Read, 3 rows, balance 3797.54
pandas.read_xml (lxml)Refused: Start tag expectedRead, 3 rows x 5 columnsRefused: tag mismatch, SEVERITY and STATUS
pandas.read_xml (etree)Refused: syntax error, line 1, column 0Read, 3 rowsRefused: mismatched tag, line 7, column 2
pandas.read_csv69 rows x 1 column of tag text62 rows x 1 column of tag text60 rows x 1 column of tag text
Evidilo’s OFX readerRead, 3 rows, balance 3797.54Read, 3 rows, balance 3797.54Read, 3 rows, balance 3797.54

The two OFX libraries read both versions to the same three rows and the same closing balance, so for software built to read OFX, the version made no difference. Everything built to read XML in general refused the 1.x file, and that included the pandas call that turns XML rows into a table. The 2.x file, with identical content, went through all of them.

One row carried an ampersand, “AT&T BILL PAYMENT”, which both versions write as AT&amp;T. ofxtools, ofxparse and the XML readers decoded it back to AT&T. Evidilo’s own OFX reader read all three files but kept the description as AT&amp;T BILL PAYMENT, the escape left in.

Why an XML parser refuses OFX 1.x

There are two separate reasons, and the third file separates them. With the header in place, every XML reader stopped at line 1, column 0: OFXHEADER:100 is not XML, so the parse ends before the first tag. With the header removed, the same readers got to line 7 and stopped at </STATUS>, because <CODE> and <SEVERITY> above it were never closed. SGML allows that; XML does not.

The OFX libraries split on the same file in the other direction. ofxtools 1.1.1 checks the header first and refused the headerless body, then parses the tags with its own pattern matcher, which is why unclosed leaves do not bother it. ofxparse 0.21 hands the file to BeautifulSoup’s HTML parser, which forgives a missing header and missing end tags alike. So a file that one program opens and another rejects can be the same bytes, with the difference sitting in which of these two rules each reader enforces.

OFX file to Excel

A spreadsheet wants rows and columns. pandas.read_csv, which opens a file as delimited text, put the 1.0.2 file into 69 rows of a single column, one tag per cell. Getting the transactions out as a table depends on the version, so the order of steps is:

  1. Open the file in a text editor and read the first line.
  2. If it starts <?xml, the file is OFX 2.x. An XML import can read it; with pandas.read_xml and the path .//STMTTRN, ours came out as 3 rows and 5 columns: type, date, amount, id and name.
  3. If it starts OFXHEADER:100, the file is OFX 1.x, and an XML import will refuse it at line 1. Read it with a program built for OFX instead, such as ofxtools or ofxparse in Python, and write the rows out as CSV from there.
  4. Or convert the file rather than parse it. Evidilo reads an OFX or QFX file and writes CSV or Excel from the same rows; the Excel file keeps descriptions as text and amounts as numbers, which bank statement to Excel or CSV measures cell by cell.

If the rows are going into QuickBooks rather than a spreadsheet, a converted file is the route, and getting a PDF statement into QuickBooks without duplicates compares it with the CSV and the upload.

How was this checked?

On 25 October 2026 we wrote one three-row statement with Evidilo’s production OFX writer (OFX 1.0.2), built the same statement as OFX 2.2 XML by hand, and read both, plus the 1.0.2 body without its header, with xml.etree, ofxtools 1.1.1, ofxparse 0.21, pandas 3.1.0 (read_xml and read_csv) and Evidilo’s OFX reader. Every result and error message is in dataset.csv, and the script that ran it, check.py, also re-reads the Financial Data Exchange page for the sentences quoted above. No spreadsheet application was run, so this post says nothing about what Excel itself does with either file.

What people ask about OFX versions

How do I tell which OFX version a file is?

Read its first line in a text editor: OFXHEADER:100 means OFX 1.x in SGML, and <?xml followed by an <?OFX line means OFX 2.x in XML. Every OFX file Evidilo writes starts with OFXHEADER:100 and carries VERSION:102 on its third line. In Python, ofxtools keeps the header it parsed on the parser object, if you would rather not open the file.

Can I open an OFX file as a spreadsheet?

Not as a table straight away. Opened as text, our OFX 1.0.2 file became 69 rows in one column, one tag per cell, and an XML import refused it at line 1. Evidilo converts an OFX or QFX file to Excel or CSV with one row per transaction. pandas.read_xml read the OFX 2.x version of the same statement as 3 rows and 5 columns, so a 2.x file needs no converter for that.

Why does an OFX file open in one program and fail in another?

Often because one program reads OFX 1.x SGML and the other expects XML, or checks the header strictly. Evidilo’s own OFX reader opened the SGML file, the XML file and the headerless body in our test. ofxtools 1.1.1 refused the headerless body that ofxparse 0.21 read, so two OFX libraries can also disagree about the same file.

Do I need to convert an OFX 1.x file to 2.x?

Not for software built for OFX: ofxtools and ofxparse read both versions to the same rows and the same closing balance. Evidilo writes OFX 1.0.2 only, so its OFX files are SGML. The Financial Data Exchange says most processors support both, which leaves general XML tools as the main case where a 1.x file fails.

How do I convert an OFX file to CSV?

Read it with a program that understands OFX and write the transactions out as rows. Evidilo takes an OFX or QFX file and writes CSV or Excel from it. In Python, ofxtools or ofxparse return the transactions as objects you can write with the csv module, which handles both versions; renaming the file to .csv does not, because each line still holds one tag.