# Zillow Property Listings Scraper (`superslowsloth/zillow-listings`) Actor

For-sale, for-rent and recently-sold property listings from Zillow search. One flat row per property: address, price, beds, baths, living area, lot size, home type, days on Zillow, coordinates, broker and photo.

- **URL**: https://apify.com/superslowsloth/zillow-listings.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** Real estate, AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.63 / 1,000 listing scrapeds

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?

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 Listings Scraper

One search in, one flat table out. Give it a location — a city, a ZIP, a county,
or a Zillow search URL pasted from your browser — and it returns the properties
Zillow would show you, each with its address, price, beds, baths, floor area,
lot size, home type, days on Zillow, coordinates, listing broker and photo.

Covers all three Zillow tabs: **for sale**, **for rent** and **recently sold**.

Built to be called by an agent or a script: small input, small output, one row
per property, no nested objects, no HTML.

### Input

| field | meaning |
|---|---|
| `location` | `"Austin, TX"`, `"90210"`, `"Miami-Dade County, FL"`, or a full Zillow search URL |
| `listingType` | `forSale` (default), `forRent` or `sold` |
| `maxResults` | How many rows to return. Default 100, ceiling 820 — see below |
| `priceMin`, `priceMax` | Price band. Asking price for sale and sold; monthly rent for rent |
| `bedsMin`, `bathsMin` | Minimum bedrooms and bathrooms |
| `sort` | Zillow default, newest, price, bedrooms, square feet or lot size |
| `requestIntervalSecs` | Pause between result pages. Default 1 |
| `proxyConfiguration` | Residential proxy. Required in practice — see below |

Pasting a Zillow URL keeps whatever filters are already on it — waterfront,
school rating, keyword, HOA — and applies the fields above on top. The one thing
a pasted URL does *not* override is `listingType`, because an input that loses
to a value hidden inside a URL is an input nobody can trust.

### Output

One item per property:

| field | meaning |
|---|---|
| `zpid` | Zillow's property id. Stable for a home; a `lat--lng` string for an apartment building |
| `url` | The listing page on zillow.com, absolute |
| `address` | Full address as Zillow renders it |
| `street_address`, `city`, `state`, `zipcode` | The same address, split |
| `price` | Asking price, monthly rent, or sale price, depending on `listingType` |
| `price_text` | The same figure as Zillow renders it, e.g. `"$3,455+"` |
| `currency` | Currency `price` is quoted in |
| `beds`, `baths` | Bedroom and bathroom counts. Zillow counts a half bath as `0.5` |
| `living_area`, `living_area_unit` | Interior floor area, `sqft` for US listings |
| `lot_size`, `lot_size_unit` | Lot size, and **the unit it was measured in** — read both |
| `home_type` | `SINGLE_FAMILY`, `CONDO`, `TOWNHOUSE`, `MULTI_FAMILY`, `APARTMENT`, `LOT`, … |
| `listing_status` | `FOR_SALE`, `FOR_RENT`, `SOLD`, `PENDING`, … |
| `days_on_zillow` | Days the listing has been live |
| `latitude`, `longitude` | Coordinates |
| `broker_name` | Listing brokerage |
| `image_url` | First photo, full size |
| `zestimate`, `rent_zestimate` | Zillow's own value and rent estimates |
| `year_built` | **Always `null`** — see below |
| `is_building` | `true` for an apartment building rather than a single home |
| `listing_type`, `search_location` | The search this row belongs to, echoed back |

#### `lot_size` needs `lot_size_unit`

Zillow switches units by size: a suburban lot comes back as `6359.76` **sqft**
and a rural one as `2.4` **acres**. A pipeline that reads `lot_size` alone and
sorts on it will put the two-acre parcel below the townhouse. The unit always
travels with the number.

#### `year_built` is declared but never filled

Zillow's search endpoint does not carry it. The value lives only on each
property's own detail page, which would be one extra request per row — a
different, and much more expensive, product than this one. The column is kept so
this dataset joins cleanly onto detail-page data; it is never guessed at.

#### Apartment buildings are nulls, not zeros

A for-rent search returns individual homes *and* whole apartment buildings. A
building has no single price, no bed count and no floor area, because it is a
container for units that each have their own. Those rows carry
`is_building: true`, `null` in the per-home fields, and the **cheapest
advertised unit price** in `price` with Zillow's own `"$3,455+"` string in
`price_text`.

A `0` there would read as "studio, no space, free", and any screen built on the
dataset would pick it up. Nothing in this output is ever zero to mean "unknown".

### Limits worth knowing before you run it

- **820 rows per search, always.** Zillow serves 41 listings per page and stops
  paginating at 20 pages, whatever the match count. `total_available` in the run
  log tells you how many matched — it is routinely five figures. To collect more
  than 820, split the search: by ZIP, by price band, or by home type.
- **Residential proxy is required, not optional.** Zillow refuses datacenter
  addresses, and is uneven even on residential ones — the same page answers
  HTTP 200 from one exit address and 403 from the next, minutes apart. The run
  answers a refusal by rotating to a fresh address and backing off with jitter
  rather than failing, which is what turns that unevenness into data. Turning
  the proxy off will produce blocks.
- **Zillow's anti-bot page is an HTTP 200.** It serves a "Press & Hold" page
  rather than an error status, so a status check alone would report success and
  return nothing. This actor treats a page with no data in it as a refusal and
  rotates, which is why a run can log retries and still come back complete.

### Pricing

Pay per event: a small charge when the run starts, plus one charge per listing
row delivered. Rows skipped as duplicates across page boundaries are not
charged, and a run that returns nothing charges only the start event.

# Actor input Schema

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

Where to search. A city and state ("Austin, TX"), a ZIP code ("90210"), a county ("Miami-Dade County, FL") or a neighbourhood all work. You can also paste a Zillow search URL straight from your browser, in which case any filters already on that URL - waterfront, school rating, keywords - are kept and the fields below are applied on top of them.

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

Which Zillow tab to read. For rent returns individual homes and apartment buildings; a building has no single price, so its row carries the cheapest advertised unit and is flagged with is\_building.

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

How many rows to return. Zillow serves 41 listings per page and stops paginating at 20 pages, so 820 is the ceiling for any one search however many listings match. Narrow the location or the price band to reach the rest.

## `priceMin` (type: `integer`):

Lowest price to include. For sale and sold this is the asking or sale price; for rent it is the monthly rent, which Zillow filters on a different field - the actor picks the right one for the listing type.

## `priceMax` (type: `integer`):

Highest price to include. Same field-per-listing-type rule as the minimum above. Leave both empty for no price filter.

## `bedsMin` (type: `integer`):

Only listings with at least this many bedrooms. Apartment-building rows have no single bedroom count and are excluded by Zillow when this is set.

## `bathsMin` (type: `integer`):

Only listings with at least this many bathrooms. Zillow counts a half bath as 0.5 in the data but accepts whole numbers here.

## `sort` (type: `string`):

How Zillow orders the result set, which decides which listings you get when maxResults is smaller than the number of matches. Leave on Zillow default to get the same order the website shows.

## `requestIntervalSecs` (type: `integer`):

How long to wait between result pages. Zillow refuses bursts from one exit address, and the run answers a refusal by backing off onto a fresh address; raising this trades wall-clock time for fewer retries.

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

Required in practice. Zillow refuses datacenter addresses outright and is uneven even on residential ones - the same page answers 200 from one address and 403 from another - so the run rotates to a fresh address and retries rather than failing. Turning this off will produce blocks rather than data.

## Actor input object example

```json
{
  "location": "Austin, TX",
  "listingType": "forSale",
  "maxResults": 100,
  "sort": "default",
  "requestIntervalSecs": 1,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# 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",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/zillow-listings").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",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/zillow-listings").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",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call superslowsloth/zillow-listings --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,superslowsloth/zillow-listings"
        }
    }
}

```

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/fqc64TFppLx5hE5gM/builds/h6I7vrmOGOiEs0hlW/openapi.json
