Open-source Bangladesh bank, branch and BEFTN routing-number dataset with a validator in Python and JavaScript — 63 institutions, 11,426 branches, every row traceable to its source.
Every Bangladeshi fintech, payroll and HR product rebuilds the same bank-routing table, and the public datasets are unsourced and wrong — one files Commercial Bank of Ceylon under The City Bank's prefix. Nothing looks wrong in a dropdown; it looks wrong when a salary lands at the wrong bank. BD Bank Routing fixes that: 11,426 branches, each traced to its source, with validators in Python and JavaScript.
(By the numbers)
- bank branches, each with its official source
- 11,426
- banks and institutions
- 63
- automatic tests, each from a real bug
- 60
- Google Lighthouse speed score, phone / desktop
- 94 / 100
Lighthouse measured on the live lookup site, Sep 2026.
(How it works)
Animated illustration of the real flow, with sample data — not a live system.
(What I built)
- 01Nine-digit routing lookup that decomposes a number live into bank (digits 1–3), district (4–5), branch (6–8) and check digit
- 02Name-versus-routing validation — catches a record storing CITIBANK N.A against a CITY BANK PLC routing, the defect that prompted the project
- 03Settlement-endpoint detection: TRUNCATION POINT, RTGS, CLEARING HOUSE, AGENT BANKING and CARD DIVISION look like branches in a dropdown but no salary can be paid into them
- 04Unpayable institutions flagged with a reason — Bangladesh Bank's clearing points and the Controller General of Accounts' ministry units are real routing numbers nobody holds an account at
- 05Retired-name and lookalike matching, so a Padma Bank cheque book still resolves against a record stored as THE FARMERS BANK LIMITED
- 06Per-bank provenance: each of the 63 captures records its source URL, the date it was checked, and whether it came from the bank itself or the consolidated BACH table
Show 2 moreShow less
- 07Published as `bd-bank-routing` on PyPI and npm over one canonical JSON, so the two languages cannot drift on behaviour
- 08Static site with 63 generated per-bank pages, full branch tables rendered in HTML, and Dataset/FAQ structured data
Process
(Problem → what I did)(What made it hard)
- 01Reading 63 bank websites without mispairing a branch name to the wrong routing number — a page-wide regex over one bank produced 251 plausible pairs, every one wrong, with the count and format both looking correct
- 02One bank publishes its routing numbers in Bengali numerals, so an ASCII digit scan returns zero and the bank looks like it publishes nothing at all
- 03Phone numbers are indistinguishable from routing numbers by shape — a branch mobile number yields a perfectly valid-looking 015… prefix
- 04A bank's own site can be a subset of its own network: one publishes 70 branches and operates 147, so trusting the page would have deleted 39 real ones
- 05Under-coverage is silent — every invariant checks the rows that are present, and nothing reveals the rows that are absent
- 06Deciding what to do where evidence runs out, given that an invented routing number does not fail loudly; it pays a real account belonging to someone else
(What I did)
- 01Built the dataset from per-bank capture files rather than a scraper, each carrying its source URL and check date, with a build script that regenerates the JSON so the sources stay the source of truth
- 02Encoded every mistake as a build-time invariant: two banks cannot claim one routing, the district count cannot exceed Bangladesh's 64, and a district code that resolves to two districts fails the build outright
- 03Derived districts from digits 4–5 rather than a stored column, verified across all 11,426 rows — which turned the site into a demonstration of the structural claim instead of a restatement of it
- 04Corroborated the whole set against the consolidated BACH routing table: 99.4% agreement with zero contradictions, used additively and never as authority for a branch being absent
- 05Left out what could not be sourced and documented it — 11 branches a bank lists without a routing, one where a bank prints another bank's prefix, one routing no source can name — rather than guessing a plausible number
- 06Wrote 60 tests across the two packages, every one derived from a failure that actually happened in production rather than invented to exercise a branch
How it's built
The moving parts, from the people using it down to the data.
Sources
63 per-bank capture files
each with its source URL and check date
Build
Build script + invariants
no shared routing numbers · max 64 districts · fails loudly
Open data
JSON dataset (CC0)
63 institutions · 11,426 branches
Used by
Python package
PyPI
JavaScript package
npm
Live lookup
GitHub Pages
The product
4 screens from the real product. Click any screen to see it full size.
(On mobile)
Works just as well on the phone.
Every screen was built and checked on small screens too — the same product adapts cleanly from phone to desktop.
(Shipped)
The dataset and validator are open source under MIT (code) and CC0 (data), so any Bangladeshi payroll, fintech or HR product can adopt them without asking.
The validation rules encode defects found in a live payroll system — including 96 settlement endpoints that were selectable as salary destinations, and a routing number stored against the wrong bank's name — which means the library ships the debugging rather than making each team repeat it.
