# Redfin Scraper - Homes for Sale & Sold Listings (`abotapi/redfin-scraper`) Actor

Scrape redfin.com home listings: price, beds, baths, sqft, lot, year built, coordinates, address, MLS, status, days on market, photos, schools, amenities, tax records, agent and brokerage plus 90+ fields. Search any city, ZIP, neighborhood, or county with sort and filters, or paste Redfin URLs.

- **URL**: https://apify.com/abotapi/redfin-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Real estate, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 4 total users, 0 monthly users, 96.6% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 listing results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Redfin Scraper: Homes for Sale, Sold Listings & Property Data

Redfin Scraper pulls structured home listings from redfin.com at scale. Search any city, ZIP code, neighborhood, or county, narrow with sort and filters, and get a clean JSON record per home with price, beds, baths, square footage, lot size, year built, coordinates, address, MLS number, status, days on market, photo data, key facts, and listing remarks, plus the listing agent and brokerage when detail enrichment is on. Or paste Redfin region and listing URLs and let the scraper walk them for you, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.

### Why This Scraper?

- **90+ fields per listing.** Price, price per sq ft, beds, full and partial baths, square footage, lot size, stories, year built, HOA dues, coordinates, MLS number, status, days on market, photos, key facts, tags and the full listing remarks on every search result.
- **Search or paste URLs.** Find homes by city, ZIP, neighborhood or county with sort and filters, or paste exact Redfin region and listing URLs and pull them verbatim.
- **For sale or recently sold.** Switch between active listings and homes sold in the last three years.
- **Optional detail enrichment.** Add the listing agent, brokerage, valuation label, nearby schools with ratings, structured amenities, home highlights, public and tax records, and the Redfin Estimate per home.
- **Predictable cost.** One Max listings cap controls exactly how many records a run returns, with a separate Max pages safety stop for paging depth.
- **Built for schedules.** Incremental mode returns only new and changed listings on recurring runs, and a genuinely blocked run fails loudly instead of returning an empty dataset.
- **MCP export.** Optionally pipe results straight into Notion, Linear, Airtable, or Apify.

### Use Cases

- **Real estate investors and analysts:** track pricing, days on market and price-per-sqft trends across neighborhoods to spot value.
- **Agents and brokerages:** monitor competing listings, new inventory and price changes in a farm area on a schedule.
- **Real estate data products and dashboards:** feed structured listing data into your own search, map or comps tool.
- **Market researchers:** build historical datasets of sold homes, including sale dates and property details, for trend analysis.
- **Lead generation tools:** collect listing agent and brokerage details alongside property data for outreach lists.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live listing. Fields below come from a **search result** or a **pasted region URL**. A pasted single **listing** URL returns a narrower record; see "Paste a single listing URL" under How to Use.

| Field | Example |
| --- | --- |
| `id` | `12345678` |
| `url` | `"https://www.redfin.com/IL/Chicago/123-Example-St-60601/home/12345678"` |
| `price` | `575000` |
| `priceType` | `"List price"` (when detail enrichment is on) |
| `pricePerSqFt` | `311` |
| `beds` | `3` |
| `baths` | `2.5` |
| `bathFull` / `bathPartial` | `2` / `1` |
| `sqft` | `1850` |
| `lotSize` | `4200` |
| `yearBuilt` | `1998` |
| `hoaDues` | `250` |
| `propertyTypeLabel` | `"Condo/Co-op"` |
| `status` | `"Active"` |
| `daysOnMarket` | `12` |
| `streetLine` | `"123 Example St"` |
| `city` / `state` / `zip` | `"Chicago"` / `"IL"` / `"60601"` |
| `latitude` / `longitude` | `41.8781` / `-87.6298` |
| `mlsId` | `"mred00000000"` |
| `photos` | `"0-24:0"` (photo count descriptor) |
| `listingTags` | `["Hardwood flooring"]` |
| `listingRemarks` | `"Bright corner unit with lake views and an updated kitchen."` |
| `listingAgent` | `"Alex Example"` (when detail enrichment is on) |
| `brokerName` | `"Example Realty Group"` (when detail enrichment is on) |
| `scrapedAt` | `"2025-01-01T00:00:00+00:00"` |

With detail enrichment on, each record also carries `mainHouseInfo` (full street address plus canonical URL), `schools` (nearby schools with ratings, grade ranges and distance), `amenities` (structured group/value pairs), `homeHighlights`, `publicRecords` (APN, tax rate, county), and `redfinEstimate` (amount and label, only on homes where Redfin publishes one). Incremental mode additionally adds `changeType`, `changedFields`, `firstSeenAt` and `lastSeenAt` to every returned row.

### How to Use

1. Pick a **mode**: `search` (by location + filters) or `url` (paste Redfin links).
2. Fill in the fields for that mode: locations, listing type, sort and filters for search, or region/listing links for url mode.
3. Set **Max listings** (and optionally **Max pages**) to control run size and cost, then click **Start**.
4. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Search a city for homes for sale (default):**

```json
{
  "mode": "search",
  "locations": ["Chicago, IL"],
  "listingType": "for_sale",
  "maxItems": 50
}
```

**Filtered search, cheapest first, condos only:**

```json
{
  "mode": "search",
  "locations": ["Austin, TX", "78701"],
  "propertyTypes": ["Condo"],
  "sortBy": "price_low",
  "minPrice": 300000,
  "maxPrice": 700000,
  "minBeds": 2,
  "maxItems": 100
}
```

**Recently sold homes with full detail:**

```json
{
  "mode": "search",
  "locations": ["Brooklyn, NY"],
  "listingType": "sold",
  "fetchDetails": true,
  "maxItems": 40
}
```

**Paste region and listing URLs:**

```json
{
  "mode": "url",
  "urls": [
    "https://www.redfin.com/city/29470/IL/Chicago",
    "https://www.redfin.com/IL/Chicago/123-Example-St-60601/home/12345678"
  ]
}
```

**Listings mode** (`mode` = "Addresses / previous dataset") fetches exact homes from `addresses` and/or the listing URLs of a previous run's dataset (`searchResultsDatasetId`). Each home is one dataset item, charged like any other row, plus the detail-enrichment event when `fetchDetails` is on. Listings mode never produces `EXPIRED` rows. New flat detail fields: `avmSectionPreviewText`, `homeStatusLabel`, `fullStreetAddress`.

**Supported URL shapes (URL mode):**

| Shape | Example |
|---|---|
| City | `https://www.redfin.com/city/29470/IL/Chicago` |
| ZIP code | `https://www.redfin.com/zipcode/60611` |
| County (the id decides the county) | `https://www.redfin.com/county/727/IL/Cook-County` |
| State | `https://www.redfin.com/state/Illinois` |
| Paginated (starts at that page of 40, partial scope) | `https://www.redfin.com/city/29470/IL/Chicago/page-2` |
| Filtered (`min-price`, `max-price`, `min-beds`, `min-baths`, `min-sqft`, `max-sqft` applied; others are skipped with a warning) | `https://www.redfin.com/city/29470/IL/Chicago/filter/max-price=500k,min-beds=2` |
| Recently sold (returns sold homes) | `https://www.redfin.com/city/29470/IL/Chicago/recently-sold` |
| Sold in the last 3 years (filter) | `https://www.redfin.com/city/29470/IL/Chicago/filter/include=sold-3yr` |
| Category: property type | `https://www.redfin.com/city/29470/IL/Chicago/condos` |
| Category: price band | `https://www.redfin.com/city/29470/IL/Chicago/homes-for-sale-under-150000` |
| Neighborhood | `https://www.redfin.com/neighborhood/28211/IL/Chicago/Lincoln-Park` |
| Single listing | `https://www.redfin.com/IL/Chicago/123-Example-St-60601/home/12345678` |

Behaviour change: the `urls` field now defaults to the first seven region examples above (the single-city example stays first), so a run started with URL mode and no edits scrapes all seven. Any URL that is not a supported shape is skipped with a warning in the log. Search mode still resolves plain city names, and stops with an error when nothing matches.

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/redfin-scraper").call(run_input={"mode": "search", "locations": ["Chicago, IL"], "maxItems": 50})
for home in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(home["streetLine"], home["price"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/redfin-scraper').call({ mode: 'search', locations: ['Chicago, IL'], maxItems: 50 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### How Max listings and Max pages interact

**Max listings** (`maxItems`) is the only cap on total results; it defaults to 20 and `0` means unlimited. **Max pages** (`maxPages`) is an optional safety stop on the number of result pages fetched per location (350 listings per page) and does not by itself cap your totals; Max listings still decides when a run stops. Leave Max pages at `0` (the default) for uncapped paging.

#### Detail enrichment and its cost

Turn on **Fetch full listing details** to add the listing agent, brokerage, a valuation label, nearby schools, structured amenities, public and tax records, and the Redfin Estimate. Each listing enriched this way bills a separate detail-enrichment charge in addition to the per-listing result charge; the Pricing tab shows the current rates. This toggle governs search-mode results, pasted region URLs, and a **pasted single listing URL** (a link containing `/home/<id>`), which needs it on (the default) to return anything more than its id and URL. With it off, a pasted listing URL is still billed the per-listing result charge, but the row carries only `id`, `propertyId`, a fallback URL and `scrapedAt`, with no detail fields and no detail-enrichment charge.

#### Paste a single listing URL

A pasted listing URL fetches that home's detail panels directly; it never re-walks the search page the listing came from. That means the record it returns carries the listing agent, brokerage, full address (under `mainHouseInfo`), schools, amenities, public records and the Redfin Estimate when available, but it does **not** carry `price`, `beds`, `baths`, `sqft`, `lotSize`, `yearBuilt`, `streetLine`/`city`/`state`/`zip`, `latitude`/`longitude`, `photos`, `keyFacts`, `listingTags` or `listingRemarks` - those only come from a search-result card. To get the full field set for one specific property, search by its address or ZIP instead of pasting its listing URL; paste the URL only when you already have the fields above from elsewhere and just need the detail-only data.

#### Resume and incremental mode

- **Resume** (`resumeFromRunId`) continues one interrupted run: paste its run or dataset ID and the actor skips every listing already collected there, so you don't pay twice.
- **Incremental mode** (`incrementalMode`) is for scheduled runs over the same search or URL list. Each listing is classified `NEW`, `UPDATED` (with `changedFields`), `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on), or `EXPIRED` (only after a run that completely covered every selected location/URL, and only with `emitExpired` on). Counters that tick on their own (days on market, time on Redfin, the scrape timestamp) are ignored when deciding whether a listing changed, so an unchanged listing never looks "updated" just because a day passed. Every other scraped field is compared, and the fields stay in the output either way. Use `ignoreFieldsForChanges` to exclude more noisy fields (a name matches at any depth; a dotted path such as `schools.rating` targets one nested spot). Pasted `/page-N` and `/filter/...` URLs are treated as partial scopes and never produce `EXPIRED` rows.
- **`REAPPEARED`** is not something that happens "by default": it can only be reported for a listing this actor has already tombstoned `EXPIRED` in an earlier run (which itself required a prior complete, uncapped, non-resumed scan with `emitExpired` on). A listing dropping out of one run and coming back in a later one, without ever having been marked `EXPIRED` first, is reported as `NEW` or `UPDATED` like any other change, not `REAPPEARED`.
- `emitExpired` only fires after a run that resolved every given location/URL, was not truncated by Max listings or Max pages, was not a resumed or checkpoint-continued run, and hit no rejected or failed requests. If any of that is not true, expiry reporting is skipped for that run (with a log line explaining why) and previously tracked listings stay as they were.
- **One-time fresh baseline for ZIP scopes.** An incremental task that targets a ZIP code (a pasted `/zipcode/<zip>` URL or a bare 5-digit location) starts a fresh baseline on its first run after the ZIP region fix, so it reports that ZIP's listings as `NEW` instead of marking the previously tracked rows `EXPIRED`. If you set a manual `stateKey` on such a task, switch to a new State key once for the same reason. City, county, neighborhood and state tasks keep their baselines.
- Leave `stateKey` empty unless you want to control the baseline yourself: it is otherwise derived from your search settings, so changing a filter starts a separate baseline instead of reporting your whole result set as new.

### Send results into your apps (MCP connectors)

Optionally pipe each run's results into the apps you already use through Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each listing, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify, Settings, Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where records should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (location + filters) or `url` (paste Redfin links). |
| `locations` | array | `["Chicago, IL"]` | Search mode only. Cities, ZIPs, neighborhoods or counties, e.g. "Chicago, IL". |
| `listingType` | string | `for_sale` | `for_sale` or `sold` (last 3 years). |
| `sortBy` | string | `recommended` | `recommended`, `newest`, `price_low`, `price_high`, `sqft`, `lot_size`, or `price_per_sqft`. |
| `propertyTypes` | array | `[]` (all types) | Keep only these property types (House, Condo, Townhouse, Multi-Family, Land, Manufactured). |
| `minPrice` | integer | no default (optional) | Only listings priced at or above this amount (search mode). |
| `maxPrice` | integer | no default (optional) | Only listings priced at or below this amount (search mode). |
| `minBeds` | integer | no default (optional) | Only listings with at least this many bedrooms (search mode). |
| `minBaths` | integer | no default (optional) | Only listings with at least this many bathrooms (search mode). |
| `minSqft` | integer | no default (optional) | Only listings with at least this much interior area (search mode). |
| `urls` | array | one example of each supported shape | URL mode only. Redfin region or listing URLs; run-level filter fields are ignored (facets in a pasted `/filter/...` URL are applied). Running URL mode with this field untouched now scrapes the seven example URLs below instead of a single city. |
| `fetchDetails` | boolean | `true` | Fetch each listing's detail panels for the agent, brokerage, valuation label, schools, amenities and public records. See "Detail enrichment and its cost" above for how this interacts with pasted listing URLs. |
| `maxItems` | integer | `20` | The only cap on total results. Set `0` for unlimited. |
| `maxPages` | integer | `0` (unlimited) | Optional safety stop on result pages per location (350 listings per page). Does not cap totals; Max listings still decides. |
| `resumeFromRunId` | string | no default (empty) | Optional. ID of a previous run or dataset of this actor; listings already there are skipped. |
| `addresses` | array | none | Listings mode only. Street addresses; each is matched to its Redfin listing, and an address with no match is logged as an error (the run fails if nothing matches). |
| `searchResultsDatasetId` | string | none | Listings mode only. Dataset ID of a previous run; every listing URL in it is fetched again. |
| `incrementalMode` | boolean | `false` | Track this exact search or URL list across scheduled runs and classify every listing NEW / UPDATED / UNCHANGED / REAPPEARED / EXPIRED. |
| `stateKey` | string | no default (auto-derived) | Optional name for the saved baseline. Leave empty and one is derived from your search settings. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) listings that did not change since the last run. |
| `emitExpired` | boolean | `false` | Also return (and bill) a row for listings tracked before that are gone this run, once a complete scan proves it. |
| `ignoreFieldsForChanges` | array | `[]` | Field names or dotted paths excluded from UPDATED detection (incremental mode only). |
| `proxy` | object | Apify Proxy | Connection settings. Region pages, pasted URLs and detail enrichment need a residential connection (see Plan Requirement). |
| `maxResidentialMB` | integer | `0` (unlimited) | Cap residential traffic in MB; the run auto-downgrades to the lower-cost tier after the cap. |
| `mcpConnectors` | array | no default (empty) | Optional. Pipe results into Notion, Linear, Airtable, or Apify via MCP. |
| `notionParentPageUrl` | string | no default (empty) | Optional. Parent page for a selected Notion connector. |
| `maxNotifyListings` | integer | `50` | Optional. Cap how many listings are pushed to the connectors. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live listing.

```json
{
  "id": 12345678,
  "propertyId": 12345678,
  "listingId": 200000000,
  "url": "https://www.redfin.com/IL/Chicago/123-Example-St-60601/home/12345678",
  "mlsId": "mred00000000",
  "mlsStatus": "Active",
  "status": "Active",
  "price": 575000,
  "priceType": "List price",
  "pricePerSqFt": 311,
  "hoaDues": 250,
  "beds": 3,
  "baths": 2.5,
  "bathFull": 2,
  "bathPartial": 1,
  "sqft": 1850,
  "lotSize": 4200,
  "stories": 2,
  "yearBuilt": 1998,
  "streetLine": "123 Example St",
  "city": "Chicago",
  "state": "IL",
  "zip": "60601",
  "latitude": 41.8781,
  "longitude": -87.6298,
  "propertyTypeLabel": "Condo/Co-op",
  "daysOnMarket": 12,
  "photos": "0-24:0",
  "hasVirtualTour": false,
  "keyFacts": [{ "description": "Large windows", "rank": 0 }],
  "listingTags": ["Hardwood flooring"],
  "listingRemarks": "Bright corner unit with lake views and an updated kitchen.",
  "listingAgent": "Alex Example",
  "brokerName": "Example Realty Group",
  "mainHouseInfo": {
    "fullStreetAddress": "123 Example St, Chicago, IL 60601",
    "url": "https://www.redfin.com/IL/Chicago/123-Example-St-60601/home/12345678"
  },
  "schools": [{ "name": "Example Elementary", "rating": 8, "distanceMiles": 0.4 }],
  "scrapedAt": "2025-01-01T00:00:00+00:00"
}
```

#### Output views

The dataset has three views in the Console: **Listings overview** (price, beds, baths, size, status, address), **Listing detail** (agent, brokerage, schools, amenities, public records, Redfin Estimate) and **Changes (incremental)** (`changeType`, `changedFields`, first and last seen).

### Plan Requirement

**Residential access is needed for part of the data.** Redfin's region pages and listing detail pages sit behind a bot-verification wall that datacenter connections do not pass (they get a verification page or an empty answer). The bulk search feed answers on the lower-cost datacenter tier, but region pages, pasted URLs and detail enrichment need a residential connection, and results on those pages can be intermittent, so the actor retries on fresh connections inside a per-request time limit. Apify free plans do not include residential proxies; on a free plan the actor continues on a backup connection and some pages may still be unavailable. For full coverage use a paid Apify plan or supply your own proxy URLs.

What you will see in the run log and the failure message when access is limited:

- Warning when the selected connection is unavailable: "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable."
- Notice when pages were served over the backup: "Served over the backup connection (the default connection was unavailable)."
- Failure when the proxy connection is refused: "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required."
- Failure when residential is unavailable and the backup also fails: "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Redfin pages."
- Failure when every request failed: "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."

A blocked run fails loudly with one of these messages rather than finishing with an empty dataset. Use **Residential traffic budget (MB)** to cap residential traffic.

#### Billing

Three pay-per-event charges apply, each shown on the **Pricing** tab:

- **Listing result**: once per dataset row returned.
- **Detail enrichment**: once per listing that received full detail treatment (only when **Fetch full listing details** is on).
- **Actor Start**: once per GB of run memory, minimum one, when the run starts.

Rows are charged when they are delivered. Unchanged listings in incremental mode are not returned or charged unless **Emit unchanged rows** is on. Use **Max listings** to cap a run.

### FAQ

#### How much does it cost?

You pay per listing returned, plus a separate detail-enrichment charge for any listing that was given full detail treatment, which includes a pasted single-listing URL only when Fetch full listing details is on (the default). The **Pricing** tab shows the current rates. Use **Max listings** to cap the cost of any run.

#### Is it legal to scrape Redfin?

This actor collects only publicly available listing data. You are responsible for how you use it: follow Redfin's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Facts like price, beds and square footage are generally not protected, but photos, remarks and agent/brokerage details may be subject to third-party rights.

#### Can I get only new or changed listings on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new and updated listings, and unchanged ones are not billed unless you turn on **Emit unchanged rows**.

#### Why does a pasted listing URL return fewer fields than a search result?

Because fetching a single listing's detail panels is the only way to identify it directly, a pasted listing URL needs Fetch full listing details on (the default) to return anything beyond its id and URL; with it on, you get the detail-only fields (agent, brokerage, address, schools, amenities, public records, Redfin Estimate) but never the search-card fields (price, beds, baths, sqft, lot size, year built, city/state/zip, coordinates, photos, tags, remarks). Search by the address or ZIP instead if you need the full field set for one property.

#### Why did my run fail instead of returning an empty dataset?

If every request to Redfin was rejected, the run stops with a clear message so "no listings for this search" is never confused with "nothing could be read". Retry in a few minutes, or switch to a residential proxy group under Connection. If the site answered but genuinely has no matching listings, the run still succeeds and reports "No listings found" without billing anything for it.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more real estate data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🧩 <a href="https://apify.com/abotapi/zillow-scraper"><b>Zillow Scraper</b></a><br>Scrape zillow.com properties for sale, for rent and recently sold across the US: price...</td><td>🧩 <a href="https://apify.com/abotapi/homes-com-scraper"><b>Homes.com Scraper</b></a><br>Scrape US property records from Homes.com. Pick a city, state or ZIP and a channel (for...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/realtor-com-scraper"><b>Realtor.com</b></a><br>Extract property listings from realtor.com. Get comprehensive data, including prices...</td><td>🏠 <a href="https://apify.com/abotapi/trulia-scraper"><b>Trulia Scraper</b></a><br>Scrape Trulia.com property listings by location, filters, or URL. Extract prices, beds...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/crexi-commercial-listings-scraper"><b>Crexi Scraper</b></a><br>Scrape crexi.com commercial listings: asking price, cap rate, square footage, units, year...</td><td>🏠 <a href="https://apify.com/abotapi/newhomesource-scraper"><b>NewHomeSource</b></a><br>Scrape new homes, floor plans, builders and communities from NewHomeSource.com. Search by...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/redfin-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

How to find listings. "Search" finds listings by location with filters. "URL" fetches the exact region or listing URLs you paste (filters are ignored). "Addresses / previous dataset" fetches the exact homes named by street address and/or by a previous run's dataset.

## `locations` (type: `array`):

Places to search, e.g. "Chicago, IL", "Austin, TX", "60611", or "Brooklyn, NY". Each location is resolved and searched independently.

## `listingType` (type: `string`):

Which listings to return: homes currently for sale, or homes sold in the last 3 years.

## `sortBy` (type: `string`):

Order results are returned in.

## `propertyTypes` (type: `array`):

Keep only listings matching these property types (e.g. House, Condo, Townhouse, Multi-Family, Land, Manufactured). Leave empty for all types.

## `minPrice` (type: `integer`):

Only listings priced at or above this amount.

## `maxPrice` (type: `integer`):

Only listings priced at or below this amount.

## `minBeds` (type: `integer`):

Only listings with at least this many bedrooms.

## `minBaths` (type: `integer`):

Only listings with at least this many bathrooms.

## `minSqft` (type: `integer`):

Only listings with at least this much interior area (sq ft).

## `urls` (type: `array`):

Redfin URLs. Supported: /city, /zipcode, /county, /state, /neighborhood region pages (also with /page-N, /filter/max-price=500k,min-beds=2, /filter/include=sold-3yr, /recently-sold, and category pages such as /condos and /homes-for-sale-under-150000) and listing URLs (.../home/12345678). Pasted /page-N, /filter/ and /recently-sold parts are applied; run-level filter fields are ignored. Unsupported URLs are skipped with a warning. Used only when Mode is "URL".

## `addresses` (type: `array`):

Street addresses, e.g. "61 Oak Point Dr, Holiday Island, AR 72631". Each is matched to its Redfin listing; an address with no match is reported as an error. Used only when Mode is "Addresses / previous dataset".

## `searchResultsDatasetId` (type: `string`):

Dataset ID of a previous run of this actor; every listing URL in it is fetched again with full details. Used only when Mode is "Addresses / previous dataset".

## `fetchDetails` (type: `boolean`):

Open each listing's detail panels for the address section, valuation label, and the listing agent and brokerage. Disable for a faster, lower-cost run with the summary fields only.

## `maxItems` (type: `integer`):

The cap on how many listings to return in total. This is THE cap and the only finite limit applied by default (20). Set 0 for unlimited.

## `maxPages` (type: `integer`):

Optional safety stop on the number of result pages per location (350 listings per page). This does NOT cap your total results and defers to "Max listings". Defaults to unlimited (empty or 0): paging is uncapped and "Max listings" decides when to stop. Only set a value if you specifically want to limit paging depth.

## `proxy` (type: `object`):

Connection settings. Region pages, pasted URLs and detail enrichment need a residential connection; the bulk search feed works on datacenter.

## `maxResidentialMB` (type: `integer`):

When using a residential proxy group, cap residential traffic at this many MB; after the cap the run auto-downgrades to the lower-cost tier (identical results). 0 = unlimited.

## `resumeFromRunId` (type: `string`):

Optional. ID of a previous run of this actor (or a dataset ID). Listings already in that dataset are skipped, so this run returns only NEW listings. Combine both runs' datasets for the full set. Max listings then counts only the new listings. One-off continuation of a SPECIFIC run, for ongoing scheduled monitoring of the same search use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

For a search you run on a schedule (e.g. daily/weekly): remembers what this exact search returned last time and classifies every listing as NEW / UPDATED / UNCHANGED / REAPPEARED / EXPIRED, so you can act on only what changed. A listing is UPDATED when any scraped field changes (price, status, remarks, agent, schools, photos and so on); scrapedAt, days on market and time on Redfin are excluded because they change on their own, and you can exclude more with Ignore fields below. Off by default so existing scheduled runs are unaffected. Separate from Resume above, which continues one specific interrupted run. Uses State key below to isolate different searches; see Emit unchanged rows / Emit expired rows to control which of those get pushed (both bill extra rows when on).

## `stateKey` (type: `string`):

Names the saved baseline Incremental mode compares against. Leave empty to auto-derive one from mode/locations/urls/listingType/sortBy/propertyTypes/price/beds/baths/sqft/fetchDetails, two differently-configured searches never share a baseline even with this left empty. Set your own text only if you want to explicitly reuse or separate baselines yourself. Ignored when Incremental mode is off.

## `emitUnchanged` (type: `boolean`):

When Incremental mode is on: also push rows classified UNCHANGED (identical to last run), not just NEW/UPDATED/REAPPEARED/EXPIRED. Off by default to save you from paying for rows that didn't change. Turning this on bills the per-result event (and the detail-enrichment surcharge, when fetched) for every unchanged row too.

## `emitExpired` (type: `boolean`):

When Incremental mode is on: push a synthetic EXPIRED row for listings tracked in a previous run that no longer appear in this one. Only fires after a run that completely covered every selected location/URL: if any location could not be looked up, any request was rejected, part of a listing could not be read, or a cap or resume truncated the run, expiry reporting is skipped for that run (with a log line saying which) because a partial scan can't tell 'gone' apart from 'not reached yet'. Off by default; turning it on bills the per-result event for each EXPIRED row emitted. EXPIRED rows count against Max listings like any other row; if the cap is reached, the remaining gone listings stay tracked and are reported on a later run rather than being emitted uncapped.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Field names to exclude from UPDATED change detection, in addition to the built-in handling (scrapedAt, days on market and time on Redfin already never trigger a UPDATED: they tick on their own). Matches the name at any depth inside a record; use a dotted path such as schools.rating to ignore only that nested spot. The fields stay in the output either way.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify, Settings, Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title plus key fields), not the full JSON; the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

Optional. Parent page ID/URL for a selected Notion connector.

## `maxNotifyListings` (type: `integer`):

Optional. Cap how many listings are pushed to the connectors.

## Actor input object example

```json
{
  "mode": "search",
  "locations": [
    "Chicago, IL"
  ],
  "listingType": "for_sale",
  "sortBy": "recommended",
  "propertyTypes": [],
  "urls": [
    "https://www.redfin.com/city/29470/IL/Chicago",
    "https://www.redfin.com/zipcode/60611",
    "https://www.redfin.com/county/727/IL/Cook-County",
    "https://www.redfin.com/state/Illinois",
    "https://www.redfin.com/city/29470/IL/Chicago/page-2",
    "https://www.redfin.com/city/29470/IL/Chicago/filter/max-price=500k,min-beds=2",
    "https://www.redfin.com/city/29470/IL/Chicago/filter/include=sold-3yr",
    "https://www.redfin.com/city/29470/IL/Chicago/condos",
    "https://www.redfin.com/city/29470/IL/Chicago/homes-for-sale-under-150000",
    "https://www.redfin.com/neighborhood/28211/IL/Chicago/Lincoln-Park"
  ],
  "fetchDetails": true,
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true
  },
  "maxResidentialMB": 0,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `changes` (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 = {
    "mode": "search",
    "locations": [
        "Chicago, IL"
    ],
    "urls": [
        "https://www.redfin.com/city/29470/IL/Chicago",
        "https://www.redfin.com/zipcode/60611",
        "https://www.redfin.com/county/727/IL/Cook-County",
        "https://www.redfin.com/state/Illinois",
        "https://www.redfin.com/city/29470/IL/Chicago/page-2",
        "https://www.redfin.com/city/29470/IL/Chicago/filter/max-price=500k,min-beds=2",
        "https://www.redfin.com/city/29470/IL/Chicago/filter/include=sold-3yr",
        "https://www.redfin.com/city/29470/IL/Chicago/condos",
        "https://www.redfin.com/city/29470/IL/Chicago/homes-for-sale-under-150000",
        "https://www.redfin.com/neighborhood/28211/IL/Chicago/Lincoln-Park"
    ],
    "proxy": {
        "useApifyProxy": true
    },
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/redfin-scraper").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 = {
    "mode": "search",
    "locations": ["Chicago, IL"],
    "urls": [
        "https://www.redfin.com/city/29470/IL/Chicago",
        "https://www.redfin.com/zipcode/60611",
        "https://www.redfin.com/county/727/IL/Cook-County",
        "https://www.redfin.com/state/Illinois",
        "https://www.redfin.com/city/29470/IL/Chicago/page-2",
        "https://www.redfin.com/city/29470/IL/Chicago/filter/max-price=500k,min-beds=2",
        "https://www.redfin.com/city/29470/IL/Chicago/filter/include=sold-3yr",
        "https://www.redfin.com/city/29470/IL/Chicago/condos",
        "https://www.redfin.com/city/29470/IL/Chicago/homes-for-sale-under-150000",
        "https://www.redfin.com/neighborhood/28211/IL/Chicago/Lincoln-Park",
    ],
    "proxy": { "useApifyProxy": True },
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/redfin-scraper").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 '{
  "mode": "search",
  "locations": [
    "Chicago, IL"
  ],
  "urls": [
    "https://www.redfin.com/city/29470/IL/Chicago",
    "https://www.redfin.com/zipcode/60611",
    "https://www.redfin.com/county/727/IL/Cook-County",
    "https://www.redfin.com/state/Illinois",
    "https://www.redfin.com/city/29470/IL/Chicago/page-2",
    "https://www.redfin.com/city/29470/IL/Chicago/filter/max-price=500k,min-beds=2",
    "https://www.redfin.com/city/29470/IL/Chicago/filter/include=sold-3yr",
    "https://www.redfin.com/city/29470/IL/Chicago/condos",
    "https://www.redfin.com/city/29470/IL/Chicago/homes-for-sale-under-150000",
    "https://www.redfin.com/neighborhood/28211/IL/Chicago/Lincoln-Park"
  ],
  "proxy": {
    "useApifyProxy": true
  },
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/redfin-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/redfin-scraper"
        }
    }
}
```

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/eY1KgSKUOXmjAAk0g/builds/S79K8igbkd8ZynICZ/openapi.json
