How to Understand and Generate an ACH File in Python

In my latest project at the company I work for, I got the opportunity to take a deep dive into the wonderful world of the electronic banking ecosystem. The company was looking to revamp their system for paying dividends to their shareholders. Before, they used a very old Cobol program to grab data and generate ACH files to facilitate the transfer of funds to shareholders. But now, it is my job to reimplement this system with a sleek new python program, and to make the process smoother and more straightforward in the process. First things first though, what on earth is an ACH file, and how do they work?

What is an ACH File?

“ACH” stands for “Automated Clearing House”. The Automated Clearing House is a centralized US financial network run by Nacha, a nonprofit organization owned by a large group of banks and other financial institutions. By submitting ACH files, which are small files containing transaction data, to Nacha (often through their bank), organizations can request the transfer of funds across the ACH network from account to account and institution to institution within the United States.

Format of an ACH File

As I previously mentioned, requests to process payments to the ACH network are created through ACH files. ACH files have a simple format that is easy to understand once you know what to look for.


Here, you can see a sample ACH file. Each file contains 4 main parts. First, there is the File Header. This section is a 94-character string that contains metadata about your organization and the financial institution you plan to submit the file to.

Then, there is a Batch Header. The batch header is a 94-character line that allows you to create “batches” of payments within your file. This provides logical and chronological grouping for payments.


Next comes a series of Batch Records. Each of these 94-character entries is grouped within the preceding batch denoted by the most recent batch header, and contains the details of an individual financial event that should be processed against a particular bank account. So, each payment record could be a direct deposit paycheck, a bill for an invoice, or dividend payment to a given shareholder. At this point, multiple sets of batch headers with their accompanying batch records can be included in the file


Then, at the end of a batch, a Batch Control Record appears. This record contains data used to summarize and validate the data within the preceding batch.

Finally, ACH files contain a File Control Record. This record contains data that summarizes the batches in the ACH file, and contains hashes used to validate the format and content of the file.

The padding is simple content with no purpose except to take up extra space at the bottom of the file.

Now that we have seen all the main parts of an ACH file, let’s take a look at a helpful too for creating ACH files with python.

Meet pyACH

pyACH is a handy python library that is used to generate ACH files in an simple, object-oriented way. It allows the programmer to create an instance of an ACHFile object, set it’s attributes, and then use methods like add_batch and add_entry to easily add records to the file. This object can then be written to a file, and it will create a valid ACH file that can be submitted to a financial institution. Install it with

pip install pyACH

Now, let’s take a closer look at the exact structure of each section of the ACH file to really get a grasp on the meaning of every character.

File Header

We begin with the ACH header. Let’s break it down one field at a time.

FieldSizeFormatField NamepyACH AttributeComment
11“1”Record-Type CodeAutoCode identifying the File Header Record.
22“01”Priority CodeAutoOnly “01” is valid.
310Num(10)Immediate DestinationACHFile.destination_routing_numberNumber identifying the site where this file will be processed. Ask your financial institution about this!
Immediate OriginACHFile.origin_id10-digit number assigned to you by your financial institution. Ask your financial institution about this!
56YYMMDDFile Creation DateAuto
Date of file creation.
64HHMMFile Creation TimeAuto
Time of file creation.
71A-Z0-9File ID ModifierFileheader._file_id_modifierDistinguishes between multiple files sent in the same day, first labelled “A”, second “B”, etc.
83“094”Record SizeAutoBytes per record (always “094”).
92“10”Blocking FactorAutoRecords per block.
101“1”Format CodeAutoOnly “1” is valid.
1123A-z0-9DestinationACHFile.destination_nameName of file processing site, ex. “Bank of America DAL”.
1223A-z0-9Origin/Company NameACHFile.origin_nameYour organization’s name, ex. “JG Web Development”.
138A-z0-9Reference CodeACHFile.reference_codeThis field can be used for your own purposes to describe the input file.
Check your understanding! Can you identify the date and time the file in the image above was created?

The ACH File Header designates the formatting of the file, as well as identifying the your organization and your bank as the immediate origin and immediate destination of the payments contained within. Now, let’s look at the next section, the batch header.

Batch Header

FieldSizeFormatField NamepyACH AttributeComment
11“5”Record Type CodeAutoCode identifying the Batch Header Record.
23“200” or “220” or “225”Service Class CodeACHFile.service_class_code
Identifies the type of entries in the batch, “220” = credits only, “225” = debits only, “200” = both
316A-z0-9Company NameACHFile.batch_name
The name of your organization.
Company Discretionary DataAuto
Space for your organization’s internal use.
510A-z0-9Company IdentificationACHFile.company_identification_number
10-digit number assigned to you by your financial institution. Ask your financial institution about this!
63“ECC”, “PPD”, etc.Standard Entry Class CodeACHFile.entry_class_code
A Mnemonic, designed by Nacha, which identifies information about the type of payment.
710A-z0-9Company Entry DescriptionACHFile.entry_description
Use this value to provide a payment description to be displayed to the receiver. Shown in the receiver’s account statement.
86A-z0-9Company Descriptive DateAuto
Descriptive date you would like displayed to the receiver.
96A-z0-9Effective Entry DateAuto
Date (up to 30 days in the future) funds should post to the receiver’s account.
103Leave blankSettlement Date (Julian)AutoPopulated by ACH provider.
111“1”Originator Status CodeAutoIdentifies originator as a non-Federal Government entity.
128Num(8)Originating DFI IdentificationBatchHeader._originator_dfi_identificationNumber identifying the site where this file will be processed. Ask your financial institution about this!
137Num(7)Batch NumberAutoNumber identifying the batch, these increase throughout the file.
Check your understanding! Does this header contain any discretionary data?

After the Batch Header record, a series of one or more batch records will appear. Let’s take a look at one of those next.

Batch Records

FieldSizeFormatField NamepyACH AttributeComment
11“6”Record Type CodeAutoCode identifying the Batch Record.
22Num(2)Transaction CodeEntry._transaction_codeTwo-digit code identifying account credits, debits, or prenotes. See PyACH for all options.
38Num(8)RDFI Routing Transit NumberEntry._routing_numberTransit Routing Number of the receiver’s financial institution.
41Num(1)Check DigitAutoThe ninth character of the RDFI Routing Transit number, used to check for transpositions.
517A-z0-9DFI Account NumberBatch._account_numberReceiver’s account number at the RDFI (receiving institution).
610$$$$$$$$.¢¢AmountBatch._amountAmount of money processed in the transaction. Right-justified and left zero-filled.
715A-z0-9Check Serial NumberBatch._identification_numberCheck serial number of the receiver’s source document. Is often printed on the receiver’s statement.
822A-z0-9Individual NameBatch._receiver_nameMay contain the receiver’s name, reference number, or identification number.
92BlankDiscretionary DataBatch._discretionary_dataCan be used for internal purposes, some institutions require it be left blank.
101Num(1)Addenda Record IndicatorAuto“0” = no addenda, “1” = addenda present.
1111Num(11)Trace NumbersAutoA combination of the value of field 12 from the Batch Header, concatenated with an entry detail sequence number, which increments with each entry in the current batch.
Check your understanding! Does this record have any addenda?

Now, we know what an entry in a batch looks like. But how does a batch end? Read on to learn about the Batch Control Record.

Note: in some files, there will be “addenda records” following a batch entry. These are denoted by a 7 at the first character position. They are not covered at depth in this guide, however.

Batch Control

FieldSizeFormatField NamepyACH AttributeComment
11“8”Record Type CodeAutoCode identifying the Batch Control Record.
23“200”, “220”, or “225”Service Class CodeACHFile.service_class_code
Identifies the type of entries in the batch, must match the value used in the batch header.
36Num(6)Entry/Addenda CountAutoTotal number of records and addenda in the batch.
410Num(10)Entry HashAutoTotal of all routing numbers in the batch (not including the check digit).
512$$$$$$$$$$¢¢Total Debit Entry Dollar Amount (Batch)AutoDollar total of debit entries in the batch.
612$$$$$$$$$$¢¢Total Credit Entry Dollar Amount (Batch)AutoDollar total of credit entries in the batch.
710A-z0-9Company IdentificationACHFile.company_identification_number
10-digit number assigned to you by your financial institution. Ask your financial institution about this!
89A-z0-9Message Authentication CodeAuto (Do not modify!)Leave blank.
96A-z0-9ReservedAuto (Do not modify!)Leave blank.
108Num(8)Originating DFI NumberBatchControl._originator_dfi_identificationNumber identifying the site where this file will be processed. Ask your financial institution about this!
117Num(7)Batch NumberAutoBatch number for the batch this control record corresponds to. Must match field 12 of the batch header record.
Check your understanding! How much money was credited in the batch shown above?

And that concludes a batch of payments! As was already mentioned, an ACH file can contain multiple batches, denoted by batch headers and batch controls with their accompanying batch records.

Now that we know how to close out a batch, let’s take a look at how to close out the ACH file as a whole.

File Control

FieldSizeFormatField NamepyACH AttributeComment
11“9”Record Type CodeAutoCode identifying the File Control Record.
26Num(6)Batch CountAutoTotal number of batch header records in the file.
36Num(6)Block CountAutoTotal number of physical blocks in the file.
48Num(6)Entry/Addenda CountAutoTotal number of entry and addenda records in the file.
510Num(6)Entry HashAutoTotal of all routing numbers in the file (not including the check digit).
612$$$$$$$$$$¢¢Total Debit Entry Dollar Amount (File)AutoDollar total of debit entries in the file.
712$$$$$$$$$$¢¢Total Credit Entry Dollar Amount (File)AutoDollar total of credit entries in the file.
839Leave BlankReservedAutoLeave this blank.
Check your understanding! How many batches and entries/addenda appear in the ACH file above?

That’s it! After the file control record, the data in the ACH file is done. Some ACH files, including those generated by PyAch, will include several rows of 9s, which simply serve as padding for the file.

I have listed the pyACH classes and attributes that correspond to most of the ACH fields you will wish to modify in your file generation code. But, pyACH has a designated workflow for generating files with sensible defaults that is much more intuitive than creating all the class instances yourself. Let’s take a look at that next!

In Code

With all that in mind, let’s take a look at how to use PyACH to easily make an example ACH file, so we can see what the implementation of a feature like this might look like using example data. Look at the following code:

from pyach.ACHRecordTypes import ACHFile
import pyach.ACHRecordTypes as ACHRecordTypes

# create file object
payment_file = ACHFile()

# set file header information
payment_file.destination_routing_number = "01234567"
payment_file.origin_id = "76543210"
payment_file.destination_name = "BANK NAME"
payment_file.origin_name = "COMPANY NAME"
payment_file.reference_code = "REFCODE"

# create a batch header record
payment_file.batch_name = "BATCH NAME"
payment_file.entry_description = "ENT_DESC"
payment_file.company_identification_number = "COMP_ID"
payment_file.entry_class_code = "ECC"
payment_file.service_class_code = ACHRecordTypes.MIXED
payment_file.new_batch("DFI_NUM", "BATCH_NAME")

# add 8 batch entry records to the batch
transaction_code = ACHRecordTypes.CHECK_DEBIT
payment_type_code = ACHRecordTypes.SINGLE_ENTRY
for i in range(8):

# add an addenda record to an entry in the batch
addenda_type = ACHRecordTypes.POS
    addenda_type, "Here's some additional information about the transaction"

# save to the file"./ach_file.ach")

As you can see, PyACH makes it vastly simpler to build ACH files than it would be using a traditional string formatting method. All you have to do is create an ACHFile instance, set the appropriate fields on it, and then call the new_batch() and add_entry() methods to add records to the file. The batches and entries will pull many of their properties from those that you set on the ACHFile instance. That program ends up producing a handy template ACH file that looks something like this:

Pretty cool, no? PyACH really does make the process of creating ACH files programmatically easier, and the best part is, it slots directly into your larger python environment, making it an especially good fit in the python development community.


And… that’s it! Now, you know all about ACH files, including all about the different types of records contained in them, what each part of the file means, and how to create them in your own code. Now, you are ready to level up your api or other backend system with the ability to create ACH files! Feel free to refer back to this article to remember what a certain field or section of an ACH file means. Thank you very much for reading, contact me with any questions, and happy coding!


Leave a Reply

Your email address will not be published. Required fields are marked *