Guide · Python · SDK · REST API

ABA Routing Number Validation in Python — US Banking

ABA routing numbers are 9-digit codes that identify US financial institutions for ACH transfers, wire payments, and check processing. Here's how the check-digit algorithm works and how to validate them in production.

1. What is an ABA routing number?

An ABA routing number (also called a routing transit number or RTN) is a 9-digit code assigned by the American Bankers Association to identify US financial institutions. Introduced in 1910, it is one of the oldest bank identification systems still in active use.

Routing numbers appear on the bottom-left of paper checks (in MICR encoding) and are required for virtually every type of US domestic payment:

  • ACH transfers — direct deposit, payroll, bill payments
  • Fedwire transfers — same-day domestic wire payments
  • Direct deposit — salary, tax refunds, government benefits
  • Check processing — paper check clearing through the Federal Reserve

If your application accepts US bank account details — for payouts, payroll, or account linking — you need to validate the routing number before initiating any transaction.


2. ABA anatomy — prefix, identifier, check digit

Every ABA routing number has three components:

102101645
10 = Fed prefix210164 = institution ID5 = check digit

Federal Reserve prefix (2 digits)

The first two digits identify the Federal Reserve district where the institution is located. Values range from 01 (Boston) to 12 (San Francisco), with special prefixes for thrift institutions (21–32) and government accounts (00).

ABA institution identifier (4 digits)

Digits 3 through 8 uniquely identify the financial institution within its Federal Reserve district. Combined with the prefix, these six digits form the institution's unique identifier in the Federal Reserve system.

Check digit (1 digit)

The 9th digit is calculated using a weighted-sum algorithm. It detects most single-digit transcription errors and adjacent-digit transpositions.

PrefixFederal Reserve District
01Boston
02New York
03Philadelphia
04Cleveland
05Richmond
06Atlanta
07Chicago
08St. Louis
09Minneapolis
10Kansas City
11Dallas
12San Francisco

3. The check digit algorithm — step by step

The ABA check digit uses a weighted-sum algorithm. The weights 3, 7, 1, 3, 7, 1, 3, 7, 1 are applied to all 9 digits, and the resulting sum must be divisible by 10. Let's walk through it with 102101645:

Step 1 — Write out the digits and their weights

Positiond1d2d3d4d5d6d7d8d9
Digit102101645
Weight371371371

Step 2 — Multiply each digit by its weight

(1×3) + (0×7) + (2×1) + (1×3) + (0×7) + (1×1) + (6×3) + (4×7) + (5×1)

= 3 + 0 + 2 + 3 + 0 + 1 + 18 + 28 + 5

Step 3 — Sum the products and check divisibility by 10

3 + 0 + 2 + 3 + 0 + 1 + 18 + 28 + 5 = 60 → 60 mod 10 = 0

The sum is divisible by 10, so the check digit is valid.

Here's the algorithm implemented in Python:

# aba_check_digit.py — ABA routing number checksum validation
import re


def is_valid_aba(routing_number: str) -> bool:
    """Validate an ABA routing number using the 3-7-1 weighted checksum."""
    if not re.fullmatch(r"\d{9}", routing_number):
        return False

    digits = [int(c) for c in routing_number]
    weights = [3, 7, 1, 3, 7, 1, 3, 7, 1]

    weighted_sum = sum(d * w for d, w in zip(digits, weights))

    return weighted_sum % 10 == 0


print(is_valid_aba("102101645"))  # True
print(is_valid_aba("102101646"))  # False — bad check digit
ℹ️The 3-7-1 weighted sum catches all single-digit substitution errors and most adjacent transpositions. However, it cannot catch all possible transposition patterns and does not verify that the routing number is actually assigned to a bank.

4. Why manual validation isn't enough

Checksum only catches typos

A routing number can pass the 3-7-1 checksum and still be completely fictitious. There are roughly 28,000 active routing numbers in the US — a valid checksum does not guarantee the number is actually assigned to a financial institution.

Doesn't verify the bank exists

Banks merge, close, and change routing numbers over time. A routing number that was valid last year may no longer be active. The Federal Reserve publishes an updated routing number directory, but maintaining a local copy means tracking weekly updates.

ABA vs. ACH routing differences

Some banks use different routing numbers for paper checks (ABA) and electronic transfers (ACH). A routing number valid for check processing may not work for ACH direct deposits, and vice versa. Large banks like JPMorgan Chase and Bank of America have dozens of routing numbers across different states and use cases.

Federal Reserve prefix validation

A routing number with prefix "15" or "99" is invalid because no Federal Reserve district uses those prefixes. A regex or checksum alone won't catch this unless you also maintain a prefix table.


5. The right solution

The IsValid ABA API validates the checksum, verifies the Federal Reserve prefix, and parses the routing number into its components in a single GET request. The response includes the Federal Reserve district, institution identifier, and check digit — all the fields you need for downstream processing.

3-7-1
Checksum
weighted sum validation
<20ms
Response time
pure algorithmic check
100/day
Free tier
no credit card

Get your free API key at isvalid.dev. The free tier includes 100 calls per day — enough for most development and low-volume production use.

Full parameter reference and response schema: ABA Routing Number Validation API docs →


6. Python code example

Using the isvalid-sdk Python SDK or the requests library. Install with pip install isvalid-sdk or pip install requests.

# aba_validator.py
import os
from isvalid_sdk import IsValidConfig, create_client

iv = create_client(IsValidConfig(api_key=os.environ["ISVALID_API_KEY"]))

# ── Example usage ────────────────────────────────────────────────────────────

result = iv.aba("102101645")

if not result["valid"]:
    print("Invalid routing number")
else:
    print(f"Valid ABA — Fed district: {result['federalReserveDistrict']}")
    print(f"Routing number: {result['routingNumber']}")
    print(f"Check digit: {result['checkDigit']}")
    print(f"Institution ID: {result['abaInstitutionIdentifier']}")
    # → Valid ABA — Fed district: Kansas City
    # → Routing number: 102101645
    # → Check digit: 5
    # → Institution ID: 210164

In a bank account verification flow, you might use it like this with Flask:

# app.py (Flask)
from flask import Flask, request, jsonify

app = Flask(__name__)


@app.post("/bank-account")
def bank_account():
    data = request.get_json()

    try:
        aba_check = validate_aba(data["routing_number"])
    except requests.RequestException:
        return jsonify(error="ABA validation service unavailable"), 502

    if not aba_check["valid"]:
        return jsonify(error="Invalid routing number"), 400

    # Log the Federal Reserve district for compliance
    district = aba_check["federalReserveDistrict"]
    app.logger.info(f"Verified routing number in {district} district")

    link_bank_account(
        routing_number=data["routing_number"],
        account_number=data["account_number"],
    )
    return jsonify(success=True, district=district)
Use the federalReserveDistrict field to display the bank's region to the user for confirmation. This helps catch cases where a user accidentally enters a routing number from a different branch.

7. cURL example

Validate a routing number from the command line:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/aba?value=102101645"

Test with an invalid routing number:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/aba?value=000000000"

8. Understanding the response

Valid routing number:

{
  "valid": true,
  "routingNumber": "102101645",
  "federalReservePrefix": "10",
  "federalReserveDistrict": "Kansas City",
  "abaInstitutionIdentifier": "210164",
  "checkDigit": 5
}

Invalid routing number:

{
  "valid": false
}
FieldTypeDescription
validbooleanWhether the routing number passes format, prefix, and checksum validation
routingNumberstringThe 9-digit routing number as submitted
federalReservePrefixstringFirst 2 digits identifying the Federal Reserve district (01–12 for banks, 21–32 for thrifts)
federalReserveDistrictstringHuman-readable name of the Federal Reserve district (e.g. "Kansas City")
abaInstitutionIdentifierstringDigits 3–8 identifying the specific financial institution within the district
checkDigitnumberThe 9th digit used for checksum validation (weighted sum mod 10)
⚠️A valid ABA routing number confirms that the number is structurally correct and has a valid Federal Reserve prefix. It does not guarantee that the routing number is currently active or assigned to a specific bank. For payment execution, your payment processor will verify the routing number against the Federal Reserve directory.

9. Edge cases to handle

Electronic vs. paper routing numbers

Some banks use different routing numbers for electronic (ACH) and paper (check) transactions. When a user provides a routing number from a check, it may not work for ACH direct deposits. Always ask users to specify the type of transaction and confirm the routing number matches the intended use case.

# Prompt users for the correct routing number type
def get_routing_number_help(transfer_type: str) -> str:
    if transfer_type == "ach":
        return "Enter the electronic/ACH routing number (contact your bank if unsure)"
    return "Enter the routing number from the bottom-left of your check"

Thrift institution prefixes (21–32)

Credit unions and savings institutions use Federal Reserve prefixes in the range 21–32. These map to the same 12 districts (21 = Boston, 22 = New York, etc.) but indicate a thrift institution rather than a commercial bank. A simple prefix check that only accepts 01–12 will incorrectly reject these valid routing numbers.

Government routing numbers (prefix 00)

The prefix 00 is reserved for the US government (e.g. the US Treasury). These routing numbers are valid but are not used by commercial banks. If your application only handles private-sector payments, you may want to flag these separately rather than rejecting them.

Multiple routing numbers per bank

Large banks often have different routing numbers for different states or regions. For example, JPMorgan Chase uses different routing numbers in California, New York, Texas, and other states. Bank of America has over 30 active routing numbers. Do not assume a single routing number per bank — always validate the specific number the user provides rather than looking up by bank name.


Summary

Do not rely on checksum alone — it only catches typos, not invalid banks
Do not assume one routing number per bank — large banks have dozens
Validate the Federal Reserve prefix (01-12, 21-32, or 00)
Check the 3-7-1 weighted checksum for basic integrity
Distinguish between ACH and paper routing numbers
Use the API to parse prefix, institution ID, and check digit

See also

Validate ABA routing numbers instantly

Free tier includes 100 API calls per day. No credit card required. Checksum validation, Federal Reserve prefix parsing, and institution identifier extraction in a single call.