# Zillow Scraper | from $0.50/1K | Sale, Rent & Sold (`x_guru/zillow-search-scraper`) Actor

Search Zillow homes for sale, rentals and sold listings by city, ZIP or URL. Export CSV/JSON. Paid plans: $0.50/1K basic or $0.80/1K detailed rows. Free: $1/$2 per 1K, limited to 100 basic or 25 detailed properties per collection.

- **URL**: https://apify.com/x_guru/zillow-search-scraper.md
- **Developed by:** [Hundevmode Labs](https://apify.com/x_guru) (community)
- **Categories:** Real estate, Business
- **Stats:** 2 total users, 1 monthly users, 94.4% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 paid plan: basic zillow listings

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/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 Scraper — Sale, Rent & Sold Listings 🏠

Search Zillow homes for sale, rentals and sold listings by **city, ZIP code or Zillow search URL**. Export addresses, prices, bedrooms, bathrooms, living area and available Zestimate values to CSV or JSON. Add full property details when you need descriptions, photo URLs, histories or listing contacts.

**Start small:** enter `Austin, TX`, choose **For sale**, set **Maximum results per run** to `25`, then click **Start**. No additional setup or Zillow login is required.

### 💳 Pay per saved result

| Result | Paid Apify plans / 1,000 | Free plan / 1,000 |
| --- | ---: | ---: |
| Basic listing | $0.50 | $1.00 |
| Property with successfully fetched details | $0.80 total | $2.00 total |

**One result charge per property:** details replace the basic rate; they are not an added $0.80 on top. If enrichment fails and the basic listing is retained, the basic rate applies.

Your Apify account determines the rate automatically; input cannot select a cheaper plan. Apify's standard start event costs **$0.00005 per GB of allocated memory, minimum one event**. At the default 256 MB, each run costs one start event. Platform usage is included in PPE pricing; no additional dataset-item fee is configured. Errors and diagnostics are not paid results. Each continuation batch has its own start fee and budget.

#### Control your spending on Free and paid plans

Minimum permitted Max total charge: $0.00205, covering the start event and at least one result at the highest Free rate. This is a minimum budget setting, not a minimum bill: only the start and saved result events are charged. The same result rates apply to all paid Apify plans.

Set **Max total charge** in Apify run settings before starting. Through the API, use `maxTotalChargeUsd`. This is separate from `maxResults`: a large requested count does not override the spending limit. The Actor checks whether the remaining run budget permits another result and stops when it does not. Apify can also mark a run ABORTED when that limit is reached.

Free accounts use their available Apify credits at $1.00/1,000 basic rows or $2.00/1,000 detailed rows. A Free collection is capped at **100 basic search results, or 25 properties when collecting details**. The allowance is shared across continuation batches and is retained by Resurrect. For the Details Actor the allowance is always 25 properties. Free runs also cap requests at 40, collection time at 180 seconds and compressed downloads at 5 MB; raising advanced settings cannot raise these caps. Paid accounts use the input limits. Independent fresh runs have independent allowances; this is not an account-wide monthly quota. [Apify account usage limits](https://docs.apify.com/account/billing#limits) apply to the entire account. Exhausting prepaid credits on a paid plan can allow additional paid usage when overage is enabled; set a run budget if you want a fixed maximum for this collection.

Saved dataset rows remain in storage, subject to Apify retention and account access. Continue only after the account permits runs again, using the latest `PAGINATION.nextInput` if available and a new run budget. Continuation does not replenish credits or start paid runs automatically. An immediate platform stop can interrupt checkpoint saving; use the latest available checkpoint and compare saved rows if no continuation was created.

### What can you collect?

| Your task | Input to use | Result |
| --- | --- | --- |
| Find homes for sale | City or ZIP code + For sale | Listing inventory with prices and property attributes |
| Compare rental listings | City or ZIP code + For rent | Available rental cards and monthly prices |
| Research sold properties | City or ZIP code + Sold | Sold listings; add details for available sale dates and last sold prices |
| Reuse a filtered search | Zillow search URL | The URL's filters, with optional input overrides |
| Enrich known properties | Property URLs, ZPIDs or full addresses | Available property-page details |
| Build a larger export | Continue a previous run | New rows in the next batch, with shared deduplication |

Use the listing export for inventory comparisons, rental research and property-data pipelines. Enable **Add full property details** when richer records are worth the additional requests. Already have a property list? **Zillow Property Details Scraper** offers the same detailed output through a shorter input form.

### How to scrape Zillow listings

1. Enter a US city or ZIP code, one per entry. Alternatively, paste Zillow search URLs.
2. Choose **For sale**, **For rent** or **Sold** for a location search. For a search URL, leave the listing type unset to preserve its existing type.
3. Set **Maximum results per run** and optionally add price, bedroom, bathroom or living-area filters.
4. Keep **Add full property details** off for a faster listing export, or enable it for richer records.
5. Click **Start**, open the property results and download CSV, Excel or JSON. Check the coverage summary before treating the export as complete.

#### Example: homes for sale

```json
{
  "locations": ["Austin, TX"],
  "listingType": "sale",
  "maxResults": 25,
  "includeDetails": false
}
```

#### Example: rentals within a monthly budget

```json
{
  "locations": ["90210"],
  "listingType": "rent",
  "maxPrice": 5000,
  "maxResults": 25
}
```

For sold listings, use `"listingType": "sold"`. To enrich a known property directly, use `"zpids": ["89070667"]` or a property URL in `startUrls`. Direct property inputs always fetch details.

### What does the output look like?

Each dataset row represents one unique property or rental building card. **Missing values are `null`**, and the table stays flat for CSV exports.

| Data | Columns |
| --- | --- |
| Identity and location | `zpid`, `url`, `address`, `streetAddress`, `city`, `state`, `zipcode`, `latitude`, `longitude` |
| Listing and price | `status`, `price`, `currency`, `monthlyRent`, `pricePeriod` |
| Size and attributes | `bedrooms`, `bathrooms`, `livingArea`, `homeType`, `yearBuilt`, lot size, `daysOnZillow` |
| Estimates | `zestimate`, `rentZestimate` |
| Available listing contacts | `brokerName`, `agentName`, `agentPhone`, `agentEmail`, `listingPhone` |
| Additional property details | Description, sold date, last sold price, MLS ID, photo URLs and available histories, facts and schools |
| Collection information | `detailStatus`, `source`, `scrapedAt` |

This excerpt comes from a property collected in a private Apify cloud test. It illustrates an enriched row; field availability varies by property:

```json
{
  "zpid": "89070667",
  "address": "837 Washington Avenue #3B, Bronx, NY 10451",
  "soldDate": "2009-08-17",
  "lastSoldPrice": 173000,
  "photoCount": 13
}
```

Detailed structures are stored in `priceHistoryJson`, `taxHistoryJson`, `photosJson`, `factsJson` and `schoolsJson` as JSON strings. Photo output contains URLs; image files are not downloaded. Listing agents and brokerage contacts are **not property-owner contacts**.

### Input options

| Field | Purpose | Default |
| --- | --- | --- |
| `locations` | US cities or five-digit ZIP codes | Empty |
| `startUrls` | Zillow search or property URLs, as `[{"url":"…"}]` | Empty |
| `listingType` | `sale`, `rent` or `sold`; explicit values override URL type filters | For-sale location searches; preserve search-URL type when omitted |
| `maxResults` | Maximum unique rows saved in this run | 100 via API; 25 suggested in the form |
| `includeDetails` | Fetch property-page details for search results | `false` |
| Price and size filters | `minPrice`, `maxPrice`, `minBedrooms`, `maxBedrooms`, `minBathrooms`, `minSquareFeet`, `maxSquareFeet` | Unset |
| `zpids`, `addresses` | Known properties to enrich directly | Empty |
| `resumeFromRunId` | Continue the last finished batch | Unset |
| `propertyDatasetId` | Existing Dataset containing property IDs or URLs | Unset |
| `datasetField` | Identifier column: `zpid` or `url` | `zpid` |
| `advanced` | Request, time and traffic limits | Optional |

Prices are in USD; rental price filters refer to monthly rent. Living-area filters use square feet. Each manually entered input list accepts up to 100 entries; an input Dataset supports up to 100,000 rows. You may combine methods; the result limit is shared across them, and earlier inputs can consume it before later ones are reached.

Full addresses must include the street number, unit if applicable, city, two-letter state and five-digit ZIP. An ambiguous or unmatched address produces a separate collection issue, not a substitute property.

### Continue a larger export

**Maximum results per run is a batch limit.** A requested number is not a guarantee that Zillow will supply that many properties.

After each batch, open **Continue collection: next batch input** (`PAGINATION`). If `hasMore` is true, copy the full `nextInput` into another run of this same Actor. The next batch saves only new properties in a new dataset. Keep each batch's dataset to assemble your export.

For a target of 2,500 properties, you might request batches of 1,000, 1,000 and then the remaining 500. Calculate the remaining number from rows actually saved. Continue from the **latest** batch; reusing an older continuation can create separate branches with overlapping results.

Keep locations, property inputs, listing type and filters unchanged. You can change the next batch's `maxResults` and advanced limits. The previous run must be finished, belong to your account and this Actor, and retain its storage. New runs are started by you; continuation does not launch paid runs automatically.

If Zillow blocks a request, saved progress remains available. A later run can retry unfinished work, but a new run does not guarantee that source access has been restored. Live listings can move between pages over time.

### Automatic collection and recovery

Connection recovery is included in the price and handled automatically. You do not need a Zillow login or any additional service. If a temporary access restriction cannot be recovered within the collection limits, the Actor saves its progress and provides continuation input. Availability is not guaranteed for every property or run.

#### Collect details from an existing Dataset

Paste an accessible Apify Dataset ID into **Property list from Apify Dataset** and choose the `zpid` or `url` column. Up to 100,000 source rows are supported. The property list is frozen before collection, so continuation uses the same unique property IDs even if rows are later appended to the source. Invalid or missing identifiers are reported separately. Only Zillow property URLs containing a ZPID are accepted in the `url` column.

```json
{
  "propertyDatasetId": "YOUR_DATASET_ID",
  "datasetField": "zpid",
  "maxResults": 100
}
```

Replace the example ID with your Dataset ID. The same result, request, time and budget limits apply. A source Dataset is an input list; it is not modified, and old rows are not copied into the output.

### 🔁 What to do after an error or early stop

| Outcome | What to do next |
| --- | --- |
| Result, request, time or traffic limit | Open `PAGINATION`. If `hasMore` is true, copy **the full `nextInput`** into the next run's JSON input. Adjust the result cap or advanced limits as needed. |
| Access temporarily unavailable | Automatic recovery has already been attempted. Keep the saved dataset, wait, then continue with the full `nextInput`. Repeated access failures stop collection rather than retrying indefinitely. |
| Free collection allowance reached | Saved rows remain available. Continuation and Resurrect do not renew the allowance. After upgrading to a paid Apify plan, continue this same query with `resumeFromRunId` set to its latest run ID. |
| Budget reached | Apify can mark the run ABORTED when its charge budget is reached. Keep the saved dataset and use the latest `nextInput` with a new run budget. Budgets are per run, not a shared total across the chain. |
| Invalid input or changed continuation query | Correct the input. Do not repeat the same invalid run. Keep the original query when continuing, or start a fresh independent search with the corrected query. |
| `hasMore` is false | Inspect the summary and issues. Missing source data or failed exact-address matching can remain; a completed request queue is not a guarantee of complete source coverage. |
| Run failed before a checkpoint was saved | There is no usable continuation. Start again from your original input and compare any saved rows; a fresh independent run cannot guarantee deduplication against the old dataset. |

Use continuation from the **latest finished run of this same Actor and account**. Previously saved rows are not copied into the new dataset or charged again along that chain. Apify **Resurrect** restarts the same run with the same storage; `resumeFromRunId` creates a separate batch and dataset. Do not run both recovery methods at once. A Resurrect can create another standard start event; saved rows are not charged again. Request, time and traffic usage is restored from the latest checkpoint for the same run. A hard interruption can lose usage since that checkpoint, so the last few requests may not be included in restored counters.

### Coverage, collection limits and pricing

Large searches use pagination and split crowded map areas, with duplicate properties removed. Zillow may still cap results, block requests or show grouped rental cards. Rental buildings are not expanded into individual units, and some cards have no ZPID. **Check `coverageComplete` in the summary separately from `PAGINATION.hasMore`.** No remaining pages does not necessarily mean complete coverage.

Default collection limits are 80 requests, 600 seconds and 100 MB of compressed response traffic. Leave advanced settings unchanged for a first run. Raising the result limit does not raise these limits automatically. Temporary connection, rate-limit and server failures have bounded retries; blocked requests do not trigger an endless retry loop.

The dataset contains property rows only. Coverage, collection issues, request diagnostics and continuation input appear separately in the output links and key-value store. If enrichment fails after a listing was found, the basic row is retained and its `detailStatus` explains the outcome.

**Private preview:** PPE is configured for the two private test Actors. They have not been published in Apify Store. The result rates and standard start fee above are the configured policy; owner tests are separate from billing by an external customer.

### Choose the right Zillow actor

Choose this actor for a general city, ZIP or Zillow search-URL export, with optional property details. Use **Zillow ZIP Code Scraper** for independent quotas across ZIP territories, **Zillow Rental Scraper** for available apartment units, **Zillow Sold Homes Scraper** for sale dates and candidate comps, or **Zillow Property Details Scraper** when you already have property IDs or URLs.

### Frequently asked questions

#### Can I choose how many Zillow properties to collect?

Yes. On paid Apify plans, set `maxResults` between 1 and 100,000 for one run. Free collections have the smaller allowances described above. Collection can stop earlier because of available data, collection limits, blocking or your Apify budget. Use continuation for another batch.

#### Can I collect Zestimate and price history?

Available Zestimate values can appear in listing rows. Enable **Add full property details** for available histories, descriptions, facts, schools and photo URLs. These fields can be absent from the source.

#### Do I need a Zillow account or additional setup?

No Zillow login or additional service setup is required. Automatic connection recovery is included in the price. This does not guarantee access on every run or immunity from Zillow's blocking.

#### Is this an official Zillow API?

No. This is an independent scraper of publicly accessible Zillow data, callable through the Apify API. Zillow does not sponsor or endorse it.

#### Which Actor should I use for a list of ZPIDs?

Use **Zillow Property Details Scraper** for a focused property-enrichment form. Use this Actor to discover listings, or to combine discovery and enrichment.

#### Retry details that failed during a search

A retained basic row with `detailStatus: failed` is charged at the basic rate. Open **Retry failed details with the Details Actor** (`DETAIL_RETRY_INPUT`) and copy its `nextInput` to Zillow Property Details Scraper. This is a fresh detail collection: only successful detail rows are charged at the detail rate. Normal search continuation skips already saved basic rows and does not enrich them again. For a larger retry list, import the remaining failed ZPIDs into a Dataset and use that Dataset as input.

# Actor input Schema

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

Enter US locations, one per entry: Austin, TX or 90210. Leave empty if you only want to use Zillow URLs, property IDs or addresses. Locations without an explicit listing type search homes for sale.

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

Choose For sale, For rent or Sold for location searches. Leave unset when using a Zillow search URL to preserve its listing type. An explicit choice overrides the URL's listing-type filter.

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

Maximum unique rows to save (1–100,000 on paid plans), subject to source availability, collection limits and budget. Free: up to 100 basic results or 25 properties when collecting details across one collection and its continuation chain. Entering a larger number does not raise the Free allowance. Use Max total charge in Apify run settings to cap spending.

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

Paid plans: $0.80/1,000 successful detailed rows; Free: $2.00/1,000. This replaces the basic charge ($0.50 paid / $1.00 Free). Photo URLs and available histories are included. Free detailed collections are capped at 25 properties across continuation batches. Failed enrichment may retain a basic row at the basic rate. Direct property inputs always fetch details.

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

Paste Zillow URLs, one per entry. Search URLs preserve their filters unless you explicitly change listing type or filters below. Property URLs must contain a ZPID and always fetch details.

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

Set a minimum asking price. For rentals, use monthly rent in US dollars.

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

Set a maximum asking price or monthly rental price in US dollars.

## `minBedrooms` (type: `integer`):

Enter the smallest number of bedrooms to include.

## `maxBedrooms` (type: `integer`):

Enter the largest number of bedrooms to include.

## `minBathrooms` (type: `number`):

Enter the smallest number of bathrooms to include, for example 2 or 2.5.

## `minSquareFeet` (type: `integer`):

Enter the minimum interior living area in square feet.

## `maxSquareFeet` (type: `integer`):

Enter the maximum interior living area in square feet.

## `zpids` (type: `array`):

Enter numeric Zillow property IDs, one per entry. Find the ID before \_zpid in a property URL. Duplicates are removed across input methods.

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

Include street number, unit if applicable, city, two-letter state and five-digit ZIP. Only an exact address and unit match is saved; ambiguous or unmatched inputs are reported separately.

## `propertyDatasetId` (type: `string`):

Optional: paste the 17-character Dataset ID from a previous Zillow search or your own import. The Actor reads up to 100,000 rows, freezes the property list, and collects details for each unique valid property. You must have read access. Invalid rows are reported separately.

## `datasetField` (type: `string`):

Choose the source Dataset column: zpid for numeric property IDs, or url for Zillow property URLs containing a ZPID.

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

Paste the finished run ID from this same Actor and account. Prefer copying the full PAGINATION.nextInput from the previous run. Keep the query unchanged. This run saves and charges only new rows, plus the standard start fee. Maximum results per run and the Apify budget both apply to this new batch.

## `advanced` (type: `object`):

Leave these settings unchanged for your first run. Optional limits control how much work one batch can do. Free runs additionally cap requests at 40, time at 180 seconds and compressed downloads at 5 MB; higher input settings cannot raise those caps.

## Actor input object example

```json
{
  "locations": [
    "Austin, TX"
  ],
  "maxResults": 3,
  "includeDetails": false,
  "datasetField": "zpid"
}
```

# Actor output Schema

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

No description

## `summary` (type: `string`):

No description

## `errors` (type: `string`):

No description

## `requests` (type: `string`):

No description

## `pagination` (type: `string`):

No description

## `detailRetry` (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 = {
    "locations": [
        "Austin, TX"
    ],
    "maxResults": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("x_guru/zillow-search-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 = {
    "locations": ["Austin, TX"],
    "maxResults": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("x_guru/zillow-search-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 '{
  "locations": [
    "Austin, TX"
  ],
  "maxResults": 3
}' |
apify call x_guru/zillow-search-scraper --silent --output-dataset

```

## MCP server setup

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