# Reonomy Scraper Platform — Architecture ## Overview Full-stack Reonomy property data extraction platform with: - REST API server (Express.js) - MCP Server (TypeScript, stdio transport) - MCP App(s) with React UI - Integration with LocalBosses web app ## Components ### 1. API Server (`/api`) Express.js REST API that orchestrates scraping. **Endpoints:** - `POST /api/scrape` — Start a scrape job with search + output config - `GET /api/scrape/:jobId` — Check job status - `GET /api/scrape/:jobId/results` — Get results (JSON) - `GET /api/filters` — List all available Reonomy filter options - `GET /api/exports/:jobId?format=csv|json` — Export results **Auth:** API key header (`X-API-Key`) ### 2. Scraper Engine (`/engine`) Core scraping logic (evolved from v13). Modular extraction. **Modules:** - `auth.js` — Login, session management, state save/load - `search-builder.js` — Translates filter config → Reonomy UI actions - `extractor.js` — Modular tab extraction (only grabs what user requested) - `anti-detection.js` — Random delays, humanization, daily limits - `queue.js` — Job queue with rate limiting **Extraction modules (per tab):** - `extract-building.js` — Building & Lot data - `extract-owner.js` — Owner names, phones, emails (CURRENT v13 logic) - `extract-sales.js` — Sale history - `extract-debt.js` — Mortgage/lender info - `extract-tax.js` — Tax assessed values ### 3. MCP Server (`/mcp-server`) TypeScript MCP server exposing Reonomy tools. **Tools:** - `reonomy_search` — Configure search filters, returns search ID + count - `reonomy_scrape` — Start extraction from a search (with output config) - `reonomy_get_results` — Fetch results for a job - `reonomy_get_filters` — List all available filter options - `reonomy_export` — Export results as CSV/JSON **Resources:** - `reonomy://app/search` — Search configuration UI - `reonomy://app/results` — Results viewer UI - `reonomy://app/dashboard` — Dashboard with stats ### 4. MCP App(s) (`/mcp-app`) React + Vite, bundled to single HTML files per app. **Apps:** - **Search Builder App** — Visual filter configuration - Location picker, property type checkboxes - Owner filters (phone/email toggles) - Building & Lot ranges - Output field selection - "Start Scrape" button - **Results Viewer App** — Table/card view of scraped leads - Sortable/filterable data table - Expandable owner cards with contact info - Export to CSV button - Job status indicator - **Dashboard App** — Scrape stats overview - Total leads, daily usage, job history - Properties by type chart ### 5. LocalBosses Integration - Add Reonomy channel to toolbar - Wire MCP apps into iframe system - API endpoints accessible from app ## Tech Stack - **Runtime:** Node.js 22 - **API:** Express.js - **MCP Server:** @modelcontextprotocol/sdk (TypeScript) - **MCP App:** React 18 + Vite + Tailwind - **Browser Automation:** agent-browser CLI - **Queue:** In-memory (Bull optional for production) - **Storage:** SQLite (better-sqlite3) for results + job tracking - **Rate Limiting:** Built-in daily caps + per-request delays ## File Structure ``` reonomy-api/ ├── ARCHITECTURE.md ├── package.json ├── tsconfig.json ├── src/ │ ├── server.ts # Express API entry │ ├── routes/ │ │ ├── scrape.ts # /api/scrape endpoints │ │ ├── filters.ts # /api/filters │ │ └── exports.ts # /api/exports │ ├── engine/ │ │ ├── auth.ts # Reonomy auth │ │ ├── search-builder.ts │ │ ├── extractor.ts # Orchestrates tab extraction │ │ ├── anti-detection.ts │ │ ├── queue.ts │ │ └── extractors/ │ │ ├── building.ts │ │ ├── owner.ts │ │ ├── sales.ts │ │ ├── debt.ts │ │ └── tax.ts │ ├── mcp/ │ │ ├── server.ts # MCP server entry │ │ ├── tools.ts # Tool definitions │ │ └── resources.ts # Resource definitions │ ├── db/ │ │ ├── schema.ts # SQLite schema │ │ └── queries.ts # DB operations │ └── types.ts # Shared types ├── mcp-app/ │ ├── package.json │ ├── vite.config.ts │ ├── src/ │ │ ├── apps/ │ │ │ ├── SearchBuilder.tsx │ │ │ ├── ResultsViewer.tsx │ │ │ └── Dashboard.tsx │ │ ├── components/ │ │ │ ├── FilterPanel.tsx │ │ │ ├── PropertyCard.tsx │ │ │ ├── DataTable.tsx │ │ │ └── ExportButton.tsx │ │ └── lib/ │ │ └── mcp-app-sdk.ts │ └── dist/ # Built HTML files └── data/ └── reonomy.db # SQLite database ``` ## Search Config Schema ```typescript interface SearchConfig { location: string; // "Miami-Dade, FL" propertyTypes?: string[]; // ["Multifamily", "Multi Family (General)"] building?: { yearBuiltFrom?: number; yearBuiltUntil?: number; yearRenovatedFrom?: number; yearRenovatedUntil?: number; zoning?: string; lotSizeSfMin?: number; lotSizeSfMax?: number; lotSizeAcresMin?: number; lotSizeAcresMax?: number; opportunityZone?: boolean; totalUnitsMin?: number; totalUnitsMax?: number; buildingAreaMin?: number; buildingAreaMax?: number; }; owner?: { nameOrCompany?: string; ownerType?: "Company" | "Person"; includesPhone?: boolean; includesEmail?: boolean; includesMailingAddress?: boolean; portfolioMin?: number; portfolioMax?: number; ownerOccupied?: boolean; inStateOwner?: boolean; portfolioValueMin?: number; portfolioValueMax?: number; reportedOwner?: string; mailingAddress?: string; }; occupants?: { name?: string; naicsSic?: string; website?: string; }; sales?: { dateRange?: string; multiParcel?: boolean; priceMin?: number; priceMax?: number; pricePerSfMin?: number; pricePerSfMax?: number; likelyToSell?: boolean; }; debt?: { amountMin?: number; amountMax?: number; originationFrom?: string; originationUntil?: string; maturityFrom?: string; maturityUntil?: string; lenderName?: string; cmbsLoan?: boolean; }; distressed?: { auctionDateFrom?: string; auctionDateUntil?: string; preForeclosureCategory?: string; cmbsWatchlist?: boolean; }; } interface OutputConfig { propertyInfo?: ("address" | "type" | "units" | "sqft" | "yearBuilt" | "lotSize" | "zoning" | "opportunityZone" | "apn" | "legal")[]; ownerInfo?: ("name" | "company" | "portfolioSize" | "portfolioValue" | "ownerType")[]; contactInfo?: ("phones" | "emails")[]; salesInfo?: ("lastSaleDate" | "lastSalePrice" | "buyer" | "seller" | "deedType")[]; debtInfo?: ("lender" | "loanType" | "mortgageAmount" | "maturityDate" | "interestType")[]; taxInfo?: ("assessedValue" | "taxAmount")[]; } ```