# Redfin Property Listings Scraper (`devilscrapes/redfin-property-listings-scraper`) Actor

Scrape normalized US Redfin listings — for-sale, sold, and pending — with full MLS/geo field coverage and reliable pagination past Redfin's 350-result cap via price-bucket bisection, deduped by property ID.

- **URL**: https://apify.com/devilscrapes/redfin-property-listings-scraper.md
- **Developed by:** [DevilScrapes](https://apify.com/devilscrapes) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

<div align="center">
  <img src=".actor/icon.svg" width="160" alt="Devil Scrapes mark" />

## Redfin Property Listings Scraper

**💰 $2.52 / 1 000 results**  ·  pay only for results  ·  no credit card to try

*We do the dirty work so your dataset stays clean.* 😈

Scrape normalized US Redfin listings — for-sale, sold, and pending — with full MLS/geo field coverage and reliable pagination past Redfin's 350-result cap via price-bucket bisection, deduped by property ID.

</div>

***

### 🎯 What this scrapes

tri\_angle/redfin-search is the Apify Store's volume leader for Redfin (36,722 runs / 633 users) but sits at 3.59★ on 7 reviews — a durable execution gap, not a fluke. The rest of the field is thinner still: rigelbytes/redfin-scraper (1,008 runs / 122 users, 0★) and lulzasaur/redfin-scraper (898 runs / 47 users, 0★) round out a weak set of alternatives with no meaningful traction to validate reliability at scale. We built this Actor to close the gap on the two failure modes that show up over and over in that 3.59★: (1) reliable pagination past Redfin's hard 350-result-per-call cap, via a price-bucket bisection technique with per-bucket fault isolation, so a dense metro doesn't truncate silently at 350; and (2) full confirmed-field coverage per listing — MLS id, days-on-market, exact geo, HOA presence, remarks/tags, open-house info, photos — not just price/beds/baths. Redfin search pages carry real anti-bot posture; we run every request through Redfin's own stingray/api/gis search endpoint the way a real browser session would, so you get the data without wiring up the retry and rotation logic yourself.

### 🔥 What we handle for you

- 🛡️ **We rotate browser fingerprints** — `curl-cffi` impersonates real Chrome / Firefox / Safari TLS handshakes so Redfin's search endpoint sees a browser, not Python.
- 🌐 **We rotate proxy sessions** through Apify Proxy on every block or geo-mismatch — fresh session ID, fresh exit IP.
- 🔁 **We retry with exponential backoff** on `408 / 429 / 5xx` and network errors — up to 5 attempts per request, `Retry-After` honoured.
- 🧱 **We back off when Redfin rate-limits or geo-splashes us** — a mismatched city/state response gets a session rotation and one retry before we skip it and move on, never silently returning wrong-city data.
- 🧊 **We keep the dataset clean** — Pydantic-validated rows, deduped by `property_id` across price buckets, ISO-8601 timestamps, JSON / CSV / Excel export straight from the Apify Console.
- 💰 **You pay only for results that land** — Pay-Per-Event pricing, no data no charge (only the small `actor-start` warm-up fee).

### 💡 Use cases

- **Investment deal screening** — pull normalized for-sale listings across any Redfin metro, with days-on-market and price-per-sqft ready for comparables analysis.
- **Sold-comp research** — reconstruct recent sold prices for a neighborhood without the 350-listing cap silently truncating dense ZIP codes.
- **iBuyer / PropTech analytics** — feed a normalized MLS-status/geo/HOA schema straight into an acquisition or valuation pipeline.
- **Agent lead-gen** — track pending listings and open-house schedules across target markets.

### ⚙️ How to use it

1. Click **Try for free** at the top of the page.
2. Fill in the input form — most fields have sensible defaults.
3. Click **Start**. Output streams into the run's dataset.
4. Export from **Storage → Dataset** as JSON, CSV, or Excel — or fetch via the API.

### 📥 Input

| Field | Type | Required | Default | Notes |
|---|---|:--:|---|---|
| `searchUrls` | `array` | no | \['https://www.redfin.com/city/17420/CA/San-Francisco'] | Redfin search-page URLs copied from redfin.com (/city/, /zipcode/, /neighborhood/, /county/ shapes). Up to 25. One of… |
| `regionId` | `string` | no | '—' | Direct region\_id override — skips the search-page fetch. Required together with Region type + Market if Search URLs is… |
| `regionType` | `string` | no | '—' | Direct region\_type override. Required together with Region ID + Market if Search URLs is unset. |
| `market` | `string` | no | '—' | Direct market slug override. Required together with Region ID + Region type if Search URLs is unset. |
| `status` | `string` | no | 'for-sale' | for-sale, sold, or pending listings. |
| `maxResultsPerSearch` | `integer` | no | 1000 | Cap on rows per Search URLs / direct-override entry; triggers price-bucket bisection past Redfin's 350-home cap. |
| `priceBucketMinWidthUsd` | `integer` | no | 25000 | Bisection floor — stop splitting a price bucket further once its width would drop below this. |
| `proxyConfiguration` | `object` | no | {'useApifyProxy': True, 'countryCode': 'US'} | Apify Proxy, pinned to the US. Default (datacenter) group only — never RESIDENTIAL (unavailable on the FREE plan). |

#### Example input

```json
{
  "searchUrls": [
    "https://www.redfin.com/city/17420/CA/San-Francisco"
  ],
  "status": "for-sale",
  "maxResultsPerSearch": 8,
  "priceBucketMinWidthUsd": 25000,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "countryCode": "US"
  }
}
```

### 📤 Output

Every row is one dataset item.

| Field | Type | Notes |
|---|---|---|
| `property_id` | `string` | propertyId — dedup key. |
| `listing_id` | `['string', 'null']` | listingId. |
| `mls_id` | `['string', 'null']` | mlsId.value. |
| `mls_status` | `['string', 'null']` | mlsStatus. |
| `search_status` | `['string', 'null']` | searchStatus. |
| `listing_type` | `['string', 'null']` | listingType. |
| `property_type` | `['string', 'null']` | propertyType. |
| `ui_property_type` | `['string', 'null']` | uiPropertyType. |
| `price_usd` | `['number', 'null']` | price.value. |
| `price_per_sqft_usd` | `['number', 'null']` | pricePerSqFt.value. |
| `sqft` | `['number', 'null']` | sqFt.value. |
| `lot_size_sqft` | `['number', 'null']` | lotSize. |
| `beds` | `['number', 'null']` | beds. |
| `baths` | `['number', 'null']` | baths. |
| `hoa_fee_usd` | `['number', 'null']` | hoa (often absent, non-condo). |
| `hoa_frequency_known` | `['boolean', 'null']` | isHoaFrequencyKnown. |
| `year_built` | `['integer', 'null']` | yearBuilt.value. |
| `days_on_market` | `['integer', 'null']` | dom.value. |
| `time_on_redfin_ms` | `['integer', 'null']` | timeOnRedfin.value. |
| `neighborhood` | `['string', 'null']` | location.value. |
| `street_address` | `['string', 'null']` | streetLine.value. |
| `unit_number` | `['string', 'null']` | unitNumber. |
| `city` | `string` | city. |
| `state` | `string` | state. |
| `zip_code` | `['string', 'null']` | postalCode.value. |
| `latitude` | `['number', 'null']` | latLong.value.latitude. |
| `longitude` | `['number', 'null']` | latLong.value.longitude. |
| `num_photos` | `['integer', 'null']` | numPictures. |
| `photo_urls` | `array` | photos.value, best-effort (empty OK). |
| `listing_url` | `string` | https://www.redfin.com + url. |
| `status_ribbons` | `array` | sashes\[] (empty OK). |
| `key_facts` | `array` | keyFacts\[] (empty OK). |
| `listing_tags` | `array` | listingTags\[] (empty OK). |
| `listing_remarks` | `['string', 'null']` | listingRemarks. |
| `is_redfin_listing` | `boolean` | isRedfin. |
| `is_new_construction` | `boolean` | isNewConstruction. |
| `sold_date` | `['string', 'null']` | soldDate, ISO YYYY-MM-DD. |
| `open_house_start` | `['string', 'null']` | openHouseStart. |
| `open_house_end` | `['string', 'null']` | openHouseEnd. |
| `source_search_url` | `string` | Which search\_urls entry yielded this row. |
| `scraped_at` | `string` | ISO-8601 UTC row-creation timestamp. |

#### Example output

```json
{
  "property_id": "12345678",
  "listing_id": "98765432",
  "mls_id": "SF123456",
  "mls_status": "Active",
  "search_status": "Active",
  "listing_type": "for-sale",
  "property_type": "Single Family Residential",
  "ui_property_type": "House",
  "price_usd": 1250000.0,
  "price_per_sqft_usd": 850.5,
  "sqft": 1470.0,
  "lot_size_sqft": 3200.0,
  "beds": 3.0,
  "baths": 2.0,
  "hoa_fee_usd": null,
  "hoa_frequency_known": false,
  "year_built": 1948,
  "days_on_market": 12,
  "time_on_redfin_ms": 1036800000,
  "neighborhood": "Noe Valley",
  "street_address": "123 Example St",
  "unit_number": null,
  "city": "San Francisco",
  "state": "CA",
  "zip_code": "94114",
  "latitude": 37.7502,
  "longitude": -122.4337,
  "num_photos": 32,
  "photo_urls": [
    "https://ssl.cdn-redfin.com/photo/1/bigphoto/example.jpg"
  ],
  "listing_url": "https://www.redfin.com/CA/San-Francisco/123-Example-St-94114/home/12345678",
  "status_ribbons": [
    "COMING SOON"
  ],
  "key_facts": [
    "Central Air"
  ],
  "listing_tags": [],
  "listing_remarks": "Charming Noe Valley home...",
  "is_redfin_listing": false,
  "is_new_construction": false,
  "sold_date": null,
  "open_house_start": null,
  "open_house_end": null,
  "source_search_url": "https://www.redfin.com/city/17420/CA/San-Francisco",
  "scraped_at": "2026-08-12T12:00:00+00:00"
}
```

### 💰 Pricing

Pay-Per-Event — you pay only when these events fire:

| Event | USD | What it is |
|---|---:|---|
| `actor-start` | $0.02 | One-off warm-up charge per run |
| `result-row` | $0.0025 | Per unique dataset item |

Example: 1 000 results at the rates above ≈ **$2.52**. No subscription, no minimum, no card to start — Apify gives every new account $5 of free credit.

### 🚧 Limitations

v1 covers US Redfin search results only — no Canada. Price history, tax/assessment records, and full agent-contact fields are not available in v1: they simply don't ship in the search-page payload this Actor reads, and pulling them means fetching Redfin's property-detail pages, which sit behind a real anti-bot challenge that a plain HTTP client can't clear. Rather than ship a flaky, half-working detail fetch, we scoped v1 to the rich search/listings endpoint only — every field confirmed there ships reliably, every field that isn't ships as null instead of a guess. Detail-page enrichment is a tracked v2 candidate once we've validated a browser-based path against it. Free-text/city-name search isn't supported (Redfin's autocomplete endpoint 403s); paste a search-page URL instead.

### ❓ FAQ

**Do I need a Redfin account or API key?**

No. This Actor scrapes Redfin's own publicly listed search-result data via the search page's embedded API call — no login, no API key.

**How does this avoid Redfin's 350-result cap?**

Every gis call is capped server-side at 350 homes regardless of what's requested. We bisect the price range into buckets (mpt=99 + min\_price/max\_price) and recurse until every bucket returns fewer than 350 homes, deduping by property ID across buckets.

**Can I target a ZIP code or neighborhood instead of a city?**

Yes — paste any /city/, /zipcode/, /neighborhood/, or /county/ search-page URL from redfin.com.

**Does one bad search URL stop the whole run?**

No — each entry is fault-isolated. A geo-mismatch or fetch failure on one entry is logged and skipped; the rest of the run continues.

### 💬 Your feedback

Spotted a bug, hit a weird edge case, or need a new field? Open an
issue on the Actor's **Issues** tab on Apify Console — we ship
fixes weekly and we read every report.

***

<div align="center">

Built by **[Devil Scrapes](https://apify.com/DevilScrapes)** 😈 — a small fleet of
opinionated public-data Actors. Honest pricing, real engineering, zero fine print.

</div>

# Actor input Schema

## `searchUrls` (type: `array`):

Redfin search-page URLs copied from redfin.com (/city/, /zipcode/, /neighborhood/, /county/ shapes). Up to 25. One of Search URLs or (Region ID + Region type + Market) is required.

## `regionId` (type: `string`):

Direct region\_id override — skips the search-page fetch. Required together with Region type + Market if Search URLs is unset.

## `regionType` (type: `string`):

Direct region\_type override. Required together with Region ID + Market if Search URLs is unset.

## `market` (type: `string`):

Direct market slug override. Required together with Region ID + Region type if Search URLs is unset.

## `status` (type: `string`):

for-sale, sold, or pending listings.

## `maxResultsPerSearch` (type: `integer`):

Cap on rows per Search URLs / direct-override entry; triggers price-bucket bisection past Redfin's 350-home cap.

## `priceBucketMinWidthUsd` (type: `integer`):

Bisection floor — stop splitting a price bucket further once its width would drop below this.

## `proxyConfiguration` (type: `object`):

Apify Proxy, pinned to the US. Default (datacenter) group only — must not request the RESIDENTIAL group (unavailable on the FREE plan).

## Actor input object example

```json
{
  "searchUrls": [
    "https://www.redfin.com/city/17420/CA/San-Francisco"
  ],
  "status": "for-sale",
  "maxResultsPerSearch": 20,
  "priceBucketMinWidthUsd": 25000,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "countryCode": "US"
  }
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

All dataset items as JSON.

## `datasetItemsCsv` (type: `string`):

Same data exported to CSV.

## `datasetView` (type: `string`):

Open the run dataset in the Console.

# 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 = {
    "searchUrls": [
        "https://www.redfin.com/city/17420/CA/San-Francisco"
    ],
    "status": "for-sale",
    "maxResultsPerSearch": 20,
    "priceBucketMinWidthUsd": 25000,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "countryCode": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("devilscrapes/redfin-property-listings-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 = {
    "searchUrls": ["https://www.redfin.com/city/17420/CA/San-Francisco"],
    "status": "for-sale",
    "maxResultsPerSearch": 20,
    "priceBucketMinWidthUsd": 25000,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "countryCode": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("devilscrapes/redfin-property-listings-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 '{
  "searchUrls": [
    "https://www.redfin.com/city/17420/CA/San-Francisco"
  ],
  "status": "for-sale",
  "maxResultsPerSearch": 20,
  "priceBucketMinWidthUsd": 25000,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "countryCode": "US"
  }
}' |
apify call devilscrapes/redfin-property-listings-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,devilscrapes/redfin-property-listings-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/jyLccMTFFBMxXSVBI/builds/2F5aKnXqRv8XOPHba/openapi.json
