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?
![](https://jacob-t-graham.com/wp-content/uploads/2024/07/How-to-Understand-an-ACH-File-Cover-Image.png)
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.
![](https://jacob-t-graham.com/wp-content/uploads/2024/07/image-1024x383.png)
Header
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.
Body
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
Footer
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.
![](https://jacob-t-graham.com/wp-content/uploads/2024/07/image-2-1024x52.png)
Field | Size | Format | Field Name | pyACH Attribute | Comment |
1 | 1 | “1” | Record-Type Code | Auto | Code identifying the File Header Record. |
2 | 2 | “01” | Priority Code | Auto | Only “01” is valid. |
3 | 10 | Num(10) | Immediate Destination | ACHFile.destination_routing_number | Number identifying the site where this file will be processed. Ask your financial institution about this! |
4 | 10 | Num(10) | Immediate Origin | ACHFile.origin_id | 10-digit number assigned to you by your financial institution. Ask your financial institution about this! |
5 | 6 | YYMMDD | File Creation Date | Auto Fileheader._creation_date | Date of file creation. |
6 | 4 | HHMM | File Creation Time | Auto Fileheader._creation_time | Time of file creation. |
7 | 1 | A-Z0-9 | File ID Modifier | Fileheader._file_id_modifier | Distinguishes between multiple files sent in the same day, first labelled “A”, second “B”, etc. |
8 | 3 | “094” | Record Size | Auto | Bytes per record (always “094”). |
9 | 2 | “10” | Blocking Factor | Auto | Records per block. |
10 | 1 | “1” | Format Code | Auto | Only “1” is valid. |
11 | 23 | A-z0-9 | Destination | ACHFile.destination_name | Name of file processing site, ex. “Bank of America DAL”. |
12 | 23 | A-z0-9 | Origin/Company Name | ACHFile.origin_name | Your organization’s name, ex. “JG Web Development”. |
13 | 8 | A-z0-9 | Reference Code | ACHFile.reference_code | This field can be used for your own purposes to describe the input file. |
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
![](https://jacob-t-graham.com/wp-content/uploads/2024/07/image-1-1024x57.png)
Field | Size | Format | Field Name | pyACH Attribute | Comment |
1 | 1 | “5” | Record Type Code | Auto | Code identifying the Batch Header Record. |
2 | 3 | “200” or “220” or “225” | Service Class Code | ACHFile.service_class_code BatchHeader._service_class | Identifies the type of entries in the batch, “220” = credits only, “225” = debits only, “200” = both |
3 | 16 | A-z0-9 | Company Name | ACHFile.batch_name BatchHeader._company_name | The name of your organization. |
4 | 20 | A-z0-9 | Company Discretionary Data | Auto BatchHeader._discretionary_data | Space for your organization’s internal use. |
5 | 10 | A-z0-9 | Company Identification | ACHFile.company_identification_number BatchHeader._company_identification_number | 10-digit number assigned to you by your financial institution. Ask your financial institution about this! |
6 | 3 | “ECC”, “PPD”, etc. | Standard Entry Class Code | ACHFile.entry_class_code BatchHeader._entry_class_code | A Mnemonic, designed by Nacha, which identifies information about the type of payment. |
7 | 10 | A-z0-9 | Company Entry Description | ACHFile.entry_description BatchHeader._entry_description | Use this value to provide a payment description to be displayed to the receiver. Shown in the receiver’s account statement. |
8 | 6 | A-z0-9 | Company Descriptive Date | Auto BatchHeader._descriptive_data | Descriptive date you would like displayed to the receiver. |
9 | 6 | A-z0-9 | Effective Entry Date | Auto BatchHeader._effective_entry_date | Date (up to 30 days in the future) funds should post to the receiver’s account. |
10 | 3 | Leave blank | Settlement Date (Julian) | Auto | Populated by ACH provider. |
11 | 1 | “1” | Originator Status Code | Auto | Identifies originator as a non-Federal Government entity. |
12 | 8 | Num(8) | Originating DFI Identification | BatchHeader._originator_dfi_identification | Number identifying the site where this file will be processed. Ask your financial institution about this! |
13 | 7 | Num(7) | Batch Number | Auto | Number identifying the batch, these increase throughout the file. |
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
![](https://jacob-t-graham.com/wp-content/uploads/2024/07/image-3-1024x315.png)
Field | Size | Format | Field Name | pyACH Attribute | Comment |
1 | 1 | “6” | Record Type Code | Auto | Code identifying the Batch Record. |
2 | 2 | Num(2) | Transaction Code | Entry._transaction_code | Two-digit code identifying account credits, debits, or prenotes. See PyACH for all options. |
3 | 8 | Num(8) | RDFI Routing Transit Number | Entry._routing_number | Transit Routing Number of the receiver’s financial institution. |
4 | 1 | Num(1) | Check Digit | Auto | The ninth character of the RDFI Routing Transit number, used to check for transpositions. |
5 | 17 | A-z0-9 | DFI Account Number | Batch._account_number | Receiver’s account number at the RDFI (receiving institution). |
6 | 10 | $$$$$$$$.¢¢ | Amount | Batch._amount | Amount of money processed in the transaction. Right-justified and left zero-filled. |
7 | 15 | A-z0-9 | Check Serial Number | Batch._identification_number | Check serial number of the receiver’s source document. Is often printed on the receiver’s statement. |
8 | 22 | A-z0-9 | Individual Name | Batch._receiver_name | May contain the receiver’s name, reference number, or identification number. |
9 | 2 | Blank | Discretionary Data | Batch._discretionary_data | Can be used for internal purposes, some institutions require it be left blank. |
10 | 1 | Num(1) | Addenda Record Indicator | Auto | “0” = no addenda, “1” = addenda present. |
11 | 11 | Num(11) | Trace Numbers | Auto | A 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. |
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
![](https://jacob-t-graham.com/wp-content/uploads/2024/07/image-4-1024x66.png)
Field | Size | Format | Field Name | pyACH Attribute | Comment |
1 | 1 | “8” | Record Type Code | Auto | Code identifying the Batch Control Record. |
2 | 3 | “200”, “220”, or “225” | Service Class Code | ACHFile.service_class_code BatchControl._service_class | Identifies the type of entries in the batch, must match the value used in the batch header. |
3 | 6 | Num(6) | Entry/Addenda Count | Auto | Total number of records and addenda in the batch. |
4 | 10 | Num(10) | Entry Hash | Auto | Total of all routing numbers in the batch (not including the check digit). |
5 | 12 | $$$$$$$$$$¢¢ | Total Debit Entry Dollar Amount (Batch) | Auto | Dollar total of debit entries in the batch. |
6 | 12 | $$$$$$$$$$¢¢ | Total Credit Entry Dollar Amount (Batch) | Auto | Dollar total of credit entries in the batch. |
7 | 10 | A-z0-9 | Company Identification | ACHFile.company_identification_number BatchControl._company_identification_number | 10-digit number assigned to you by your financial institution. Ask your financial institution about this! |
8 | 9 | A-z0-9 | Message Authentication Code | Auto (Do not modify!) | Leave blank. |
9 | 6 | A-z0-9 | Reserved | Auto (Do not modify!) | Leave blank. |
10 | 8 | Num(8) | Originating DFI Number | BatchControl._originator_dfi_identification | Number identifying the site where this file will be processed. Ask your financial institution about this! |
11 | 7 | Num(7) | Batch Number | Auto | Batch number for the batch this control record corresponds to. Must match field 12 of the batch header record. |
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
![](https://jacob-t-graham.com/wp-content/uploads/2024/07/image-5-1024x58.png)
Field | Size | Format | Field Name | pyACH Attribute | Comment |
1 | 1 | “9” | Record Type Code | Auto | Code identifying the File Control Record. |
2 | 6 | Num(6) | Batch Count | Auto | Total number of batch header records in the file. |
3 | 6 | Num(6) | Block Count | Auto | Total number of physical blocks in the file. |
4 | 8 | Num(6) | Entry/Addenda Count | Auto | Total number of entry and addenda records in the file. |
5 | 10 | Num(6) | Entry Hash | Auto | Total of all routing numbers in the file (not including the check digit). |
6 | 12 | $$$$$$$$$$¢¢ | Total Debit Entry Dollar Amount (File) | Auto | Dollar total of debit entries in the file. |
7 | 12 | $$$$$$$$$$¢¢ | Total Credit Entry Dollar Amount (File) | Auto | Dollar total of credit entries in the file. |
8 | 39 | Leave Blank | Reserved | Auto | Leave this blank. |
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"
payment_file.create_header()
# 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):
payment_file.batch_records[-1].add_entry(
transaction_code,
"11111111",
"PAYOR_ACCT_NUM",
100,
"PAYOR_ID",
"PAYOR_NAME",
discretionary_data=payment_type_code,
)
# add an addenda record to an entry in the batch
addenda_type = ACHRecordTypes.POS
payment_file.batch_records[-1].entry_records[-1].add_addenda(
addenda_type, "Here's some additional information about the transaction"
)
# save to the file
payment_file.save("./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:
![](https://jacob-t-graham.com/wp-content/uploads/2024/07/image-6.png)
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.
Conclusion
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