Public records in, one researched contact out, source attached.

For teams that prospect property owners: one county record, one AI web-search request, and a phone candidate saved only with its source URL, held for review. Built and deployed 29 to 30 Oct 2026.

The pipeline demo, Dedupe stage /work/lead-scraping-pipeline/demo/ Captured

The pipeline demo at its Dedupe stage: eight stage cards with before and after counts, the Dedupe rule in plain words, and its count, 38 leads to 36 unique businesses. Full capture
The larger project's eight-stage call-list pipeline, replayed stage by stage.

What one press does

A county record names an LLC, and there is no phone number to call. We built the step in between. One press asks an AI with web search for the business's phone number and saves it only with its source.

This is a live demo on its own server, with one real public record. Further down, a replay walks through the larger project's call-list pipeline.

Landlord prospecting, live landlord.fullstack-freelancer.dev Captured

The landlord prospecting app: the heading Public records. Useful prospects., the path from county records to one resolved owner to web research, and the figures 23 linked properties and 6/6 persistence checks. Enlarge
Open the live app

One real Colorado company's public record, with 23 linked properties, produced a phone candidate with its cited source. The run is recorded in the deployed evidence and took the saved contacts from 0 to 1.

  1. Press Enrich with Grok. The server reserves the day's allowance, then makes one request with web search. The site makes one paid request per 24 hours and shares the result with every visitor.
  2. Read the research and the cited source. A number is saved only with a web source URL and no error. Phones are cleaned to 10 digits, repeats are dropped, and every saved number is held for review.
  3. Download the contacts as a CSV file with the columns owner_id, owner_name, phone, source_url, saved_at and status. Repeat visits inside the 24 hours see the saved research and make no new request.

On the run, the model stated 0.9 confidence and explained why it set aside a contact person's number; that reasoning stays in the research text. The contact lives in a separate DuckDB outside the release, and the contact and its attempt survived a restart.

Receipts

  • 1 per 24 hours, whole site Paid research requests Live source line Checked
  • 0 to 1 saved contact Last real run Deployed evidence file Checked
  • 7 true, including the real call Deployment checks Deployed evidence file Checked
  • 5 pytest cases passed, no network Offline tests Local test suite Checked
  • 8 public files identical to local source Release match SHA-256 manifest of 15 files Checked
  • 6 of 6 passed Original version's checks Original verification file Checked

The code

The request allows at most 3 tool calls and 1,800 output tokens. It tells the AI service not to keep the response (store set to false) and times out after 120 seconds. The default model is grok-4.7. [xAI Responses API]

grok_client.py, lines 54 to 65
    try:
        r = req_lib.post(
            API_URL,
            headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
            json={"model": MODEL, "input": prompt, "tools": [{"type": "web_search"}],
                  "max_tool_calls": 3, "max_output_tokens": 1800, "store": False},
            timeout=120,
        )
        r.raise_for_status()
        data = r.json()
        if data.get("error") or data.get("status") == "incomplete":
            raise ValueError("Grok did not complete its research response")

Two small tables hold the contacts and every attempt. [DuckDB]

server.py, lines 28 to 33
        con.execute('''CREATE TABLE IF NOT EXISTS owner_contacts (
            phone VARCHAR PRIMARY KEY, owner_id BIGINT, source_url VARCHAR,
            sourced_at TIMESTAMP, status VARCHAR DEFAULT 'unverified')''')
        con.execute('''CREATE TABLE IF NOT EXISTS grok_research_attempts (
            attempted_at TIMESTAMP PRIMARY KEY, status VARCHAR, research VARCHAR,
            source_url VARCHAR, model VARCHAR, added INTEGER DEFAULT 0)''')

The page is plain JavaScript on a Python standard-library server, with no build step. [Python, DuckDB 1.5.2, requests 2.32.5]

Where it fits

This demo runs the research step and its storage on one real Colorado company and its 23 linked properties. The prompt is fixed on the server, so the AI service only ever sees the server's own request. County feeds, registry matching, listing scrapers and the React interface belong to the larger project.

The county records behind the larger project: 3,361,791 raw rows became 1,391,184 parcel records, one per piece of land, from nine county sources.

Safeguards

Every phone candidate arrives with its cited source, so your team checks it before outreach.

The day's allowance is reserved before the paid call and survives a restart or an error.

The service accepts same-origin POST only, with no body and a 1 KB limit at the web server. Under systemd it runs with no new privileges, a strict file system and a 512 MB memory cap. The release holds no keys, original databases or owner lists, and the original app's database and six source files were not changed.

The larger project stops collecting public data the moment a source refuses, and never retries. [HTTP 403]

Tell us what you would change

Tell us which public records you use and what contact information you need. Include where you would review each candidate's source.

Start a project