Find the agents
who send loans.
Purchase volume comes through real estate agents. A loan officer with twelve productive referral partners has a business; one with none has a marketing budget. This finds those partners — by area, by houses sold, by the price band the lender can actually serve — checks each licence against the state, and hands over a list a person can work through in an afternoon.
Search the way a loan officer thinks.
Not "scrape this site". A territory, a production floor, a price band, and what a record has to carry before it counts.
# everyone in the county doing real volume, in a band this lender can serve node src/cli.js --area "Sonoma County, CA" --sold-min 12 --listed-min 2 \ --price 400K-900K --years-min 3 --dom-max 45 \ --has email,phone --verified --out run.json # one realtor #, straight to the state registry — no crawling at all node src/cli.js --licence 01428836 --state CA
Houses sold
The trailing twelve months, kept apart from career totals — a page that says "312 transactions" is not claiming 312 this year, and a column that conflates them has invented a top producer.
Houses listed
Active listings right now. Every listing draws buyers who need a lender, so the listing side predicts referrals the sold side has already spent.
Realtor #
The state licence number — not the NRDS membership id and not an MLS id, which are different things that different pages all label "Realtor #". Only the first one can be verified.
The same filter runs twice: once against roster cards, before any profile is fetched, and again against the finished records. On the run below that meant 12 profiles never requested — a third of the crawl, saved by reading the card first.
What it will not do.
The refusals are in the code, not in a config file, because a flag that turns off a terms-of-use check is a flag that eventually gets turned on by someone in a hurry.
No portals
Zillow, Realtor.com, Redfin, Trulia, Homes.com and the rest prohibit automated collection in their terms of use. Refused before a request is made — the URL never reaches the fetcher.
No MLS or IDX data
An IDX feed is licensed to the broker who receives it, under rules that do not permit re-publication. A lender who builds a book out of one has a contract problem long before it has a data problem.
No ignoring robots.txt
RFC 9309, including the parts that are usually wrong: the most specific user-agent group wins, the longest matching rule wins, and a 5xx on robots.txt closes the host rather than being read as permission. There is no override.
No consent it does not have
Nothing published on a web page is consent to be texted. Every record leaves with texting off until a consent record exists, and carries what CAN-SPAM and the state rules require before the other channels are used.
Every number says where it came from.
Nothing is written to a record directly. Values are claimed, with a source, and the record keeps every claim ever made. "How do you know she sold 41 houses?" has an answer, and the honest answers are worth different amounts.
| Source | Weight | What it is |
|---|---|---|
| registry | 100 | A state licence lookup. An authority, not a marketer. |
| public_record | 90 | A recorded deed or a county index. |
| jsonld | 70 | The site asserting a value in machine-readable form. |
| microdata | 65 | The same, older. |
| page | 50 | Read out of the page's own markup. |
| text | 35 | Inferred from prose. |
| derived | 30 | Computed by us from other fields. |
The registry overrules the roster
A brokerage page is a marketing asset and lags reality by months. In the run below the state said one agent's licence had expired while her profile was still live and still advertising 24 sales. Her score is zero, and the record says why.
Corroboration breaks the tie
Two pages of equal authority disagreeing about a sold count is ordinary. The figure more independent sites agree on wins — not the one that happened to be crawled last. The loser stays on the record, flagged, rather than being deleted.
Then someone goes through it.
A scrape is half the job. The other half is one person making three hundred decisions without losing their place — so the console is keyboard-first, every figure expands to its sources, and what the filter dropped is one checkbox away.
- JK move · A keep · S skip · D duplicate · U undo
- Possible duplicates are questions, not decisions. Two records the evidence could not settle are shown side by side with the reasons. Fusing two people is worse than listing one twice.
- Conflicts sit at the top of the pane. Where two sources disagree, both are shown with their sources rather than one being silently chosen.
- Decisions persist per run. Closing the tab is not a decision.
- Then export. A CSV, or POST /v1/leads into the AMY hub, where the lender's existing mapping puts each partner in Salesforce and the LOS.
One run, honestly counted.
The numbers below are from the run in the console — two brokerages, one agent's own site and a state registry, swept with --sold-min 8 --has email,phone --verified. It is the fixture world, not a live territory: a real HTTP server built to contain the awkward parts rather than a tidy demo.