How we collect, verify, and publish mortgage rate data.
This is the long-form technical reference. The About page covers the editorial framing of why RatePerch exists. This page covers exactly how the data gets in, how it's validated, how it's aggregated, what our biases are, and where the data set falls short. If anything here is unclear, email hello@rateperch.com and we'll update this document.
1. Data sources
Every number on RatePerch traces back to one of four primary sources. Each is public or borrower-contributed; nothing is licensed from a proprietary data broker.
(a) Borrower-submitted offers
The largest single category. Real borrowers submit the rate they were quoted — credit bucket, down payment, state, loan type, term, lender name, and (optionally) their Loan Estimate document. Submissions are anonymous by default; even when signed in, the submitter's name is never publicly displayed. Median latency from form submission to public visibility: under 5 seconds for auto-approved rows, up to 24 hours for rows held in the moderation queue.
(b) Credit union posted rates
Scraped weekly from each credit union's public rate-sheet page. See §5 for the full scraping methodology, including how we handle sites that block automated access.
(c) Freddie Mac Primary Mortgage Market Survey (PMMS)
The official weekly U.S. average mortgage rate, published every Thursday by Freddie Mac. We pull this via the Federal Reserve Economic Data API and display alongside our borrower-submitted medians for context. The PMMS is a national average; our medians are state-segmented, so it's normal for state medians to deviate from PMMS by ±0.3% in either direction.
(d) NCUA Credit Union directory
The complete list of 4,000+ federally insured credit unions in the United States, sourced quarterly from NCUA Call Report data. We use this to know which CUs exist and what their headquarters state is. Eligibility classification (who can join) is layered on top — see §6.
2. Borrower submission pipeline
When a borrower submits via the submission form, the following happens in order, server-side:
- Rate limit check.Each submission is throttled per-IP to prevent abuse. We never store raw IP addresses — only one-way SHA-256 hashes — so the rate-limit ledger doesn't expose any visitor's identity.
- Bot challenge. Every submission carries a Cloudflare Turnstile token. We verify it server-side before doing any database work or fraud scoring. Invalid tokens fail the request with HTTP 403.
- Schema validation (Zod). Required fields must be present and within range: credit bucket from a fixed 10-bucket enum, purchase price $1–$50M, down payment 0–100%, interest rate 0.1–25%, term in {10, 15, 20, 25, 30}, etc. Invalid submissions return HTTP 400 with the offending field. No partial saves.
- Identity attach (optional).If the user is signed in via Clerk, the submission is keyed to their internal user ID for the dashboard “my offers” view. If signed out, the submission persists as anonymous and the “open to competing offers” toggle is ignored (we have nowhere to deliver bids).
- Fraud scoring. See §3. Score determines whether the row goes to
approved,pending, orrejectedstatus. - Database insert via service-role.Anon and submission metadata (hashed IP, user agent truncated to 500 chars) are stored alongside the offer. Service-role insert bypasses RLS because the public RLS policy doesn't allow anonymous writes.
- Admin notification. If the row landed in
pending, we email the admin so a human can review within hours.approvedrows don't notify (the dashboard shows the queue counts).
3. Fraud scoring & validation
Every submission runs through automated fraud screening before it's eligible for the public dataset. The screening lands each row in one of three states:
- Auto-approved when the submission passes all checks cleanly and statistical signals match peer submissions.
- Pending human review when something looks unusual but plausible — we hand-review these within 24 hours.
- Rejected at write time when the submission contains impossible values (e.g., APR below interest rate, rates outside any realistic historical range, math errors like down payment exceeding purchase price).
We use multiple independent signals — structural sanity checks, statistical comparison against the relevant peer cohort (same state, loan type, term, credit bucket), and product-specific consistency checks (e.g., FHA and VA loans have eligibility patterns that distinguish legitimate from mis-categorized submissions). We deliberately don't publish the exact rule set — fraud detection works better when potential bad actors don't know what tripwires they need to step around.
We err on the side of over-flagging: false positives land in the moderation queue (cheap to fix; we hand-approve good submissions quickly), while false negatives publish a bad rate that erodes trust. Suspicious submissions are held, not silently dropped — so a real borrower whose offer looked unusual can still see their submission moved to approved once a human eyeballs it.
4. What “verified” means
A small fraction of submissions carry the verified badge. This means the borrower attached their actual Loan Estimate — the standardized document every lender must provide within 3 business days of an application — and we successfully OCR'd it and confirmed the interest rate, APR, lender name, and date match what they typed into the form within tolerance:
- Interest rate: must match to within 0.05% (tolerance for typo / rounding).
- APR: must match to within 0.05%.
- Lender name: must fuzzy-match to within ~85% similarity (allows for “Rocket Mortgage” vs “Rocket Mortgage LLC”).
- Date: must be within 60 days of submission date.
Submissions without an uploaded LE, or where the OCR can't confidently confirm the fields, publish as unverified. They're still real (a real human submitted them) — they just don't carry document-grade attestation. We make the distinction visible in every table so readers can weight accordingly.
5. Credit union rate scraping
Borrower submissions cover what borrowers actually got. Credit union posted rates cover what's currently being offered to the public. We need both, and the second one we have to fetch ourselves.
Our scraper runs every Monday at 1pm UTC. For each credit union in the source registry, the scraper picks one of five extraction strategies based on how the CU's website is built:
| Tier | Method | When we use it |
|---|---|---|
| 1 | HTTP + AI extraction | Site returns parseable HTML. Default for most CUs. |
| 2 | Headless browser (Playwright) | JS-rendered SPAs that don't serve content via plain HTTP. |
| 3 | Stealth browser | Sites with anti-bot challenges (Cloudflare Turnstile, fingerprint detection). |
| 4 | Residential proxy + stealth | Sites with IP-based blocking. Reserves for high-value targets only. |
| 5 | Manual operator entry | PDFs, login-gated, or otherwise unscrapeable. Updated weekly by hand. |
When we hit a CU that publishes rates publicly but blocks our scraper, we don't drop it. Coverage gaps are what destroy a transparency project's credibility; the right answer is always “find a way to get those rates,” even if that means a freelancer manually retypes them weekly into our admin import form.
For Tiers 1–3 we use layout-adaptive content extraction rather than per-site CSS selectors, so a credit union redesigning their rate page doesn't silently break the pipeline. Every extracted rate is sanity-checked before insertion: rate must fall within a realistic historical range, APR (when present) must be greater than or equal to interest rate, loan type and term must match our known enumerations. We'd rather drop a malformed extracted rate than publish a wrong value.
6. Credit union eligibility classification
Credit unions don't serve everyone — every CU has a “field of membership” (FOM) that defines who can join. We classify each CU into one of four eligibility types, which determines which state pages show its rates:
- National easy-join
- Anyone in the US can join, typically by donating $5–$20 to an associated non-profit. Examples: Alliant Credit Union, Bethpage Federal Credit Union, Connexus. Their rates appear on every state's page.
- Military
- Membership requires active-duty / veteran / family relationship with the military or Department of Defense. Examples: Navy Federal Credit Union, PenFed's legacy military membership tier. Effectively nationwide (military members live everywhere), so rates appear on every state's page with a clear eligibility note.
- State / regional
- Membership requires residency in specific states or counties. Examples: BECU (Washington, Idaho, Oregon, South Carolina), State Employees' Credit Union of NC. Rates appear only on the relevant state pages.
- Employer / community SEG
- Membership requires affiliation with a specific employer, university, or community group (Select Employee Group). These are the long tail of US CUs. Currently not surfaced on state pages because eligibility verification is complex; future versions may add an explicit “Do you work for X?” flow to surface them.
Classification happens at the time we add a CU to the scraper registry. The NCUA Call Report data tells us the CU's FOM type code, but the codes don't map cleanly to user-facing eligibility, so a human reviews each CU's actual membership page before we publish. If you spot a misclassification, report it from the credit union directory and we'll fix it within a week.
7. Aggregation & statistical methods
Time windows
Borrower-quoted offer aggregates use a rolling 30-day window ending today. Older offers don't roll into “this week’s medians” — they get filtered out. Credit union posted rates use the most recent effective date per (lender, loan type, term) tuple; older rates are flagged as superseded but stay in the database for historical context.
What gets aggregated
Headline figures on the homepage and state pages are the medianinterest rate within (state, loan type, term) segments, computed from approved, non-deleted offers. We use the median rather than the mean because, at the small sample sizes typical of a single state, one unusually high quote (a low-credit or non-QM offer, or a typo) would drag a mean well above the rate a typical borrower is seeing. We also drop any rate outside a 2%–12% plausibility band before aggregating — those are data-entry errors, not real first-mortgage quotes. We don't adjust for points, lender credits, or special promotions — adjusting introduces hidden judgment calls that distort comparison.
What gets shown alongside
We disclose sample sizes (“median of N offers”) everywhere a number appears. A 6.42% median from 3 submissions is not the same signal as a 6.42% median from 300 submissions, and we never want to imply otherwise.
Cohort percentiles (planned)
Future versions of state pages will include a “how do you compare” widget showing where a borrower's quote lands within their cohort (state × loan type × credit bucket). The cohort is the relevant comparator — a 6.5% rate at 740 FICO with 20% down is not the same data point as a 6.5% rate at 620 FICO with 5% down.
8. Display ordering on state pages
On state-rate pages (/best-rates/[state]), credit union posted rates are grouped by loan type and displayed in this order:
- Conventional — always shown first, expanded by default. Covers ~70% of US mortgages.
- FHA — collapsed by default. Covers ~10% of mortgages, typically lower-credit / lower-down borrowers.
- VA — collapsed by default. Covers ~6% of mortgages but only for veterans / active-duty / qualifying family. Often the lowest rates available, but ineligible to most readers.
- Jumbo — collapsed by default. Covers ~14% of mortgages but only in high-cost markets (loan amount above the conforming limit).
This ordering is deliberate. A purely lowest-rate-first sort would put VA rates (typically 0.3–0.5% below conventional) at the top of every state page. Visitors who can't qualify for VA would see a number, get excited, then bounce when they realize the eligibility requirement. The same logic applies to FHA (high-DP FHA is unusual) and jumbo (most borrowers don't cross the conforming limit). Showing conventional first matches the realistic visibility hierarchy for someone searching “best mortgage rates in {state}.”
9. Update cadence
- Borrower submissions: real-time. Auto-approved offers appear in the public dataset within ~5 seconds. Pending offers appear after human moderation, typically within 24h.
- Credit union posted rates: weekly. Vercel cron triggers every Monday at 13:00 UTC (09:00 ET / 06:00 PT). One run takes ~60–90 seconds for the current source list; failures and successes both email the operator.
- Freddie Mac PMMS: on-demand at page render. The FRED API serves the latest weekly value; we cache for 5 minutes at the CDN to keep page-load fast.
- NCUA directory: manual quarterly refresh. The NCUA publishes Call Report data quarterly; we re-import to pick up newly-chartered CUs and dissolved-CU removals.
- State pages cache: 10 minutes at the Vercel edge. Pages revalidate in the background after that, so visitors during stale-time get the cached version while the next visitor gets the refreshed one.
10. Known limitations
This is what we'd want a savvy reader to know before they over-rely on our data. We'll update this section as we close items off.
- State-level only, not city-level. A “best rates in California” page is an aggregate of every California submission. Bay Area jumbo loans price differently from rural-California conventional. Future versions may add metro-level breakdowns.
- Skew when sample sizes are small. Sample sizes per (state × loan type × credit bucket × term) segment can be small enough that a single bad submission disproportionately moves the median. Mitigations: explicit sample-size disclosure on every aggregate, automated outlier detection (§3), human moderation queue.
- ARM products not yet scraped. Adjustable-rate mortgages quote with different conventions (initial fixed period + index + margin + caps). The current scraper extracts fixed-rate only. ARM borrowers should compare elsewhere for now.
- No first-time-buyer program data. State Housing Finance Agency loans, USDA loans, Down Payment Assistance programs — not currently in scope. These are valuable for the audiences that qualify and remain on the roadmap.
- Credit union long tail unrepresented. The 4,000+ CUs in our directory are mostly small SEG-restricted institutions. We only scrape the ~50 largest by membership. The long tail rarely has competitive rates, but a real audit project would acknowledge them.
- IP-based geo detection misses some users. Our state-page personalization uses Vercel's IP-to-state lookup. ISP routing through other states (especially for mobile carriers and VPN users) gives wrong results. We expose a manual state picker; future versions will auto-prompt when detection seems uncertain.
- No real-time interest rate locking quotes. Posted rates can be 24–72 hours stale by the time a borrower applies. The same lender quoting our scraped rate might quote 0.1–0.3% different when a real application hits.
11. Roadmap & planned changes
What we're actively working on, in rough order. This isn't a marketing roadmap (no “coming Q4” commitments) — just what's actually queued up and getting attention.
- Expand CU rate scraping to 50+ sources covering every state with at least 3 viable CUs per state page.
- Ship the “how do you compare” widget on state pages (cohort percentiles, see §7).
- Refi monitoring: borrowers register their current rate; we alert them when refinancing would save them money based on current dataset rates.
- Loan Estimate OCR pipeline for verified-badge submissions at scale.
- State-specific FHA / VA / jumbo program pages (separate URLs from the consolidated state page).
- Mortgage compliance review by a RESPA-experienced attorney before activating any loan-officer-related monetization.
- Public methodology data API for researchers, journalists, and bloggers who want to cite RatePerch numbers with attribution.
12. How to report a problem
We're going to get things wrong. The methodology above describes our intent; the implementation can drift, edge cases can slip through, and the world changes. Three ways to flag something:
- Wrong CU eligibility: the credit unions directory has a per-CU report button. We review submissions within a week.
- Wrong rate, wrong lender name, wrong anything in the dataset: email hello@rateperch.com with the page URL and what's wrong. We'll fix and email back.
- Methodology question or critique:same email. If your argument changes our methodology, we'll update this page and credit the change in the “last updated” line at the top.
We treat this page as living documentation. Every change ships with a date stamp; significant changes get called out at the top of the section that changed.
