# Zillow Property Detail Scraper (`studioxl/zillow-detail-scraper`) Actor

60 fields per Zillow property page, each with the measured number for how often it actually shows up. Paste a listing URL, street address, or Zillow ID. Price, Zestimate, beds, baths, size, tax rate, agent, brokerage, and every photo URL. Apartment buildings and builder communities too.

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

## Pricing

from $1.50 / 1,000 property scrapeds

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 Property Detail Scraper

This listing publishes the measured number for each field: how often it actually shows up on a real run, not how often it should.

### What it does

Feed it Zillow listing URLs, street addresses, or Zillow IDs (zpids), on their own or chained from [Zillow Search Scraper](https://apify.com/studioxl/zillow-search-scraper)'s dataset. Each input is one plain HTTP fetch with a Chrome TLS fingerprint, no browser process, returning the full property record: address, price, size, broker and agent detail, tax and estimate data where published, and the property's photos, all in one row. No second call for photos.

### Which Zillow pages work

- **Single home page** (`/homedetails/`): works, full data, what most of the numbers below are measured against.
- **Apartment building** (`/apartments/`): works. One tested building returned 24 units, rents from $810 to $1,200, 1 to 2 bedrooms, the leasing office phone, and 24 photos.
- **Builder community** (`/community/..._plid/`): works. Of four live communities tested, three returned full rows: builder name, builder phone, price range, spec home and floor plan counts, and 11 to 21 photos. One had zero homes left and still returned the builder's name, phone, and photos. A separate community URL, the one the most-used Zillow detail Actor on the Store (about 1,000 monthly users) ships as its prefilled example, returns HTTP 404.
- **Building page** (`/b/...`): weak. Two tested came back nearly empty, no building name, no price, 0 to 1 units, 0 to 7 photos. Not worth running yet, and this listing will not tell you otherwise.
- **`/b/.../lat,long_ll/`**: refused on purpose. Zillow's robots.txt disallows this exact route, and at least one other Zillow Actor on the Store ships that same URL as its prefilled example anyway.

### What comes back, and how often

Measured across 745 properties in Austin TX, Chicago IL, Denver CO, Greenville SC, Miami FL, and Phoenix AZ, in three runs, all chained from a search Actor dataset. Zero failed requests in all three.

Always present on all 745: street address, city, state, ZIP, Zillow ID, listing URL, price, home status, home type, currency, and photo count.

| Shows up on | Field |
|---:|---|
| 99% | latitude and longitude |
| 99% | description text |
| 99% | bathrooms |
| 99% | bedrooms |
| 99% | at least one photo |
| 99% | living area (square feet) |
| 96% | MLS name |
| 95% | MLS ID |
| 88% | lot size |
| 84% | listing agent phone |
| 84% | broker name |
| 83% | days on Zillow |
| 82% | last sold price |
| 82% | property tax rate |
| 81% | listing agent name |
| 79% | year built |
| 74% | parking capacity |
| 70% | date sold |
| 36% | monthly HOA fee |
| 28% | price change and its date |
| 21% | rent Zestimate |
| 19% | Zestimate |
| 4% | county |

Zillow publishes a Zestimate on about one property in five, so if your workflow depends on it, plan for four rows in five to be empty.

County shows up on only 4% of listing pages, worse than any other field here. If you need county on every row, plan to join it from another source.

Five fields, broker phone, broker office name, agent license number, stories, and tour view count, were cut from the output in build 0.1.19. Zillow carries the keys but never fills them, confirmed empty across all 745 properties tested. This listing counts 60 fields, not 65.

### Photos

Across the 745 properties, this Actor returned 26,519 photo URLs, 36 per property on average, largest single property 60. 735 of 745, 99%, came back with at least one photo. 10 had none.

In the Austin batch, 50 requests were refused with an HTTP 403, retried automatically on a fresh IP, and all 150 properties still came back. This Actor bills per property returned, not per request, so a retry is never a separate charge. Every photo comes from that same fetch, no add-on charge.

When Expand building units is on, each unit row carries the parent building's photos, so a 24 unit building repeats the same 24 photo URLs across 24 rows.

### How to use it

1. **Give it URLs, addresses, or Zillow IDs.** Paste a listing URL, type a street address, or supply a zpid. Chain it from [Zillow Search Scraper](https://apify.com/studioxl/zillow-search-scraper)'s output dataset, or run it standalone.
2. **Set Maximum items** as your spend cap, the number of properties the run pulls before it stops.
3. **Turn on Expand building units** for an apartment building or builder community page, if you want a row per unit instead of one summary row.
4. **Click Start**, then read results from Storage or pull them through the API.

### What you are charged for

You pay per property returned to your dataset. Price per 1,000 properties, by your Apify plan:

| Your plan | Discount tier | Price per 1,000 properties |
|---|---|---:|
| Free | No discount | $2.40 |
| Starter | Bronze | $2.00 |
| Scale | Silver | $1.60 |
| Business | Gold | $1.50 |

Platform usage, compute, proxies, and storage, is included. No separate start fee, and no field is gated behind a paid add-on. This Actor bills per property returned, so a retry or a blocked request that gets auto-retried is never a separate line item.

The most-used Zillow detail Actor on the Store, at about 1,000 monthly users, charges $3.60, $3.00, $2.40, and $1.70 at those same tiers. This Actor is 33% cheaper at Free, Bronze, and Silver, and 12% cheaper at Gold.

A photo-only Actor on the Store charges $0.000727 per photo. At the 36 photos per property measured here, that is $0.0262 for photos alone, against $0.0020 total at Bronze here, photos plus the other 59 fields.

### Input example

The realistic case: pull everything a search run already found, then top it up with a couple of specific listings, capped at a spend limit.

```json
{
  "searchResultsDatasetId": "aBcD1234EfGh5678",
  "startUrls": [
    { "url": "https://www.zillow.com/homedetails/16210-S-14th-Dr-Phoenix-AZ-85045/8134236_zpid/" },
    { "url": "https://www.zillow.com/homedetails/5014-W-Frances-Pl-Austin-TX-78731/29342650_zpid/" }
  ],
  "maxItems": 500,
  "photoMaxWidth": "768",
  "expandBuildingUnits": false,
  "respectRobotsTxt": true,
  "proxyConfiguration": { "useApifyProxy": true }
}
```

`searchResultsDatasetId` pulls the zpid or URL off every row in a finished Zillow Search Scraper run and fetches the full detail page for each one. `startUrls` adds specific listings on top of that dataset, or stands alone if you skip the chained dataset entirely. `maxItems` caps the run at 500 properties, your spend limit, regardless of how many URLs are queued behind it.

### Output example

A real row from the Phoenix run, unedited:

```json
{
  "pageType": "property",
  "zpid": 8134236,
  "url": "https://www.zillow.com/homedetails/16210-S-14th-Dr-Phoenix-AZ-85045/8134236_zpid/",
  "homeStatus": "FOR_SALE",
  "homeType": "SINGLE_FAMILY",
  "price": 749000,
  "zestimate": 730300,
  "rentZestimate": 4137,
  "lastSoldPrice": 410000,
  "dateSold": "2017-11-14",
  "propertyTaxRate": 0.41,
  "monthlyHoaFee": 51,
  "totalMonthlyCost": 4514,
  "bedrooms": 5,
  "bathrooms": 3,
  "livingArea": 2839,
  "lotSize": 8594,
  "yearBuilt": 1993,
  "parkingCapacity": 3,
  "streetAddress": "16210 S 14TH Drive",
  "city": "Phoenix",
  "state": "AZ",
  "zipcode": "85045",
  "latitude": 33.29913,
  "longitude": -112.09028,
  "daysOnZillow": 4,
  "mlsId": "7071822",
  "mlsName": "ARMLS",
  "pageViewCount": 299,
  "favoriteCount": 36,
  "agentName": "Kevin Houston",
  "agentPhone": "602-370-8106",
  "brokerName": "West USA Realty",
  "photoCount": 43,
  "photos": [
    "https://photos.zillowstatic.com/fp/8ff578e47e35892a7842ea9fc38ec734-cc_ft_1536.jpg",
    "https://photos.zillowstatic.com/fp/0d9f91d5e83ea8bc9b39def2c1d42a00-cc_ft_1536.jpg",
    "... 40 more"
  ],
  "hasVirtualTour": false,
  "description": "Gorgeous 5 bed, 3 bath in Club West located on a very quiet, low traffic street! ..."
}
```

One row per property, with photos as an array on that same row. A building or community page returns a differently shaped row for the same run: `priceMin`, `priceMax`, and `unitCount` for an apartment building, or `builderName` and `homeCount` for a community, in place of the single-home fields above.

### Integrations

Every public Actor on Apify carries the platform's integrations, so a run can push property rows wherever your team already works.

- **n8n**, **Make**, **Zapier**, and **Pipedream** for no-code automation.
- **HubSpot** and **Airtable** to land properties as CRM records or rows.
- **Slack**, **Telegram**, and **Gmail** to push properties to people rather than to storage.
- **Google Drive**, **Snowflake**, **Keboola**, and **Airbyte** for exports and warehouse loads.
- **Webhooks** and the **GitHub** integration for anything custom.
- **LangChain**, the **OpenAI Agents SDK**, the **Vercel AI SDK**, and **Google ADK** to hand property records to an LLM app.

Two combinations worth setting up on day one:

**A watchlist that only reports changes.** Switch on **Only new or changed properties**, give it a memory store name, schedule the run, and point a webhook or Slack step at the finished dataset. A property that has not changed since the last run is skipped and never charged.

**Search feeding detail.** Run [Zillow Search Scraper](https://apify.com/studioxl/zillow-search-scraper) cheaply over an area, then pass its dataset id into this Actor's **Search results dataset** field to pull the full record, including photos, for everything the search found.

### FAQ

#### Is it legal to scrape Zillow?

The data on a Zillow property page is public, reachable with no login, and the only personal contact detail returned is what a listing agent already publishes as business contact information. You are responsible for how you use what you collect. Read Apify's guide on the [legality of web scraping](https://blog.apify.com/is-web-scraping-legal/).

#### Does this Actor respect robots.txt?

Yes, and it is on by default here, unlike the sibling search Actor, where a filtered search has no compliant route and the switch defaults off. Zillow's robots.txt permits `/homedetails/`, `/apartments/`, and `/community/`. It disallows `/b/*/*_ll/`, and those URLs are skipped, with a line in the run log explaining why.

#### Why is the Zestimate missing on most rows?

Zillow only publishes a Zestimate on about one property in five, measured at 19% across 745 properties tested. This Actor passes the value through exactly as Zillow gives it and never estimates one of its own.

#### Can I get one row per photo?

No, and you do not need one. Photos come back as an array on the property row, and Apify's CSV and Excel export flattens that array into numbered columns, `photos/0`, `photos/1`, and so on. Actors that bill per photo charge many times more for the same single page fetch.

#### What happens to a URL that no longer exists?

A dead listing returns HTTP 404, is logged, and is not charged. An address that cannot be matched to a listing is reported in the run log and skipped, rather than failing the whole run.

#### Can I use this with the Apify API or an MCP server?

Yes, both. The Actor runs under limited permissions, which is why chaining a search run uses a dataset picker that declares read access, rather than a pasted dataset id.

### Support

Missing a field, or want a different output shape? Open an issue on this Actor's Issues tab. Field requests are welcome. If Zillow publishes it, it can usually be added.

To find properties in the first place, see [Zillow Search Scraper](https://apify.com/studioxl/zillow-search-scraper). To work from the agent side instead, listing the estate agents in an area and everything each one has on the market, see [Zillow Agent Scraper](https://apify.com/studioxl/zillow-agent-scraper).

# Actor input Schema

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

Paste Zillow page URLs. Three shapes are supported, and each returns the row that fits it:

• /homedetails/… a single home, the full 60-field record
• /apartments/… a rental building: unit count, rent and bed ranges, leasing contact
• /community/… a builder community: spec homes, plans, builder name and phone

Buildings and communities can also be expanded into one row per unit, see the setting below.

A fourth shape, /b/name-city-st-id/, is accepted but rarely worth running. Two tested came back with no building name, no price and at most one unit, so it is not advertised as working.

One shape is refused on purpose: /b/…/lat,long\_ll/ is disallowed by Zillow's robots.txt and is skipped with a note in the log. Another Zillow Actor ships that shape as a prefilled example.

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

Street addresses such as "21386 Sunnyside Rd, Los Gatos, CA 95033", or bare Zillow IDs such as "24996529". Addresses are matched to a property automatically. Any that cannot be matched are reported in the log and skipped, rather than failing the run.

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

Instead of pasting URLs, pick the dataset from a finished Zillow Search Scraper run and every property it found is scraped in full. Search wide and cheap first, then pay detail prices only on the rows you keep. A verified chain: a Phoenix search returned 445 listings and this Actor pulled all 445 full records in 126 seconds, with no failed requests.

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

The number of properties this run will fetch before it stops. This is your spend cap, since you pay per property returned, so set it to match the size of the URL, address, or zpid list you are feeding in. Set it low for a first test run before pointing it at a full list.

## `photoMaxWidth` (type: `string`):

Choose the pixel width for the photo URLs this Actor returns: 192, 384, 576, 768, 960, 1152, 1344, or 1536. The default is 1536, the largest option this Actor offers. This Actor returns photo URLs, not image files, so the width you choose changes the size of the image at that URL, and nothing about what the run costs you.

## `expandBuildingUnits` (type: `boolean`):

Turn this on when you are scraping an apartment building (`/apartments/`) or a builder community page and want a row per unit instead of one summary row for the whole building. On a tested apartment building, this returned 24 separate unit rows, each carrying its own rent between $810 and $1,200 and its own bedroom count of 1 or 2, rather than a single row with only a price range. Leave it off if a per-building summary is all you need. No extra requests are made either way, because the units are already on the page we fetch. But each unit is a separate charged row, so a 200-unit building bills as 200 properties. Use the maximum-properties cap alongside this. Each unit row also carries the parent building's photos.

## `mergeRelistedProperties` (type: `boolean`):

A home taken off market and re-listed gets a new Zillow ID. When on, listings at the same coordinates with the same price and size are returned only once, so you are never charged twice for the same house.

## `onlyNewSinceLastRun` (type: `boolean`):

Remember every property across runs and return only those that are new, or whose price, Zestimate, rent estimate, status or description actually changed. Unchanged properties are skipped and never charged. Changed ones carry changeDetected: true. View and favourite counts are deliberately ignored, because they move on their own and would mark everything changed every run.

## `dedupeStoreName` (type: `string`):

Only used when 'Only new or changed properties' is switched on. Reuse the same name across a schedule so the Actor remembers what it has already seen; use different names to track separate portfolios independently.

## `respectRobotsTxt` (type: `boolean`):

Check each URL against Zillow's robots.txt before fetching. The rules are fetched live and applied properly. Zillow disallows all of /homes/ and then re-permits about 40 specific paths, which only longest-match-wins resolves correctly.

The /homedetails/, /apartments/ and /community/ shapes are all permitted, so this costs you nothing on normal input. It does skip /b/…/lat,long\_ll/ URLs, which Zillow disallows explicitly.

## `maxConcurrency` (type: `integer`):

Works together with the rate limit below. 10 is what this Actor is measured and priced around: a 445-property run at 10 parallel delivered all 445, absorbing 100 HTTP 403 responses along the way by rotating to a fresh IP and retrying. That is about 22% of attempts refused on first contact, which is normal for Zillow on residential IPs and costs you nothing, because a retry is never a charged row. Raising this increases the refusal rate faster than it increases speed, and concurrency 40 collapses throughput entirely.

## `maxRequestsPerMinute` (type: `integer`):

Ceiling on requests per minute, and usually the setting that actually decides how long a run takes. At 120 the crawler sat idle waiting for the limiter. 300 is the measured sweet spot: twice as fast as 120 and slightly cheaper. Raising it further increases refusals faster than it increases speed.

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

Zillow rejects Apify datacenter IPs almost entirely. A measured A/B at identical concurrency returned 60 of 60 properties on residential and nothing at all in 408 seconds on datacenter. Residential US is therefore the default and is what this Actor is priced around; changing it will most likely return nothing.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.zillow.com/homedetails/2114-S-Trumbull-Ave-Chicago-IL-60623/2146999031_zpid/"
    }
  ],
  "addresses": [
    "21386 Sunnyside Rd, Los Gatos, CA 95033",
    "24996529"
  ],
  "maxItems": 100,
  "photoMaxWidth": "1536",
  "expandBuildingUnits": false,
  "mergeRelistedProperties": true,
  "onlyNewSinceLastRun": false,
  "dedupeStoreName": "zillow-dedupe",
  "respectRobotsTxt": true,
  "maxConcurrency": 10,
  "maxRequestsPerMinute": 300,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `properties` (type: `string`):

No description

## `buildings` (type: `string`):

No description

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {
    "startUrls": [
        {
            "url": "https://www.zillow.com/homedetails/2114-S-Trumbull-Ave-Chicago-IL-60623/2146999031_zpid/"
        }
    ],
    "addresses": [],
    "maxItems": 100,
    "photoMaxWidth": "1536",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("studioxl/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 = {
    "startUrls": [{ "url": "https://www.zillow.com/homedetails/2114-S-Trumbull-Ave-Chicago-IL-60623/2146999031_zpid/" }],
    "addresses": [],
    "maxItems": 100,
    "photoMaxWidth": "1536",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("studioxl/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 '{
  "startUrls": [
    {
      "url": "https://www.zillow.com/homedetails/2114-S-Trumbull-Ave-Chicago-IL-60623/2146999031_zpid/"
    }
  ],
  "addresses": [],
  "maxItems": 100,
  "photoMaxWidth": "1536",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call studioxl/zillow-detail-scraper --silent --output-dataset

```

## MCP server setup

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