# 🏠 Zillow Scraper — Listings, Price History & Contact (`inexhaustible_glass/zillow-listings-scraper`) Actor

Scrape live Zillow listings: price, beds/baths, sqft, address, days on market, tax value, zestimate. Optional detail mode adds description, price history, MLS info and contact phone. No official API — reads Zillow's own structured data via a stealth browser. For sale, rent, or recently sold.

- **URL**: https://apify.com/inexhaustible_glass/zillow-listings-scraper.md
- **Developed by:** [Hitman studio](https://apify.com/inexhaustible_glass) (community)
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 listings

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## 🏠 Zillow Listings Scraper — Price, Beds/Baths, History & Contact

Live **Zillow** listings — for sale, for rent, or recently sold — in any US city or ZIP.
Zillow retired its public API years ago, so this reads the same structured JSON Zillow's
own site renders from (`__NEXT_DATA__` / `gdpClientCache`), not scraped HTML text, via a
stealth browser. Every field below was verified against live zillow.com data before this
was built — nothing here is guessed.

> Keywords: zillow scraper · zillow api · zillow api alternative · real estate scraper ·
> real estate api · real estate data · property listings scraper · home listings data ·
> house price data · house listings · housing market data · housing data api · MLS data ·
> MLS scraper · property data · property data api · property investor leads · real estate
> leads · real estate lead generation · realtor contact data · real estate agent contact ·
> brokerage contact data · zestimate scraper · zillow zestimate api · price history scraper ·
> for sale by owner data · FSBO data · rental listings scraper · zillow rentals ·
> recently sold homes data · comps data · comparable sales scraper · zillow alternative api ·
> real estate investor tools · wholesaling real estate data · house flipping data ·
> property management leads · real estate market analysis · zip code real estate data ·
> days on market data · home value estimator api · real estate CRM data source ·
> real estate agent leads scraper · buyer agent data · seller lead data · zillow scraping tool ·
> zillow data extraction · US real estate data · nationwide property data · austin real estate
> data · miami real estate data · real estate data scraper 2026.

### ✅ What you get (search mode — fast, default)

One row per listing: price, status, beds, baths, sqft, full address + lat/long, home
type, lot size, days on Zillow, tax-assessed value, price-reduction note, Zillow link.

### 📞 What you get with "Include contact/description detail" (slower, optional)

Per listing, an extra page load adds: **full description text, complete price history**
(every sale/listing price change on record, not just current), MLS ID/name, listing
status + last-updated timestamp, **the listing's contact phone number**, brokerage name,
Zestimate/Rent Zestimate, price-per-sqft, HOA fee, parking details, last sold price/date.

**Honest limits on the contact field:** Zillow does not publish an individual agent's
personal e-mail anywhere on the page — only a phone number, which is usually a
brokerage call-routing line (sometimes the listing agent's own line). No email field
exists in Zillow's own data, so none is fabricated here. The number is also
**format-validated and labeled**, not passed through blindly:

- `contact_phone` — normalized `(XXX) XXX-XXXX` format
- `contact_phone_valid_format` — `true` only if it parses as a real 10-digit US number
- `contact_phone_type` — `"toll_free_call_tracking"` (800/888/877/866/855/844/833 prefix —
  this is Zillow's own Premier-Agent lead-routing line, NOT the listed agent's direct
  number, most of the time) vs `"direct_or_local"` (an actual area-code number, more
  likely to reach the agent/brokerage directly). Reported, never silently upgraded to
  "verified agent contact" — this codebase checks shape, not liveness (that needs an
  actual call, out of scope here).

Detail pages are bot-protected (PerimeterX) and get blocked on roughly **half of
individual attempts** (verified live, repeatedly). The real cause: a fresh browser
context alone resets cookies/fingerprint but NOT the egress IP, and PerimeterX's block is
IP-reputation/rate driven — so a handful of listings in a batch could still fail even
after 4 retries from the same machine IP (seen live: 8/10 on a real run). Fixed with three stacked layers, all tried per attempt: (1) a cheap crawler-UA +
TLS-impersonation HTTP pre-check (no browser — the same "many sites serve a crawler a
challenge-free SEO page" technique used in Aman's own
[`cloudreveal`](https://github.com/Amanbhaw/cloudreveal) research project, ported here
rather than pulled in as a dependency to keep this actor's image small); (2) if that
fails, a real browser routed through a **brand-new Apify Proxy session (= a real new
exit IP) every attempt**; (3) attempts **alternate between two independently-built
stealth-browser stacks** (Camoufox / CloakBrowser) — a block a site learns against one
fingerprint doesn't sink every attempt, since the two projects implement their
anti-detection patches completely separately. Randomized backoff between tries throughout,
never a fixed metronome delay. An occasional listing can still come back without detail
fields after all attempts — it's marked as such, never left silently blank-looking.

### 🌐 Optional: brokerage website + general contact lookup

Zillow's own listing data has never included an individual agent's name, profile link,
or e-mail — verified live, repeatedly, across real listings (only `contact_phone` +
`brokerage_name` ever appear). Turn on **"Also look up brokerage website + general
email"** to add one extra, clearly-separate step: search for the listing's brokerage's
own official website and read its published contact e-mail/phone off it — real, found
data, never a guessed `name.pattern@domain`. This is the **brokerage's general office
contact, not the specific listing agent** — output as `brokerage_website` /
`brokerage_general_email` / `brokerage_general_phone`, labeled as such, cached per
brokerage per run (most listings in one city share an office, so this is one extra
lookup per unique brokerage, not per listing).

**Honest limit:** this depends on a live web search (DuckDuckGo), which — like every
search engine — can itself challenge/CAPTCHA automated traffic independent of anything
this actor does; confirmed live during development. It's opt-in and best-effort: when
blocked, the three fields simply come back `null` rather than guessed. For reliable
email enrichment instead, feed this actor's `brokerage_name` output into a dedicated
company-name → email finder (e.g. Aman's own Lead Enricher Pro actor), which resolves
contacts via GitHub/SPF/DKIM rather than search-engine scraping.

### 📋 Every output field

**Always included (search mode):**
| Field | What it is |
|---|---|
| `zpid` | Zillow's unique property ID |
| `status` | Listing status text, e.g. "Active", "House for sale" |
| `home_type` | `SINGLE_FAMILY`, `CONDO`, `TOWNHOUSE`, `MULTI_FAMILY`, `MANUFACTURED`, `LOT`, `APARTMENT` |
| `price` | Current price, raw number (USD) |
| `price_display` | Formatted price, e.g. `"$739,000"` |
| `price_reduction` | Recent price drop note, e.g. `"$9,000 (Sep 16)"`, or `null` |
| `address` | Full street address |
| `street` / `city` / `state` / `zip` | Address parts |
| `latitude` / `longitude` | Coordinates |
| `beds` / `baths` | Bedroom/bathroom count |
| `living_area_sqft` | Interior square footage |
| `lot_size` / `lot_size_unit` | Lot size + unit (`acres` or `sqft`) |
| `days_on_zillow` | Days the listing has been live |
| `tax_assessed_value` | Most recent county tax-assessed value (USD) |
| `is_fsba` | `true` if "for sale by agent" listing sub-type |
| `is_showcase` | `true` if a Zillow Showcase (premium) listing |
| `image_url` | Main listing photo |
| `detail_url` | Link to the listing's own Zillow page |

**With `includeDetails: true` (adds these):**
| Field | What it is |
|---|---|
| `description` | Full listing description text |
| `mls_id` / `mls_name` | MLS record ID and the MLS board name |
| `listing_status` | MLS-sourced status (e.g. `"Active"`) |
| `listing_last_updated` | When the MLS record was last updated |
| `contact_phone` | Normalized `(XXX) XXX-XXXX` contact number (see honest limits above) |
| `contact_phone_valid_format` | `true` only if it parses as a real 10-digit US number |
| `contact_phone_type` | `"toll_free_call_tracking"` or `"direct_or_local"` |
| `brokerage_name` | Listing brokerage's name |
| `zestimate` / `rent_zestimate` | Zillow's own value/rent estimate, where published |
| `price_per_sqft` | Price ÷ living area |
| `hoa_fee_total` | HOA fee as published, e.g. `"$300 monthly"` |
| `parking_features` | List of parking features, e.g. `["Attached", "Garage"]` |
| `has_garage` | `true`/`false` |
| `price_history` | Full list of `{date, price}` — every recorded price change, going back to the property's earliest Zillow record |
| `last_sold_price` / `date_sold` | Most recent recorded sale, where available |
| `mortgage_monthly_hoa` | Monthly HOA used in Zillow's mortgage calculator |

**With `includeBrokerageContact: true` (adds these, needs `includeDetails`):**
| Field | What it is |
|---|---|
| `brokerage_website` | The brokerage's own official site, found via live search |
| `brokerage_general_email` | General contact e-mail found on that site (`null` if none published or lookup blocked) |
| `brokerage_general_phone` | General contact phone found on that site (`null` if none published or lookup blocked) |

### 🔧 Input

| Field | What it does |
|---|---|
| **location** | City/state or ZIP, e.g. `austin-tx`, `78704`, `miami-fl` |
| **listingType** | `for_sale`, `for_rent`, or `recently_sold` |
| **minPrice / maxPrice** | Price range filter |
| **minBeds / minBaths** | Minimum bed/bath count |
| **homeTypes** | Restrict to specific home types |
| **includeDetails** | Fetch description/price-history/contact per listing (slower) |
| **includeBrokerageContact** | Also look up brokerage website + general email (needs includeDetails) |
| **maxResults** | How many listings to pull |
| **maxPages** | Safety cap on search pages (~40 listings/page before filtering) |

#### Example input

```json
{ "location": "austin-tx", "listingType": "for_sale", "minPrice": 300000, "maxPrice": 800000, "maxResults": 100 }
```

### 💡 Use cases

- **Investors / wholesalers** — scan a market by price band, home type, days on market
- **Agents** — competitive-listing research, price-history due diligence
- **Lead generation** — brokerage contact numbers attached to active listings
- **Market analysts** — price trends, days-on-market distributions by city

### 🤖 Use via MCP / AI agents

This actor works out of the box with Apify's official `actors-mcp-server` — connect your
AI agent to Apify's MCP gateway and call this actor as a tool, no extra setup needed on
this actor's side.

### 💰 Cost

Pay-per-result, no subscription.
**Tip:** leave `includeDetails`/`includeBrokerageContact` off for a fast first pass across a market, then turn them on only for your shortlist.

### 🔌 Integrations

- **Zapier / Make / n8n** — alert an investor the moment a new listing matches their search.
- **API / MCP** — call this Actor directly via the Apify API, or through Apify's official `actors-mcp-server` for AI agents.

### 👤 About the developer

Built and maintained by an indie developer running a suite of real-estate and public-records Actors (HUDHawk foreclosures, DXBHawk UAE listings). Need another field or market added? Reach out — custom builds are welcome.

### ⚖️ Legal

Reads the same publicly-viewable listing data Zillow serves to any visitor's browser —
no login, no paywall bypass. Use responsibly and don't use contact numbers for unlawful
solicitation (TCPA/do-not-call rules apply to phone outreach same as anywhere else).

# Actor input Schema

## `location` (type: `string`):

City/state or ZIP, Zillow URL-slug format (spaces/commas are converted automatically). Examples: austin-tx, 78704, miami-fl. Leave empty if using Agent directory location below instead.

## `agentLocation` (type: `string`):

City/state slug to search Zillow's real-estate AGENT directory instead of property listings (e.g. austin-tx). Set this to switch the whole run into agent mode — returns agent/team name, rating, review count, sales stats, and (optionally) licensed contact info. Leave empty for normal listings mode.

## `maxAgentResults` (type: `integer`):

How many agent/team cards to pull from the directory page when Agent directory location is set.

## `includeLicenseAndContact` (type: `boolean`):

When Agent directory location is set: visit each agent's own Zillow profile page for their real license number(s) and direct personal email/cell/business phone/business address — richer than the brokerage-level contact in listings mode. Each enriched agent is a billed event. Off by default since it's slower (one extra bot-protected page load per agent, with automatic retries).

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

For sale, for rent, or recently sold.

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

Only listings priced at or above this.

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

Only listings priced at or below this.

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

Only listings with at least this many bedrooms.

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

Only listings with at least this many bathrooms.

## `homeTypes` (type: `array`):

Only keep these home types. Leave empty for all.

## `includeDetails` (type: `boolean`):

Visit each listing's own page for its full description, price history, MLS info, and the listing's contact phone number (brokerage-routed in most cases — Zillow never exposes a personal agent e-mail, only a phone number). Detail pages are bot-protected; each attempt is retried with a fresh IP + browser session automatically, which makes this noticeably slower than the base scrape. On by default since this is the data most users actually want — turn it off for a faster, lighter scrape.

## `includeBrokerageContact` (type: `boolean`):

Zillow's own listing data never includes an agent's name or e-mail — verified live, repeatedly. This adds ONE extra step: search for the listing's brokerage's own official website and read its published general contact e-mail/phone off it (real, found data — never a guessed name@domain). This is the brokerage's general office contact, NOT the specific listing agent — labeled as such in the output. Cached per brokerage per run, so it only costs one extra lookup per unique brokerage in your results, not per listing.

## `maxResults` (type: `integer`):

How many listings to pull.

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

Safety cap on how many Zillow search-result pages to page through (~40 listings/page before filtering).

## Actor input object example

```json
{
  "location": "austin-tx",
  "agentLocation": "austin-tx",
  "maxAgentResults": 10,
  "includeLicenseAndContact": false,
  "listingType": "for_sale",
  "includeDetails": true,
  "includeBrokerageContact": false,
  "maxResults": 25,
  "maxPages": 10
}
```

# Actor output Schema

## `listings` (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 = {
    "location": "austin-tx",
    "maxAgentResults": 10,
    "includeLicenseAndContact": false,
    "listingType": "for_sale",
    "includeDetails": true,
    "includeBrokerageContact": false,
    "maxResults": 25,
    "maxPages": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("inexhaustible_glass/zillow-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 = {
    "location": "austin-tx",
    "maxAgentResults": 10,
    "includeLicenseAndContact": False,
    "listingType": "for_sale",
    "includeDetails": True,
    "includeBrokerageContact": False,
    "maxResults": 25,
    "maxPages": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("inexhaustible_glass/zillow-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 '{
  "location": "austin-tx",
  "maxAgentResults": 10,
  "includeLicenseAndContact": false,
  "listingType": "for_sale",
  "includeDetails": true,
  "includeBrokerageContact": false,
  "maxResults": 25,
  "maxPages": 10
}' |
apify call inexhaustible_glass/zillow-listings-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,inexhaustible_glass/zillow-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/UfJN7OJrfHdb0a3dB/builds/LQZmZa5wcW63GXCzO/openapi.json
