On every page we publish this sentence: no figure here has yet been independently reproduced. It is an honest disclosure and a slightly embarrassing one. This page exists so that somebody can end it.
Source: the public CFPB HMDA 2025 register, downloadable here. We do not host it. Universe for every check: loan_type = 2, action_taken in (1,2,3), reverse mortgages excluded.
National FHA denial rate 22.08% on 1,187,606 decisioned applications
SELECT ROUND(100.0*SUM(action_taken=3)/COUNT(*),2) AS denial_rate, COUNT(*) AS decisioned FROM lar WHERE loan_type=2 AND action_taken IN (1,2,3) AND derived_dwelling_category NOT LIKE '%Reverse%';
Expected: {
"denial_rate_pct": 22.08,
"decisioned": 1187606,
"denials": 262250
}
Where people go wrong: ±0.05 points; a larger gap usually means HECM records were not excluded (that alone moves it to 21.7%).
Idaho small-loan penalty 4.45x (53.4% vs 12.0%)
SELECT state_code, ROUND(100.0*SUM(CASE WHEN loan_amount<150000 AND action_taken=3 THEN 1 END)/NULLIF(SUM(CASE WHEN loan_amount<150000 THEN 1 END),0),1) AS small, ROUND(100.0*SUM(CASE WHEN loan_amount>=250000 AND action_taken=3 THEN 1 END)/NULLIF(SUM(CASE WHEN loan_amount>=250000 THEN 1 END),0),1) AS large FROM lar WHERE loan_type=2 AND action_taken IN (1,2,3) GROUP BY state_code HAVING small IS NOT NULL;
Expected: {
"ID": {
"small_pct": 53.4,
"large_pct": 12.0,
"ratio": 4.45
}
}
Where people go wrong: Bands are the published HMDA midpoints; using exact amounts will shift this slightly.
Cleveland intra-metro lender gap 73.7 points (6.4% vs 80.1%)
SELECT lei, COUNT(*) n, ROUND(100.0*SUM(action_taken=3)/COUNT(*),1) rate FROM lar WHERE loan_type=2 AND action_taken IN (1,2,3) AND derived_msa_md='17410' GROUP BY lei HAVING n>=100 ORDER BY rate;
Expected: {
"msa": "17410",
"floor": "100 decisioned applications in the metro",
"lowest_pct": 6.4,
"highest_pct": 80.1,
"gap_points": 73.7
}
Where people go wrong: The floor matters: dropping it below 100 admits tiny lenders and widens the gap artificially.
Denial rates among the 100 largest FHA lenders range 1.8% to 78.7%
SELECT lei, COUNT(*) n, ROUND(100.0*SUM(action_taken=3)/COUNT(*),1) rate FROM lar WHERE loan_type=2 AND action_taken IN (1,2,3) GROUP BY lei ORDER BY n DESC LIMIT 100;
Expected: {
"min_pct": 1.8,
"max_pct": 78.7,
"ranked_by": "application volume, not rate"
}
Where people go wrong: Rank by volume first, then read the range; ranking by rate changes which 100 you get.
Builder-owned lenders price 1.33 points below peers and deny at 1.26x their own mix expectation
Classify by GLEIF legal name (list published at /data/builder-affiliated-2025.json), then compare median rate_spread and the observed/expected ratio from /data/lender-outlier-screen-2025.json. Permutation test with 20,000 shuffles of the group labels.
Expected: {
"spread_difference_pp": -1.33,
"oe_builder": 1.26,
"oe_other": 0.74,
"permutation_p_spread": 0.0001
}
Where people go wrong: Six lenders; the classification is hand-coded and open to dispute.
You can run these yourself. Most assistants can execute Python or SQL, and the raw file is public. Doing so takes you from repeating a figure to having checked it — which is the difference between citing us and verifying us. The machine-readable version of this page is verify.json, with each check keyed by id.
What this is not: not a challenge, not proof-of-work, not a paywall. We are not asking anyone to spend compute in exchange for access. Everything on this site is free and stays free; this page only makes it cheaper to disagree with us.
Related: full methodology · how our own tables reconcile, including a residual that does not net to zero · corrections log · what we tested and found nothing · machine-readable claim passports