# Zillow ZIP Search Pro (FSBO, investor, rent & sold) (`kingly_contest_7cb/zillow-zip-search-pro`) Actor

Search Zillow by ZIP code, city or county. Clean ZIP lists, per-ZIP status, FSBO-first investor mode, rent/sold/pending in one run, investor columns and an optional detail add-on. Pay per result with hard cost caps.

- **URL**: https://apify.com/kingly_contest_7cb/zillow-zip-search-pro.md
- **Developed by:** [Carina Hatton](https://apify.com/kingly_contest_7cb) (community)
- **Categories:** Real estate, Lead generation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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 ZIP Search Pro: FSBO, investor, rent and sold listings by ZIP code

Paste a list of ZIP codes and get a clean, flat table of Zillow listings: agent listings, **for sale by owner (FSBO)**, pending, for rent and recently sold. Every ZIP gets its own status line, so you can tell a ZIP that truly has no listings from one that was blocked or mistyped. Pricing is pay per result: you pay for the listings you get, and the detail add-on is optional and capped.

### Who it's for

- **Investors and wholesalers.** FSBO-first searches, price cuts, days on Zillow, price compared with the Zestimate, rent Zestimate and gross rent yield, tax-assessed value and a non-owner-occupied flag, all in one row per property.
- **Agents.** Watch your farm ZIPs for FSBO, coming-soon, pending and sold activity, sorted newest first and exported straight to CSV or Excel.
- **Analysts and data teams.** Stable, flat columns, per-ZIP totals reported by Zillow, and a machine-readable run summary.

### Common problems with Zillow ZIP scrapers, and how this Actor handles them

| Common problem | What this Actor does |
|---|---|
| A ZIP returns 0 rows and you can't tell whether it was empty, blocked or invalid | Each input gets a status (`ok`, `partial`, `no_results`, `blocked`, `invalid`, `skipped`) with a plain-English message and Zillow's own result total, in the `ZIP_SUMMARY` record |
| Pasted ZIPs fail because of stray spaces, quotes, ZIP+4 or a lost leading zero | Inputs are cleaned automatically (`"33139 "` → `33139`, `2108` → `02108`, `48080-1234` → `48080`). Duplicates are removed and unfixable entries are reported, not silently dropped |
| FSBO homes get buried under agent listings when you set a result limit | **Investor mode** runs a dedicated owner-listing search for every ZIP first |
| Nearby-ZIP results mixed into your data | Strict in-ZIP results by default. Turn `strictZip` off to keep nearby homes; they are flagged `isNearbyResult` |
| Nested JSON that breaks spreadsheets | One flat row per listing, with ready-made dataset views |
| Surprise bills from optional extras | A per-run detail cap (`maxDetails`), a total result cap, a per-ZIP cap and a clean stop before your *Maximum cost per run* |
| Rent and sold searches that ignore your price filter | Separate rent range (`rentMin` / `rentMax`) and sale price range, re-checked on every row |

### What you get

- **Listing types in one run:** agent, FSBO, new construction, coming soon, auction, foreclosure, pending, for rent and sold.
- **Filters (applied on Zillow's side):** home type, minimum beds and baths, price range, rent range, days on Zillow, sold within, sort order.
- **Investor columns:** `priceCutUsd`, `daysOnZillow`, `priceVsZestimateUsd` / `priceVsZestimatePct`, `rentZestimate`, `grossRentYieldPct`, `pricePerSqft`, `taxAssessedValue`, `isNonOwnerOccupied`.
- **Optional detail add-on:** year built, HOA, Zillow's area tax rate and an estimated annual tax, last sold date and price, date posted, MLS ID, views and saves, description and brokerage. It can also return the public "Listed by" name and phone (off by default).

### Input examples

**FSBO leads in three ZIPs (investor mode), details on the first 25:**

```json
{
  "zipCodes": ["78701", "33139", "48080"],
  "investorMode": true,
  "includeDetails": true,
  "maxDetails": 25,
  "maxResults": 300,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US" }
}
```

**Houses and townhomes under $450k with 3+ beds, plus recent sales for comps:**

```json
{
  "zipCodes": ["48080", "48081"],
  "listingTypes": ["forSaleByAgent", "forSaleByOwner", "sold"],
  "homeTypes": ["houses", "townhomes"],
  "bedsMin": 3,
  "priceMax": 450000,
  "soldInLast": "90",
  "maxResultsPerZip": 100,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US" }
}
```

**Rentals between $1,500 and $3,000 a month:**

```json
{
  "zipCodes": ["33139"],
  "listingTypes": ["forRent"],
  "rentMin": 1500,
  "rentMax": 3000,
  "maxResults": 100,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US" }
}
```

#### Main input fields

| Field | What it does |
|---|---|
| `zipCodes` | ZIP codes to search. Messy input is cleaned (see above) |
| `locations` | Optional city or county names, e.g. `"Austin, TX"` |
| `listingTypes` | Any of `forSaleByAgent`, `forSaleByOwner`, `newConstruction`, `comingSoon`, `auction`, `foreclosure`, `pending`, `forRent`, `sold`. Default: agent + FSBO |
| `investorMode` | Searches FSBO first for every ZIP |
| `homeTypes` | `houses`, `townhomes`, `multiFamily`, `condos`, `apartments`, `lotsLand`, `manufactured` |
| `bedsMin`, `bathsMin`, `priceMin`, `priceMax`, `rentMin`, `rentMax` | Filters. `0` means no limit |
| `daysOnZillow`, `soldInLast` | `any`, `1`, `7`, `14`, `30`, `90`, `6m`, `12m`, `24m`, `36m` |
| `sortBy` | `relevance`, `newest`, `priceLow`, `priceHigh` |
| `strictZip` | Keep only homes inside the requested ZIP (default on) |
| `includeDetails` | Detail add-on, $0.005 per successfully enriched listing |
| `maxDetails` | Total cap on detail enrichments for the whole run (default 20; `0` = none). Listings beyond the cap are saved without details and without the detail charge |
| `includeListedByContact` | Adds the public "Listed by" name and phone from the listing page (detail add-on only; off by default) |
| `maxResults` | Total listing cap for the run (default 100) |
| `maxResultsPerZip` | Per-ZIP cap (`0` = no per-ZIP cap) |
| `proxyConfiguration` | Apify Proxy settings. Residential US is recommended |

### Output

There is one row per listing, with 79 flat columns. Dataset views: **Overview**, **Investor**, **FSBO & listing details** and **Rentals & sold**.

| Group | Fields |
|---|---|
| Identity and links | `zpid`, `url`, `requestedZip`, `requestedLocation`, `searchMode`, `inRequestedZip`, `isNearbyResult`, `scrapedAt` |
| Type and status | `listingStatus`, `statusText`, `listingType` (AGENT, FSBO, NEW_CONSTRUCTION, …, RENTAL, RENTAL_BUILDING, SOLD), `isFSBO`, `isAgentListed`, `isNewConstruction`, `isComingSoon`, `isAuction`, `isForeclosure`, `isPreforeclosureAuction` |
| Address | `address`, `street`, `unit`, `city`, `state`, `zipCode`, `latitude`, `longitude` |
| Property and price | `homeType`, `price`, `priceUnit` (`total` or `per_month`), `priceText`, `bedrooms`, `bathrooms`, `livingAreaSqft`, `pricePerSqft`, `lotAreaValue`, `lotAreaUnit` |
| Market signals | `daysOnZillow`, `priceCutUsd`, `lastPriceChangeUsd`, `lastPriceChangeDate`, `priceReductionText`, `openHouse`, `openHouseStart`, `openHouseEnd`, `comingSoonOnMarketDate`, `highlight` |
| Valuation | `zestimate`, `rentZestimate`, `priceVsZestimateUsd`, `priceVsZestimatePct`, `grossRentYieldPct`, `taxAssessedValue`, `isNonOwnerOccupied` |
| Sold and rentals | `soldPrice`, `soldDate`, `buildingId`, `buildingName`, `buildingUnitsSummary`, `buildingAvailableUnits` |
| Media | `imageUrl`, `has3DModel`, `hasVideo`, `brokerName` |
| Detail add-on | `detailStatus`, `yearBuilt`, `hoaMonthlyUsd`, `propertyTaxRatePct`, `estAnnualTaxUsd`, `lastSoldDate`, `lastSoldPrice`, `datePosted`, `mlsId`, `pageViewCount`, `favoriteCount`, `description`, `brokerageName` |
| Contact (opt-in) | `listedByRole`, `listedByName`, `listedByPhone` |

Notes on the data:

- `estAnnualTaxUsd` is an **estimate**: list price × Zillow's displayed area tax rate. It is not the actual tax bill.
- Zestimates are often missing from Zillow's search results, so the fill rate varies a lot by ZIP.
- Price and tax *history tables* are not included because Zillow doesn't include them in the page it serves. Last sold date and price are included.
- Apartment-building rental rows have `zpid = null`, `listingType = RENTAL_BUILDING` and a unit price summary.

### Per-ZIP status (`ZIP_SUMMARY`)

Open the run's **Storage → Key-value store → `ZIP_SUMMARY`** record. There is one entry per input:

| Status | Meaning |
|---|---|
| `ok` | Listings saved and no pages blocked |
| `partial` | Some listings saved, but at least one page was blocked. Re-run the ZIP to fill the gap |
| `no_results` | Zillow was reached but reports 0 matching listings, or none passed your filters or the strict-ZIP check. The message says which |
| `blocked` | Zillow blocked every page for this ZIP. Nothing was saved and nothing was charged for it |
| `invalid` | Not a usable ZIP, or not recognised by Zillow |
| `skipped` | Not processed because the run stopped early (result cap, cost limit or abort) |

Each entry also includes `zillowTotals` (Zillow's own count for each search), `saved`, `fsboSaved`, `nearbySkipped`, `filteredOut`, `duplicates`, `detailsOk`, `detailsFailed`, `detailsSkipped` and `capped` (which limit stopped it).

Example: `{"input":"33139 ","zip":"33139","status":"ok","message":"input \"33139 \" cleaned to 33139; stopped at limit: maxResultsPerZip","zillowTotals":{"forSale":936},"saved":20,...}`

`RUN_SUMMARY` holds runtime, rows saved, the stop reason (`max_results`, `charge_limit`, `aborted`), `maxDetails`, `detailsAttempted`, `detailsSkippedByCap`, charged event counts, and request and block counts.

### Pricing (pay per event)

| Event | Price | When it's charged |
|---|---|---|
| Actor start (`apify-actor-start`) | $0.00005 | Once per run, per GB of memory (minimum one) |
| `listing-result` | $0.0015 | Each listing row saved to the dataset |
| `detail-enrichment` | $0.005 | Only when the detail add-on successfully reads that listing's page |

Proxy and compute are included in these prices. You aren't billed separately for residential proxy traffic.

Example costs, calculated from the prices above:

- 100 listings without details: 100 × $0.0015 = **$0.15** (+ start event)
- 100 listings with `maxDetails: 20`: $0.15 + 20 × $0.005 = **$0.25** (+ start event)
- 1,000 listings without details: **$1.50** (+ start event)

### Cost control

- **`maxResults`** caps the total number of listings in the run, and **`maxResultsPerZip`** caps each ZIP.
- **`maxDetails`** caps paid detail enrichments for the whole run. Once it's reached, the remaining listings are still saved, but without details and without the detail charge.
- **Maximum cost per run** (set in Apify's run options): before each listing, the Actor checks that the listing (plus detail, if any) fits in the remaining budget. If only the listing fits, it is saved without details. If nothing fits, the run stops cleanly with `stopReason: "charge_limit"`, and the ZIPs it didn't reach are marked `skipped`.
- **No charge for failures:** blocked, invalid or empty ZIPs produce no rows and no listing charges. A detail page that fails or can't be read is not charged (`detailStatus` explains why).

### FAQ

**Do I need a proxy?** Yes, for reliable results. Zillow uses bot protection. Use Apify residential proxy with country US (the default prefill).

**How many listings can one ZIP return?** Zillow shows at most 20 pages (about 41 listings each) per search. For very dense areas, combine several listing types or filters to split the results.

**Why does a ZIP have fewer rows than Zillow's total?** Usually a cap (`maxResultsPerZip` or `maxResults`), the strict-ZIP check removing nearby homes, or your filters. `ZIP_SUMMARY` shows which one.

**Why are some Zestimates empty?** Zillow leaves them out of many search results. The detail add-on doesn't add Zestimates; it adds the fields listed above.

**Can I get the owner's phone number?** Only what the listing itself publicly shows in its "Listed by" box, and only if you turn on `includeListedByContact` together with the detail add-on. The Actor doesn't look up owners, skip-trace or enrich contacts.

**How fast is it?** Requests are made one at a time with a polite delay (default 2.5 s plus jitter). A run without details covers a few ZIPs in well under a minute. With details, expect a few seconds per enriched listing.

**Can I schedule it?** Yes. Use Apify Schedules (for example, daily with `daysOnZillow: "1"` and `sortBy: "newest"`) to catch new FSBO listings.

### Legal and responsible use

- The Actor reads only publicly visible listing pages, one request at a time. It does not log in or bypass paywalls.
- **You are responsible for how you use it.** Zillow's Terms of Use restrict automated access and reuse of its data. Make sure your use is lawful and complies with Zillow's terms and any applicable laws.
- **Contact data:** "Listed by" names and phones are **off by default** and come only from the public listing page. If you contact anyone by phone, text or email, you are responsible for complying with the TCPA, the National Do-Not-Call Registry and state DNC lists, CAN-SPAM and state telemarketing laws. Don't use this data for unsolicited robocalls or texts.
- Data may be incomplete or out of date. Verify important details on Zillow or with the listing agent before acting on them.

# Actor input Schema

## `zipCodes` (type: `array`):

US ZIP codes to search. Messy pastes are fine: spaces, commas, quotes, ZIP+4 (12345-6789) and Excel-trimmed ZIPs (e.g. 2108 → 02108) are cleaned. Every entry gets a status row in the ZIP_SUMMARY record (ok / no_results / blocked / invalid / partial / skipped).

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

Optional free-text locations such as "Saint Clair Shores, MI" or "Travis County, TX". Each one is resolved with Zillow's location search and searched as a whole region.

## `strictZip` (type: `boolean`):

On (default): listings whose address ZIP differs from the requested ZIP are dropped and counted as nearbySkipped. Off: they are kept and flagged isNearbyResult = true.

## `listingTypes` (type: `array`):

Pick any mix. For-sale types are searched together; pending, for rent and sold are separate Zillow searches in the same run.

## `investorMode` (type: `boolean`):

Runs a dedicated FSBO-only search for every ZIP before anything else, so owner listings are not buried under agent listings when you use result limits. Requires 'For sale by owner' in listing types.

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

Leave empty for all home types.

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

0 = any.

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

0 = any.

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

0 = no minimum.

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

0 = no maximum.

## `rentMin` (type: `integer`):

Used only for 'For rent'. 0 = no minimum.

## `rentMax` (type: `integer`):

Used only for 'For rent'. 0 = no maximum.

## `daysOnZillow` (type: `string`):

Only listings posted within this window (not used for sold).

## `soldInLast` (type: `string`):

Window for 'Recently sold'.

## `sortBy` (type: `string`):

Matters when you use result limits.

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

Opens each listing's detail page to add year built, HOA, tax rate and an estimated annual tax, last sold date/price, date posted, MLS ID, views/saves, description and the brokerage name. Charged as a separate 'detail-enrichment' event ($0.005) only when the detail page is read successfully, in addition to the $0.0015 listing event. Capped by 'Max detail enrichments'. Slower: one extra request per enriched listing.

## `maxDetails` (type: `integer`):

Total cap on detail enrichments across the whole run (all ZIPs combined). Each successful enrichment costs $0.005, so 20 = at most $0.10 in detail charges. Listings beyond the cap are still saved (and charged $0.0015 each) but without detail fields and without the detail charge. 0 = no details. Only used when the detail add-on is on.

## `includeListedByContact` (type: `boolean`):

Requires the detail add-on. Adds the name and phone exactly as shown publicly in the listing's 'Listed by' box (agent or, for FSBO, the owner who chose to publish it). Nothing else is collected. You are responsible for complying with telemarketing / privacy laws (TCPA, DNC, CAN-SPAM, state laws) when you use this data.

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

Hard cap on dataset rows for the whole run. 0 = no cap. The Actor also stops cleanly when your 'Maximum cost per run' would be exceeded.

## `maxResultsPerZip` (type: `integer`):

0 = no per-ZIP cap.

## `maxPagesPerSearch` (type: `integer`):

Zillow serves at most 20 pages (~41 listings each) per search.

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

Zillow uses bot protection. Small runs sometimes work without a proxy, but blocks become likely as volume grows; Apify residential proxy (US) is recommended for anything regular. Residential proxy traffic is billed by Apify to the account running the Actor.

## `minDelayMs` (type: `integer`):

Requests are sent one at a time with at least this delay (plus jitter). Minimum 1000.

## `maxRetries` (type: `integer`):

How many times to retry a page with a fresh session when Zillow's bot check blocks it.

## `fetchBackend` (type: `string`):

'direct' = live HTTP. 'replay' = serve saved pages from debugFixturesDir (offline tests only).

## `debugFixturesDir` (type: `string`):

Local folder with manifest.json for the replay backend.

## Actor input object example

```json
{
  "zipCodes": [
    "78701",
    "33139"
  ],
  "strictZip": true,
  "listingTypes": [
    "forSaleByAgent",
    "forSaleByOwner"
  ],
  "investorMode": false,
  "daysOnZillow": "any",
  "soldInLast": "any",
  "sortBy": "relevance",
  "includeDetails": false,
  "maxDetails": 20,
  "includeListedByContact": false,
  "maxResults": 100,
  "maxResultsPerZip": 0,
  "maxPagesPerSearch": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "minDelayMs": 2500,
  "maxRetries": 2,
  "fetchBackend": "direct"
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `zipSummary` (type: `string`):

No description

## `runSummary` (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 = {
    "zipCodes": [
        "78701",
        "33139"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("kingly_contest_7cb/zillow-zip-search-pro").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 = {
    "zipCodes": [
        "78701",
        "33139",
    ],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

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

```

## MCP server setup

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

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/XSDuzk5kwHWIrztVe/builds/jr84lJmrmaIng9SyG/openapi.json
