# Real Estate Deal Finder (`optirefine/real-estate-deal-finder`) Actor

Scrape Zillow across the US and Canada for motivated sellers, not just listings. Every row carries the listing agent's name and phone, days on market, full price-cut history, and a transparent distress score built from price drops and motivation language like "as-is" and "estate sale".

- **URL**: https://apify.com/optirefine/real-estate-deal-finder.md
- **Developed by:** [OptiRefine](https://apify.com/optirefine) (community)
- **Categories:** Lead generation, Real estate, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 deep property lookups

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

**Find motivated sellers, not just listings.**

Real Estate Deal Finder scrapes **Zillow** across the **US and Canada** and returns what deal sourcing actually needs: the property, the person to call, and the signals that say a seller wants out.

Every row includes:

- **Property facts** — address, price, beds, baths, sqft, lot size, year built, status, coordinates
- **Agent contact** — listing agent name, phone, and brokerage
- **Deal signals** — days on market, full price-cut history, price per sqft, and list price against Zestimate and tax assessment
- **A distress score** — transparent and auditable, built from price cuts, time on market, and motivation language like *as-is*, *motivated seller*, *estate sale*, and *cash only*. Every factor that contributed is recorded on the row, so you can see exactly why a property scored high

Most scrapers hand you listings. This one tells you **which listings are worth a phone call** — and gives you the number to dial.

Paste a Zillow search URL, set how many properties you want, and run. Export as JSON, CSV, or Excel, or pull straight from the API. Schedule runs to catch new price cuts the day they land.

**Built for** wholesalers, investors, bird dogs, and agents working stale or expired inventory.

**Cost control is built in.** You set the property cap per run and nothing goes past it. Every field is optional except listing ID and URL, so a missing agent phone never costs you the whole row.

Coming next: Zoopla, Rightmove, Redfin, and Inmuebles24.

### Why use Real Estate Deal Finder?

Most real estate scrapers answer "what is for sale?" That question is already well served. This Actor answers a harder one: **"who is likely to sell at a discount, and how do I reach them?"**

- **Wholesalers and investors** — build a call list ranked by seller motivation instead of reading listings one by one
- **Bird dogs** — surface stale and price-cut inventory in a target ZIP before it hits the usual channels
- **Agents** — find expired, withdrawn, and long-sitting listings worth a conversation
- **Analysts** — track price-cut velocity and days-on-market trends across a market over time

Because it runs on the Apify platform, you also get scheduling, a REST API, webhooks, integrations with Make and Zapier, proxy rotation, and run monitoring without building any of it yourself.

### How to use Real Estate Deal Finder

1. Open Zillow and search the way you normally would — set your area, price range, and any filters.
2. Copy the resulting URL from your browser.
3. Paste it into the **Start URLs** field on the Input tab.
4. Set **Max properties** to cap how many results you want. The default is 25.
5. Click **Start** and wait for the run to finish.
6. Open the **Output** tab, or download the dataset as JSON, CSV, or Excel.

To keep a list fresh, use the **Schedule** tab to run it daily or weekly. New price cuts appear as soon as Zillow publishes them.

### Input

| Field                 | Type    | Description                                                                 |
| --------------------- | ------- | --------------------------------------------------------------------------- |
| `startUrls`           | array   | Zillow search or property URLs. US and Canadian URLs both work.             |
| `maxProperties`       | integer | Maximum properties to return. Default `25`. Caps both output size and cost. |
| `maxRequestsPerCrawl` | integer | Safety ceiling on requests during enrichment. Rarely binds.                 |
| `brightDataApiKey`    | string  | Optional. Leave blank to use the system key.                                |

Example:

```json
{
    "startUrls": [{ "url": "https://www.zillow.com/whitby-on/houses/" }],
    "maxProperties": 25
}
```

### Output

Each property becomes one dataset row. You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

```json
{
    "zpid": "2078143210",
    "url": "https://www.zillow.com/homedetails/...",
    "address": { "street": "1428 Elm St", "city": "Whitby", "state": "ON", "zip": "L1N 3B4" },
    "price": 649000,
    "beds": 3,
    "baths": 2,
    "sqft": 1740,
    "yearBuilt": 1974,
    "status": "FOR_SALE",
    "latitude": 43.8975,
    "longitude": -78.9428,
    "agentName": "Jordan Avery",
    "agentPhone": "+1-905-555-0142",
    "brokerageName": "Lakeshore Realty",
    "daysOnMarket": 96,
    "priceHistory": [
        { "date": "2026-05-02", "event": "Listed for sale", "price": 719000 },
        { "date": "2026-07-18", "event": "Price change", "price": 649000 }
    ],
    "priceCutCount": 1,
    "priceCutTotalPct": 9.7,
    "pricePerSqft": 373,
    "motivationKeywords": ["96 days on market", "1 price cut(s)", "price down 9.7%", "as-is"],
    "distressScore": 74
}
```

### Data fields

| Field                                                                              | Description                                 |
| ---------------------------------------------------------------------------------- | ------------------------------------------- |
| `zpid`, `url`                                                                      | Listing identifier and link. Always present |
| `address`, `latitude`, `longitude`                                                 | Split address plus coordinates              |
| `price`, `beds`, `baths`, `sqft`, `lotSize`, `yearBuilt`, `propertyType`, `status` | Core property facts                         |
| `agentName`, `agentPhone`, `brokerageName`                                         | Listing agent contact, where published      |
| `daysOnMarket`                                                                     | Days since listing                          |
| `priceHistory`, `priceCutCount`, `priceCutTotalPct`                                | Full price timeline and cut totals          |
| `pricePerSqft`, `zestimate`, `priceVsZestimatePct`, `taxAssessedValue`             | Valuation comparisons                       |
| `motivationKeywords`                                                               | Every factor that contributed to the score  |
| `distressScore`                                                                    | Composite seller-motivation rating          |

Every field except `zpid` and `url` may be `null`. A property with no published agent phone is still returned, with that field empty, rather than being dropped.

`priceCutCount` and `priceCutTotalPct` measure different things and are both worth reading. The count is explicit price-change events. The percentage is how far the asking price has fallen since this campaign opened, which also captures a seller who withdrew and relisted lower without ever filing a price change — often the more motivated seller of the two. Sorting on the count alone will miss them. Where a listing has no opening price on record, the percentage is `null` rather than `0`, so an unknown is never read as "no cut".

### How much does it cost to scrape Zillow?

Pricing is **pay per event**, so you are charged for results rather than for compute time. Two events are billed: one per row returned, and a higher one per property that gets a dedicated second lookup. That second lookup is what buys square footage, year built, tax assessment, Zestimate, and the full price timeline the cut signals are computed from. How much it adds varies by market — see the Canada note below, where several of those fields do not exist at source.

The practical control is **`maxProperties`**. It caps output and spend together — raise it for a bigger pull, lower it to sample a market cheaply. A run cannot exceed the cap you set, so a broad search URL cannot produce a surprise bill.

### Tips

- **Filter on Zillow first.** Narrowing by price, beds, or property type before copying the URL is free; filtering afterwards is not. Treat Zillow's filters as strong hints rather than hard limits — a price-banded search still returns some listings just outside the band, the same way it does in your browser.
- **Raise `maxProperties` when you want stale inventory.** Discovery favours recently listed properties, so a small run is mostly fresh listings — and fresh listings have no price cuts and little time on market to score on. A wider pull reaches deeper into a market's older inventory.
- **Start at the default 5.** Confirm the data is what you expect, then raise `maxProperties`.
- **Give a big pull a bigger timeout.** Collection happens upstream and its latency varies a lot with how busy the source is — the same three-property search has finished in two minutes and in eight. If a run ends early saying it ran out of time, raise the run timeout rather than lowering the cap. Rows already collected are always returned; the run never throws away what it has.
- **Sort by `distressScore` descending** and work top-down — that is the whole point of the field.
- **Schedule daily** on a tight geography. Price cuts are most actionable the day they appear.
- **Check `motivationKeywords`** before calling. It shows why a property scored the way it did.

#### What affects how many rows you get back

`maxProperties` is a ceiling, not a guarantee. A run can return fewer rows when the search area holds less inventory than you asked for, or when an upstream page fails to load — a 25-property pull on a large market returned 22 in testing, with three pages erroring. You are charged for what you receive, so a short run costs less rather than costing you the difference.

### FAQ, disclaimers, and support

**Does it work for Canada?** Yes, with one caveat worth knowing before you run it. Zillow publishes Canadian listings through partnerships with 250+ brokerages, and coverage is strongest in Ontario, BC and Alberta, thinner in Quebec and Atlantic Canada. But Zillow computes **no Zestimate and holds no tax-assessment data outside the US**, and Canadian listings frequently publish only the brokerage rather than a named agent. Canadian rows are therefore scored on fewer factors than US rows — the score is still honest, because absent inputs are skipped rather than counted as zero, but it is built on less evidence. For the richest signal, use US markets.

**Why is an agent phone missing on some rows?** Not every listing publishes one — this is common on Canadian listings, where the brokerage is often the only published contact. The row is still returned with the field empty rather than discarded.

**Is `distressScore` a prediction?** No. It is a transparent sum of observable factors, all listed in `motivationKeywords`. It ranks candidates for a conversation; it does not predict a sale.

**Is scraping legal?** This Actor collects publicly available listing data. Laws and site terms vary by jurisdiction and change over time. Review your local regulations and each site's terms before using the data commercially, particularly for outreach to contacts.

Found a bug or want another portal? Open an issue on the **Issues** tab. Custom versions and additional markets are available on request.

# Actor input Schema

## `startUrls` (type: `array`):

Zillow search or property URLs. US and Canadian URLs both work. Search on Zillow with your filters applied, then paste the resulting URL here — filtering on Zillow is free, filtering afterwards is not.

## `maxProperties` (type: `integer`):

Maximum properties to return. Caps both output size and cost — a run cannot exceed this number. Collection time grows with this number: 5 properties take about a minute, 25 about four. Raise the run timeout as well if you raise this a long way.

## `maxRequestsPerCrawl` (type: `integer`):

Safety ceiling on requests made during the enrichment phase. Max properties is the control that matters; this one only binds if something goes badly wrong.

## `brightDataApiKey` (type: `string`):

Leave blank to use the system key.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.zillow.com/toronto-on/houses/"
    }
  ],
  "maxProperties": 5,
  "maxRequestsPerCrawl": 100
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        {
            "url": "https://www.zillow.com/toronto-on/houses/"
        }
    ],
    "maxProperties": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("optirefine/real-estate-deal-finder").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": [{ "url": "https://www.zillow.com/toronto-on/houses/" }],
    "maxProperties": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("optirefine/real-estate-deal-finder").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://www.zillow.com/toronto-on/houses/"
    }
  ],
  "maxProperties": 5
}' |
apify call optirefine/real-estate-deal-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,optirefine/real-estate-deal-finder"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/vCMbsraX1h4nFOrXI/builds/kq8zcquuFNxmG6LWO/openapi.json
