# Zillow Detail Scraper — Agent Contacts & Zestimate (`thenetaji/zillow-detail-scraper`) Actor

Paste Zillow listing links and take the full record of each home: price, Zestimate, rent estimate, taxes, beds, baths, lot, year built, description, every photo, the RESO facts, and the listing agent's name, email and phone. Name a city instead and the listings are found first.

- **URL**: https://apify.com/thenetaji/zillow-detail-scraper.md
- **Developed by:** [The Netaji](https://apify.com/thenetaji) (community)
- **Categories:** Real estate, Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 listing details

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?

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

## Zillow Detail Scraper

Paste Zillow listing links, get one row per home with everything Zillow holds on it — including the
three things the page itself never shows anybody: the listing agent's email address and telephone
number, and how many people have looked at the listing.

No Zillow account, no session cookie, no key of any kind. You pay per home read, and a link that no
longer resolves is not billed.

### Accepted input

`zpid` is the list of listings, one per line. Paste the links straight out of your browser; a bare
property identifier works too, and the two forms can be mixed in one list. The same home given twice
under two spellings is read once.

```json
{
  "zpid": [
    "https://www.zillow.com/homedetails/2114-S-Trumbull-Ave-Chicago-IL-60623/2146999031_zpid/",
    "https://www.zillow.com/homedetails/2146999031_zpid/",
    "2077663766"
  ],
  "maxItems": 0
}
```

The address half of a Zillow link is decorative — `/homedetails/2146999031_zpid/` and
`/homedetails/any-slug-at-all/2146999031_zpid/` open the same house — so nothing here tries to match
on an address, and a link you assembled from one is not a link to anything in particular.

**Or name a city instead.** Leave `zpid` empty and set `region` to the `san-francisco-ca` in
`https://www.zillow.com/san-francisco-ca/`, or paste the search link itself. A bare state (`ca`) and
a ZIP code (`94103`) are regions too. The listings are found first and then read, so one run gets you
from a city name to full records:

```json
{
  "region": "san-francisco-ca",
  "status": "for_sale",
  "maxItems": 50
}
```

`status` is `for_sale` or `sold`. `startPage` splits a large city across several runs. Both are
ignored when you paste listings, because a list is specific and a city is not. `maxItems` caps the
run; `0` means no cap.

### What a row holds

```json
{
  "zpid": "2146999031",
  "price": 480000,
  "zestimate": 468600,
  "rent_zestimate": 1936,
  "bedrooms": 3,
  "bathrooms": 2,
  "living_area": 1600,
  "year_built": 1911,
  "street_address": "2114 S Trumbull Ave",
  "city": "Chicago",
  "county": "Cook County",
  "agent_name": "Luis Ortiz",
  "agent_phone": "708-990-4079",
  "broker_name": "RE/MAX Partners",
  "mls_name": "MRED as distributed by MLS GRID",
  "page_view_count": 197,
  "favorite_count": 20,
  "days_on_zillow": 23,
  "photo_count": 61
}
```

**Who is selling the home** arrives as `agent_name`, `agent_email`, `agent_phone`, `broker_name`,
`broker_phone`, `mls_id`, `mls_name`, `listing_agreement` and `true_status`. None of it is rendered
on the Zillow page. `agent_email` is null more often than not — a telephone number is stated far more
consistently than an email address. `mls_id` is only unique inside the feed that issued it, so read
it together with `mls_name`. `true_status` is the MLS's own status word before Zillow mapped it onto
its smaller set, and it is not always the same value as `home_status`.

**How much attention the listing is getting** arrives as `page_view_count`, `favorite_count` and
`tour_view_count`. A save is a logged-in action by somebody who means to come back, so its ratio to
the view count behaves as a conversion rate for the individual home. Read all three against
`days_on_zillow`: 197 views over 23 days describes a different listing from 197 views in one.

**The money** is `price`, `currency`, `zestimate`, `rent_zestimate`, `last_sold_price`, `date_sold`,
`price_change` and `price_change_date_display`, with `tax_annual_amount`, `property_tax_rate` and
`monthly_hoa_fee` beside them. A null `monthly_hoa_fee` means the home has no association, which is
a different statement from a fee of zero. The Zestimate is a model output rather than a listed
figure and is stated even for homes that are not for sale, so the gap between it and `price` is
Zillow's opinion rather than a fact about the market.

**The structure** is `bedrooms`, `bathrooms`, `living_area`, `lot_size`, `lot_area_value`,
`lot_area_units`, `year_built`, `home_type`, `property_type`, `listing_type` and
`contingent_listing_type`. `lot_size` and `lot_area_value` describe the same lot in different units;
`lot_area_units` names the second one.

**The location** is `street_address`, `city`, `state`, `zipcode`, `country`, `county`,
`neighborhood`, `community`, `subdivision`, `latitude`, `longitude` and `time_zone`. `county` is
stated here and nowhere else on the row, and it is the join key for public tax and deed records.
`neighborhood` is frequently null, meaning Zillow names none rather than that the home has none.

`photos` holds every photograph at the widest size available, deduplicated — 61 on the sampled
property. `photo_count` counts what actually arrived rather than repeating a figure the page printed.

`reso_facts` is republished whole under Zillow's own key names, such as `bathroomsThreeQuarter` and
`garageParkingCapacity`, and it is the one field on the row that does not follow this Actor's
naming. RESO is the real-estate industry's own interchange standard and its field names are the
format, so renaming them would destroy the portability that makes the object worth carrying. It is
also flat, wide and variable — roughly a hundred facts, of which any listing carries a different
subset — so a fixed shape would either invent columns or drop real ones.

### What it costs

One event per home read, charged after the record arrives. A listing that no longer resolves
produces no row and costs nothing.

Naming a city instead of pasting links adds a second, much smaller event per home, because the
listings have to be found before they can be read. Runs given links never fire it.

### Questions

**One link produced no row, and the run did not fail. Why?**
Zillow served no home for it: either it never existed, or the listing has been withdrawn. That home
is skipped with a line in the run log and the rest of the list continues. Twenty links collected
last week will not all still resolve, and losing the other nineteen rows to one dead one is the
wrong trade.

**I gave both a list and a region. What ran?**
The list. A region is only read when no listings are given, and the run log says so. Reading a whole
city when somebody pasted six links is the worse of the two surprises.

**Why are some fields empty on a sold home?**
A sold or off-market home states fewer fields than a live listing; `description` in particular is
normally absent once a listing closes. Fields the record does not state come back as null rather
than being omitted, so a column never silently changes width and "Zillow states nothing here" stays
distinguishable from "the Actor did not look".

**Where is the price history?**
Not on this row, and not because it was dropped. The document a listing record is read from does not
contain the price history, the tax history, the schools or the comparable homes — they are genuinely
absent from it rather than withheld, so collecting them is a second read of a different document.
[Zillow Property Scraper](https://apify.com/thenetaji/zillow-property-scraper) has a mode for it.

**How many listings can one region run reach?**
Zillow serves at most 20 pages of any region, about 820 homes, however many it says match. The run
log states both numbers on the first page. Split the region by ZIP code, or supply the listings
yourself.

**Is a Zillow account, cookie or API key required?**
No. None of the three is supplied to the Actor or needed by it.

### Related Actors

[Zillow Search Scraper](https://apify.com/thenetaji/zillow-search-scraper) returns the listing rows
for a city without reading each home's record, which is the cheaper way to see what is on the market
before deciding which homes are worth a full read.

[Zillow Property Scraper](https://apify.com/thenetaji/zillow-property-scraper) reads the same record
from a property identifier and additionally offers the price, tax, school and comparable history as
a second mode.

[Zillow Sitemap Scraper](https://apify.com/thenetaji/zillow-sitemap-scraper) supplies property
identifiers in bulk from Zillow's published index of roughly 14 million homes, which is the cheapest
way to assemble a list this Actor can consume.

# Actor input Schema

## `zpid` (type: `array`):

The Zillow listings to read, one per line. Paste the links straight from your browser — a bare property ID works too, and the two forms may be mixed. Leave this empty to read a whole city instead.

## `region` (type: `string`):

Read a whole city, state or ZIP code instead of a list — the `san-francisco-ca` in https://www.zillow.com/san-francisco-ca/, or the pasted search link itself. Used only when no listings are given above. Finding each home this way is billed separately from its record.

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

Which listings to read when a region is given: homes on the market, or homes that have sold. Ignored when listings are pasted above.

## `startPage` (type: `integer`):

Where in the region the search starts, at 41 homes per page. It splits one city across several runs. Ignored when listings are pasted above.

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

Maximum number of records to save. Set 0 for no limit. A pasted list is read in the order you gave it.

## Actor input object example

```json
{
  "zpid": [
    "2146999031",
    "https://www.zillow.com/homedetails/2146999031_zpid/"
  ],
  "region": "san-francisco-ca",
  "status": "for_sale",
  "startPage": 1,
  "maxItems": 20
}
```

# Actor output Schema

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

All records scraped by this run

# 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 = {
    "zpid": [
        "https://www.zillow.com/homedetails/2114-S-Trumbull-Ave-Chicago-IL-60623/2146999031_zpid/"
    ],
    "status": "for_sale",
    "startPage": 1,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("thenetaji/zillow-detail-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 = {
    "zpid": ["https://www.zillow.com/homedetails/2114-S-Trumbull-Ave-Chicago-IL-60623/2146999031_zpid/"],
    "status": "for_sale",
    "startPage": 1,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("thenetaji/zillow-detail-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 '{
  "zpid": [
    "https://www.zillow.com/homedetails/2114-S-Trumbull-Ave-Chicago-IL-60623/2146999031_zpid/"
  ],
  "status": "for_sale",
  "startPage": 1,
  "maxItems": 20
}' |
apify call thenetaji/zillow-detail-scraper --silent --output-dataset

```

## MCP server setup

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