# Zillow Property Listings Scraper – For Sale, Rent & Sold (`rl1987/zillow-api-scraper`) Actor

Scrape Zillow listings by city, ZIP, neighborhood, address or map area - for sale, for rent, sold, new construction and foreclosures. One row per home: price, beds, baths, sqft, lot, year built, Zestimate, MLS attribution, full-res photos, geo. Add price/tax history, schools, comps and climate risk.

- **URL**: https://apify.com/rl1987/zillow-api-scraper.md
- **Developed by:** [R.L.](https://apify.com/rl1987) (community)
- **Categories:** Real estate
- **Features:** Limited permissions
- **Stats:** 18 total users, 4 monthly users, 98.3% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.69 / 1,000 property rows

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?

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 Property Listings Scraper — For Sale, For Rent, Sold, Prices & Zestimates

**Zillow Property Listings Scraper** — Search Zillow real-estate listings by city, ZIP, neighborhood, address or map area, or look up full property detail by ZPID, via Zillow's private mobile API. Collect a whole metro, not just the 1,000 listings one Zillow search will return. Returns the maximum field set: all photos (full-res), price, tax assessment, the bed/bath breakdown, agent contact, Zestimate, geo and IDs. It reads Zillow's own API directly, so it's **fast, reliable and complete** — no flaky HTML parsing, no headless browser. Export to **JSON, CSV, Excel, or an API**.

> Search any US city, ZIP, neighborhood or address — for-sale, for-rent and sold homes — or look up exact ZPIDs.

***

### What does this Zillow scraper do?

You give it a location — `Austin, TX`, `90210`, `Downtown, Austin, TX` — a map area, or a
list of ZPIDs. (Zillow search-page URLs are **not** an input: one only ever reproduced 2 of the
11 filters it carried, so the filters are set explicitly instead.)

It returns **one row per property**: address, price, beds, baths, living area, lot size, year
built, property type, listing status, MLS status, tax assessment, full-resolution photos,
latitude/longitude and the ZPID.

Turn on **Include details** for the **agent and broker contact**, the Zestimate, the bathroom
breakdown and the listing-type flags — those come from Zillow's property-detail endpoint, which
is a second request and a second charge per property. A ZPID lookup always returns the full
detail set, so it is always charged for it.

- **No code required** — set your input, click **Start**, download your data.
- **Fast & accurate** — reads Zillow's own private API and returns structured JSON, not scraped HTML.
- **Rich, structured data** — every field below, clean and ready to use.
- **Gets past Zillow's 1,000-listing search cap** — **Deep search** subdivides the area until every piece fits under it, so you can collect a whole metro instead of 13% of it. It turns itself on when you ask for more than 1,000 results.
- **Ask for just the slice you need** — **Modes** return listings, full detail, one row per photo, agent contacts, or media/tour links, so you get a narrow dataset instead of sixty columns.
- **Transparent pay-per-event pricing** — charged per property delivered, never per row, never for duplicates and never for the count requests that plan a split. Each mode is priced for what it returns; from 29 October 2026 there is no separate platform-usage charge and no run minimum. See [How much does it cost?](#how-much-does-it-cost).
- **Export anywhere** — JSON, CSV, Excel, XML, or pull it live via the Apify API.

***

### What can you do with Zillow data?

- **Market research** — price, price-per-sqft and days-on-market distributions across a city or ZIP.
- **Price & availability monitoring** — re-run on a schedule and diff `price`, `marketingStatus` and `priceChangeAmount`.
- **Comparable sales (comps)** — pull `recentlySold` listings around an address.
- **Rental analysis** — pull `forRent` listings and compare asking rent against `rentZestimate`.
- **Agent and broker lead lists** — `agentName`, `agentBusinessName`, `agentPhone`, `brokerName`.

***

### Which listing types and property types can I search?

**Listing types** (`listingType`) — pick one bucket per run:

| `listingType` value | What you get |
| --- | --- |
| `fsba,fsbo,newConstruction,comingSoon,auction` | Everything currently for sale (the default for-sale mix) |
| `forRent` | Rentals |
| `recentlySold` | Sold homes |
| `foreclosure,preforeclosure,foreclosed` | Foreclosures, pre-foreclosures and bank-owned |

**Property types** (`homeTypes`) — leave empty for all, or pick any combination. These are the exact
values the API accepts and the exact values that come back in the `propertyType` field:

| `homeTypes` value | Zillow's label |
| --- | --- |
| `singleFamily` | Houses |
| `condo` | Condos |
| `townhome` | Townhomes |
| `multiFamily` | Multi-family / duplex |
| `apartment` | Apartments |
| `manufactured` | Manufactured homes |
| `land` | Lots & land |

***

### How do I collect a whole city? (the 1,000-listing cap)

**One Zillow search returns at most 1,000 listings, however many match.** Zillow stops paging
at 25 pages of 40 and says nothing about it. Measured on 2026-10-15: an Austin-wide bounding
box reports **7,720 matching listings** and hands back **1,000** of them — 13% of the city.
That is Zillow's limit, not this actor's, and no scraper can page past it in one search.

The way past it is to run several searches that each fit under the cap, and **Deep search**
does that for you. It subdivides the area into tiles, checks each tile's match count, and
splits again until every tile fits — then pages each one and merges the results.

**You rarely have to set it.** Left unset it turns itself on whenever you ask for more than
one search can deliver — `maxItems` above 1,000, or 0 for every match. Set it explicitly to
force it on or off.

Deep search only splits a tile it knows is over the cap. The count comes back on the search
response before any rows do, so planning the split costs one count request per candidate
tile and delivers no rows — **count requests are never charged**. `maxTileDepth` bounds how
far it may subdivide (default 4), and a tile still over the cap at full depth is partitioned
into price bands rather than quietly truncated. Measured on the beta build 2026-10-15:
Austin-wide (6,496 matches) planned 151 searches at depth 4, six of which were dense
downtown tiles still over the cap that went to price bands. Listings on a seam
between two tiles are de-duplicated across the whole run, so **you are never charged twice
for the same property**.

With deep search off, the actor still *tells you* when you have hit the cap: the run's status
message and log say how many listings matched and how to collect the rest, so a short dataset
is never mistaken for "that is all there is".

***

### Can I get just the photos, or just the agent contacts?

Yes — that is what **Mode** is for. Every mode returns a different slice of the same data, so
you get a narrow, ready-to-use dataset instead of sixty columns you have to cut down:

| Mode | What one row is | Columns |
| --- | --- | --- |
| **Listings** | one listing | the full search row — price, beds/baths, sqft, status, geo, tax, photos |
| **Property detail** | one listing | the above plus facts, Zestimate, price-change, agent contact and tour links |
| **Photos** | **one photo** | `zpid`, address, `photoIndex`, `photoUrl`, `photoCount` |
| **Agent contacts** | one listing | `zpid`, address, `agentName`, `agentBusinessName`, `agentPhone`, `brokerName`, `listingStatus`, `price` |
| **Media & tours** | one listing | `zpid`, address, `photoCount`, `hasVideos`, `has3DTour`, `virtualTourUrl`, `floorPlanImageUrl` |

Each mode has its own view on the dataset's **Output** tab, so you see that mode's columns
and not a wall of blanks from the others.

**Floor plans come from Zillow's own 3D Home scan**, so they appear on listings that have a
3D tour and not otherwise — the listing's `hasFloorPlan` flag is unreliable and reads `false`
even when a plan exists. The field is `floorPlanImageUrl`, the floor-plan PNG — the artifact
Zillow's own page renders. The API also reports a `view-floor-plan` viewer page, which is not
returned, because every one of those URLs answers **404**. A multi-floor home can have more
than one plan — the row carries the first and the run log says so when there are more.

**Photos** returns the listing's own photo set. Spot-checked against zillow.com 2026-10-15:
two resale listings matched the site's gallery exactly (40 and 38 photos). On a
**new-construction plan** listing the site's gallery is larger than the listing's own set —
24 against 36 — because it also shows community and floor-plan imagery that belongs to the
builder's community page rather than to the address.

Every mode works with a search *or* with `zpids` — pair **Photos** with a ZPID list to pull
the photo sets of listings you already have. And every mode works with **Deep search**, so
"every agent contact in Austin" is one run.

**Billing is per property, not per row.** A listing with 40 photos yields 40 photo rows and
is charged once. **Photos** needs no per-property detail request at all — the photo set
arrives on the search response — so it carries **no `detail-row` surcharge**. **Property
detail**, **Agent contacts** and **Media & tours** do make that request, and are charged for
it.

Tiling trades requests for coverage — the whole of Austin is ~7,700 rows instead of 1,000,
spread over more searches and so more proxy traffic and compute. Set `maxItems` if you want a
hard stop.

***

### Market stats: one row per place, not per home

Set **Mode** to `market` and put cities, ZIPs or neighborhoods in **Regions**. Each place
returns one row describing its *market* rather than any listing in it:

| column | what it is |
|---|---|
| `marketTempIndex` | Zillow's own market-temperature index for the place |
| `marketTempLabel` | what that index means in words — e.g. `strong buyer` |
| `marketTempDescription` | Zillow's paragraph explaining the label |
| `medianDaysToPending` | how long a listing there typically takes to go under contract |
| `region`, `regionId`, `useCode` | the place as you asked for it, Zillow's id for it, and the property type the figures cover |

Measured across US cities: median days to pending ran from **13 to 73** (Denver 31,
Dallas 40, Houston 44, Austin 73), and Austin read `marketTempIndex: 28.0`,
`marketTempLabel: "strong buyer"`. Zillow's own batch endpoint is unreliable above about
20 regions — asked for 20 it returned 18, and asked for 25 it failed outright — which is
why this mode asks one region per request instead of batching them.

Only one property use code returns the full picture. `uc_sfrcondo` (single-family plus
condo, the default) returns both the temperature and the days-to-pending; `uc_sfr` returns
the days only. Anything else Zillow answers with **silence rather than an error**, so the
actor refuses an unsupported code by name instead of handing you an empty run to diagnose.

`medianDaysToPending` is published per **city**, so a ZIP-level request returns the market
temperature and leaves that column null — measured: `78745` came back with
`marketTempIndex: 33` and no days-to-pending, while Austin, Denver and Miami had both. A
place that returns neither is skipped rather than emitted as a row of nulls.

This mode ignores every listing filter — there is no listing to filter. It costs **one
request per place**, it is billed per region row rather than per property, and `maxItems`
counts places. A street address is skipped: a single home has no market of its own.

### What are the price history, tax history, school and comp rows?

Turn on **Include sections** and each property brings its own history and context with it, as
extra rows in the same dataset:

| section | one row per | carries |
|---|---|---|
| `priceHistory` | price change, listing or sale event | date, event, price, price-change rate, price/sqft, and the MLS or agent that reported it |
| `taxHistory` | tax year | tax paid, assessed value, and the year-on-year change in each |
| `schools` | assigned school | name, grades, level, GreatSchools rating, distance, enrollment, students per teacher, students per counselor, certified-teacher share, themed ratings, address |
| `nearbyHomes` | nearby home | the 8 homes Zillow shows beside this one — address, price, beds, baths, area, status, MLS, agent |
| `comparables` | comparable | Zillow's own valuation comp set, same columns as nearby homes |
| `offerStrength` | price point | Zillow's modelled probability of winning at each price from 80% to 120% of list — 41 rows per property. **For-sale only**; new construction returns nothing |
| `demandByPrice` | price point × day | For four candidate asking prices, a 30-day series of average page views, saves and shares for **similar listings nearby** — not this property's own traffic. **Sold / off-market only**, 120 rows per property |
| `nearbyBuildings` | building | The neighbouring *buildings* — name, address, price range, bedroom groups ("2 bed from 2200+; 3 bed from 3300+") and minimum beds/baths. A rental's neighbours are buildings, so `nearbyHomes` answers the wrong question for an apartment. **For-rent only** |

Four more sections add **columns** to the listing row rather than rows of their own, because
each is one value per property:

| section | columns |
|---|---|
| `climate` | fire, flood, air, wind and heat: risk score, label and source, plus the FEMA flood zone, bad-air days and the 98th-percentile summer temperature. An insurance recommendation comes with **fire, flood and wind only** — air and heat have no insurance product behind them, and Zillow returns nothing for them |
| `neighborhood` | Walk Score, Bike Score and Transit Score with their descriptions, the parent region, and the local LGBT / source-of-income / housing-voucher protection levels with Zillow's source page |
| `provenance` | MLS id and name, the disclaimer text, the must-display flags a redistributor has to honour, `notMlsSourced`, the buyer brokerage, the listing AOR, and when Zillow last checked and last updated the listing |
| `engagement` | page views, favourites and time on Zillow |

**All of them arrive in one request** — with one exception, which is worth knowing before you
pick. Zillow keeps rental detail in a *different* document from for-sale detail, and this Actor
makes **one** request per property. So for a **rental**, `nearbyBuildings` and `climate`
are mutually exclusive: ask for nearby buildings and you get the rental document (no climate);
ask for climate and you get the for-sale document, which serves rentals fine but has no
nearby-buildings list. Everything else is unaffected, and a for-sale listing never uses the rental
document at all. That is why this is a checklist rather than eleven separate
modes: selecting all eleven costs exactly what selecting one costs — **one extra request per
property, charged once**. Measured on 2 properties with every section selected: **44 rows** on new construction, **336**
on sold homes — and 2 property rows plus 2 detail rows either way. The spread is because the
offer curve (41 rows) is for-sale only and the demand series (120 rows) is sold-only, so no
single listing can produce both.

Every child row carries a **`type`** column naming its section and the parent's **`zpid`**, so
one export joins straight back to the listing. A nearby home or comparable keeps its own ID
under `relatedZpid`, and a school keeps its own address — the property's address is deliberately
*not* copied onto child rows, or the school's city would be overwritten with the property's.

Two honest caveats:

- **Which sections a listing can carry depends on what it is.** Measured across three
  listings: a resale on the market filled climate 23/25, provenance 9/12 and engagement 3/3;
  new construction filled climate **0** (there is no history for a house that does not exist
  yet); and a **sold** home filled climate 0, provenance 0 and engagement 0, because Zillow's
  off-market detail does not carry them at all. Walk, bike and transit scores were the only
  group populated on all three. A Walk Score of `0` is a real score — it arrives with
  "Car-Dependent" and a live Walk Score link — not a missing value.
- **No agent email, and here is exactly how far that goes.** Several competing Zillow
  scrapers advertise agent email addresses. For an ordinary listing Zillow's app API
  publishes none: no email-shaped value and no field named `*mail*` anywhere in the search
  or lookup payloads, and the property type has no `agentEmail`, `email`,
  `listingAgentEmail`, `contactEmail` or `brokerEmail` field at all. One exception exists —
  **Zillow Showcase** listings, a paid seller product, carry the agent's email — but that
  was 1 of 40 listings in a sampled page, so this Actor does not scrape it rather than ship
  a column that is empty on ~97% of rows. You get `agentName`, `agentBusinessName`,
  `agentPhone`, `agentId`, `agentProfileUrl` and `brokerName`, and on a sold listing the
  buyer brokerage where the MLS publishes it.
- **Three MLS-provenance columns are rare.** `attributionTitle`, `buyerBrokerageName` and
  `listingAor` were empty across all 30 listings sampled in four listing kinds and three
  metros. They are kept rather than dropped because they are real fields that some MLS feeds
  do publish, and a buyer redistributing listings wants the AOR when it exists — but do not
  plan a pipeline around them.
- **On a sold listing, `price` is what it sold for — where the state publishes it.**
  Measured: Denver CO sold listings carry `price` of $400,000 / $1,075,000 / $875,000 and
  Miami FL $680,000 / $775,000 / $875,500, while **every** Austin TX sold listing carries
  no price at all. Texas is a non-disclosure state, so sale prices are not public record
  and Zillow has none to give. `lastSoldDate` still populates there, and `zestimate` is
  the best available estimate of value. This is geography, not a gap in the scraper.
- **Rentals have a section of their own**, `nearbyBuildings`, from Zillow's own rental detail
  document — and choosing it costs that rental its `climate` columns, because the two live in
  different documents and a property gets one request. See the note above the sections table.
- **A for-rent row fills fewer columns, and which ones depends on the record.** Measured
  across 5 Austin rentals: 24 to 37 of the 76 listing columns populated, mean 31, against 37
  on for-sale and 42 on sold. `mlsStatus`, `yearBuilt` and `lotSize` were null on all five —
  Zillow's rental search record carries no MLS/display-rules block. But `taxAssessedValue`
  (2/5), `daysOnZillow` (3/5), `bathrooms` (3/5) and `livingArea` (3/5) *do* populate, and the
  discriminator is the record kind: a **building or complex** listing has no unit-level facts,
  while an individual unit does. No listing agent either way (Zillow publishes none for
  rentals, so **Agent contacts** mode returns no rows and charges nothing for one).
  `rentZestimate` and `rentalApplicationsAccepted`, by contrast, only populate here.
- **A section can be legitimately empty.** New construction has no tax history, because the
  house does not exist yet; a sold home has no asking price. An empty section is Zillow having
  nothing, not a failure — and if the request itself fails for a property, the run says so and
  that property is not charged for sections.
- **`taxYear` is a year, not a date.** Zillow sends the tax record as a timestamp, but all of a
  property's rows carry the same time-of-day as *your request*, stepped back one year each — so
  only the year is real, and that is what is published rather than an invented day.

### What do Zillow's own terms mean?

- **ZPID** — Zillow's permanent numeric ID for a property, the number in a `.../12345678_zpid/` URL. Stable across relistings, which makes it the right key to join runs on.
- **Zestimate** — Zillow's *estimated* market value, computed by Zillow, not an asking price and not an appraisal. `rentZestimate` is the same idea for monthly rent. Both are only populated when **Include details** is on, and Zillow does not publish one for every property.
- **FSBA vs FSBO** — for sale by **agent** (`isFsba`) vs for sale by **owner** (`isFsbo`).
- **The three status fields** — `listingStatus` is the API bucket (`forSale`, `forRent`, `recentlySold`, `other`); `marketingStatus` is the lifecycle (`active`, `comingSoon`, `offMarket`, `closed`); `trueStatus` is the MLS-side status string, e.g. `Active`, `Active Under Contract`. They can disagree, and the MLS one is the most precise when present.
- **Contingent / pending** — `contingentListingType` and `isPending` mark a listing that is under contract but not closed.
- **Days on Zillow** — `daysOnZillow` counts days since the listing appeared on Zillow, not days since it hit the MLS.
- **Bathroom breakdown** — `bathroomsFull`, `bathroomsThreeQuarter`, `bathroomsHalf`, `bathroomsOneQuarter`, `bathroomsPartial` and `bathroomsFloat` (the decimal total). Plain columns, only populated when **Include details** is on. Zillow's wider facts & features (appliances, heating, parking, HOA) are **not** available: the app's property-detail query is pinned to a fixed field set by Zillow's persisted-query safelist, so asking for one more field is refused outright rather than answered.
- **Tax assessed value** — the county's assessment in `taxAssessmentYear`. It is not a market price and is often years stale.

***

### How do I scrape Zillow listings? (4 steps)

1. **Set your input** — a **Location** (`q`), a bounding box, or a list of **ZPIDs**.
2. **Pick a mode** — **Listings**, **Property detail**, **Photos**, **Agent contacts** or **Media & tours**, depending on which slice of the data you want.
3. **Pick options** — listing type, home types, price/beds/baths minimums, **Include details**, and a `maxItems` cap. Keep the default Residential (US) proxy.
4. **Run & export** — click **Start**, then download the dataset as JSON/CSV/Excel or fetch it via the API.

***

### What input does it take?

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | enum | `search` | Which slice of the data to return: `search` (listing rows), `detail` (full per-property detail), `photos` (one row per photo), `agent` (listing-agent contacts), `media` (tour / floor-plan / 3D links), `market` (one row per city/ZIP — see [Market stats](#market-stats-one-row-per-place-not-per-home)). |
| `regions` | array of string | – | Market stats mode only: the cities, ZIPs or neighborhoods to report on, one row each. Ignored by every other mode. |
| `q` | string | `Austin, TX` | City, ZIP, neighborhood or street address. The main search input, resolved to a map area automatically. **Zillow search-page URLs are not accepted** — see the [FAQ](#can-i-paste-a-zillow-search-url). |
| `zpids` | array of string | – | Look up full detail for these Zillow property IDs. If set, the location/search inputs are ignored. The input for `detail` mode. |
| `north` | string | – | Advanced: raw bounding-box north latitude (e.g. 30.35). Optional power-user override — supply all four of north/south/east/west to search an exact map box instead of a Location. |
| `south` | string | – | Advanced: raw bounding-box south latitude (e.g. 30.20). Optional power-user override; used only when all four of north/south/east/west are given. |
| `east` | string | – | Advanced: raw bounding-box east longitude (e.g. -97.70). Optional power-user override; used only when all four of north/south/east/west are given. |
| `west` | string | – | Advanced: raw bounding-box west longitude (e.g. -97.85). Optional power-user override; used only when all four of north/south/east/west are given. |
| `priceMin` | integer | – | Minimum price (USD) |
| `priceMax` | integer | – | Maximum price (USD) |
| `bedsMin` | integer | – | Minimum bedrooms |
| `bathsMin` | integer | – | Minimum bathrooms |
| `bedsMax` | integer | – | Maximum bedrooms |
| `bathsMax` | integer | – | Maximum bathrooms |
| `sqftMin` | integer | – | Minimum finished living area, in square feet |
| `sqftMax` | integer | – | Maximum finished living area, in square feet |
| `lotSizeMin` | integer | – | Minimum lot size **in square feet**, not acres (one acre is 43,560 sqft) |
| `lotSizeMax` | integer | – | Maximum lot size in square feet |
| `yearBuiltMin` | integer | – | Earliest year built, inclusive |
| `yearBuiltMax` | integer | – | Latest year built, inclusive |
| `daysOnZillowMax` | integer | – | Only listings new to Zillow within this many days. On Austin, 1 day matched 107 listings against 6,511 unfiltered; 7 days matched 394 |
| `keywords` | string | – | Free-text keyword search over the listing, as Zillow's own keyword box does it — `pool`, `casita`, `no HOA`. A word Zillow does not index matches **nothing**, and the run says so |
| `sortBy` | enum | `daysOn` | Order Zillow returns listings in: `daysOn` (newest), `price`, `beds`, `baths`, `livingArea`, `lotArea`, `yearBuilt`, `recentlyChanged`, `featured`, `relevance`. Plus `rentalPriorityScore`, `rentalDays`, `rentalPriorityScoreCandidate` — **for-rent searches only** |
| `sortDescending` | boolean | `false` | Reverse the sort. `relevance` ignores it, and `price` ascending puts listings with no published price (auctions) first |
| `includeSections` | enum array | `[]` | Extra per-property data, eleven to choose from. **Child rows**: `priceHistory`, `taxHistory`, `schools`, `nearbyHomes`, `comparables`, `offerStrength`, `demandByPrice`. **Extra columns**: `climate`, `neighborhood`, `provenance`, `engagement`. All eleven come from **one** Zillow request, so picking eleven costs the same as picking one — see [sections](#what-are-the-price-history-tax-history-school-and-comp-rows) |
| `regions` | array of string | – | **Market stats** mode only: the cities or ZIPs to report on |
| `useCodes` | enum array | `["uc_sfrcondo"]` | **Market stats** mode only: which property types the figures cover. Each type is its own request and its own row |
| `includeDetail` | boolean | `false` | Enrich each search result with the bed/bath breakdown, agent contact, Zestimate and listing-type flags from the GraphQL HDP endpoint (slower, one extra request per property). |
| `maxItems` | integer | `100` | Cap on **properties** (0 = all matches) — the same unit you are billed in. In `photos` mode one property is several rows, so `maxItems: 20` returns 20 listings' photos, not 20 photos. |
| `deepSearch` | boolean | auto | Split the search to get past Zillow's 1,000-listing cap. Unset, it turns on when `maxItems` is 0 or above 1,000. |
| `maxTileDepth` | integer | `4` | How many times deep search may halve the area. Each level quadruples the tile count and every candidate tile costs one free, row-less count request. Austin-wide used all 4 levels and still had 6 tiles needing a price-band split. |
| `listingType` | enum | – | What kind of listings to search for — one of `fsba,fsbo,newConstruction,comingSoon,auction`, `forRent`, `recentlySold`, `foreclosure,preforeclosure,foreclosed` |
| `homeTypes` | enum array | `[]` | Restrict results to these property types. Leave empty for all types. Values: `singleFamily`, `multiFamily`, `condo`, `townhome`, `apartment`, `manufactured`, `land` |
| `proxyConfiguration` | object | Apify Proxy | Route requests through a proxy. Residential (US) is the default and recommended — Zillow's PerimeterX blocks datacenter IPs. |

#### Example input

```jsonc
{
  "mode": "search",
  "q": "Austin, TX",
  "deepSearch": true,
  "maxTileDepth": 4,
  "zpids": [],
  "includeDetail": false,
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

***

### What does a row look like?

Each result is a JSON object like this:

```jsonc
{
  "zpid": 454188962,
  "url": "https://www.zillow.com/homedetails/454188962_zpid/",
  "streetAddress": "Madison-321 Plan, The San Francisco Shipyard : Madison",
  "city": "San Francisco",
  "state": "CA",
  "zipcode": "94124",
  "price": 554000,
  "currency": "usd",
  "pricePerSquareFoot": 773,
  "bedrooms": 1,
  "bathrooms": 1,
  "livingArea": 717,
  "lotSize": null,
  "lotSizeUnit": null,
  "yearBuilt": 2026,
  "propertyType": "condo",
  "listingStatus": "forSale",
  "marketingStatus": "active",
  "daysOnZillow": 0,
  "latitude": 37.7281,
  "longitude": -122.3706,
  "brokerName": null,
  "agentName": null,
  "agentBusinessName": null,
  "agentPhone": null,
  "taxAssessedValue": null,
  "taxAssessmentYear": null,
  "photoCount": 13,
  "photos": [
    "https://photos.zillowstatic.com/fp/d90d5a51db965cc028d2d54e6a887bb4-p_f.jpg",
    "https://photos.zillowstatic.com/fp/8a8bf73710dc2133ccc047446ce33a58-p_f.jpg",
    "…+11 more"
  ],
  "hasVideos": false,
  "has3DTour": true,
  "bathroomsFloat": 1,
  "bathroomsFull": 1,
  "bathroomsThreeQuarter": 0,
  "bathroomsHalf": 0,
  "bathroomsOneQuarter": null,
  "bathroomsPartial": null,
  "homeStatusText": "Active",
  "trueStatus": "Active",
  "contingentListingType": null,
  "newConstructionType": null,
  "isZillowOwned": false,
  "timeZone": "America/Chicago",
  "zestimate": 409100,
  "rentZestimate": null,
  "priceChange": null,
  "virtualTourUrl": null,
  "floorPlanImageUrl": null,
  "isFsba": true,
  "isFsbo": false,
  "isPending": false,
  "isNewHome": false,
  "isForeclosure": false,
  "isComingSoon": false,
  "isBankOwned": false,
  "isForAuction": false,
  "isOpenHouse": true
}
```

***

### Which fields are in the dataset?

| Field | Type | Description |
| --- | --- | --- |
| `zpid` | number | ZPID |
| `url` | link | URL |
| `streetAddress` | text | Street address |
| `city` | text | City |
| `state` | text | State |
| `zipcode` | text | ZIP |
| `price` | number | Price |
| `currency` | text | Currency |
| `pricePerSquareFoot` | number | Price / sqft |
| `priceChangeAmount` | number | Price change ($, signed) — negative is a cut, positive a rise |
| `priceChangeDate` | text | Date the price last changed (`YYYY-MM-DD`) |
| `bedrooms` | number | Beds |
| `bathrooms` | number | Baths |
| `livingArea` | number | Living area (sqft) |
| `lotSize` | number | Lot size |
| `lotSizeUnit` | text | Lot size unit |
| `yearBuilt` | number | Year built |
| `propertyType` | text | Property type |
| `listingStatus` | text | Listing status |
| `marketingStatus` | text | Marketing status |
| `daysOnZillow` | number | Days on Zillow |
| `latitude` | number | Latitude |
| `longitude` | number | Longitude |
| `brokerName` | text | Broker |
| `agentName` | text | Agent |
| `agentBusinessName` | text | Agent business |
| `agentPhone` | text | Agent phone |
| `agentId` | text | Zillow's stable id for the agent, so rows from different runs join on the person |
| `agentProfileUrl` | link | The agent's Zillow profile page |
| `taxAssessedValue` | number | Tax assessed value |
| `taxAssessmentYear` | text | Tax year |
| `photoCount` | number | Photo count |
| `photos` | array | Photos (full-res) |
| `hasVideos` | text | Has videos |
| `has3DTour` | text | Has 3D tour |
| `bathroomsFloat` | number | Bathrooms as a decimal |
| `bathroomsFull` | number | Full bathrooms |
| `bathroomsThreeQuarter` | number | Three-quarter bathrooms |
| `bathroomsHalf` | number | Half bathrooms |
| `bathroomsOneQuarter` | number | Quarter bathrooms |
| `bathroomsPartial` | number | Partial bathrooms |
| `homeStatusText` | text | Status (display) |
| `trueStatus` | text | MLS status |
| `contingentListingType` | text | Contingent type |
| `newConstructionType` | text | New construction type |
| `isZillowOwned` | boolean | Zillow owned |
| `timeZone` | text | Time zone |
| `zestimate` | number | Zestimate |
| `rentZestimate` | number | Rent Zestimate |
| `priceChange` | text | Price change |
| `virtualTourUrl` | link | Virtual tour |
| `floorPlanImageUrl` | link | Floor-plan image (PNG) |
| `isFsba` | boolean | For sale by agent |
| `isFsbo` | boolean | For sale by owner |
| `isPending` | boolean | Pending |
| `isNewHome` | boolean | New home |
| `isForeclosure` | boolean | Foreclosure |
| `isComingSoon` | boolean | Coming soon |
| `isBankOwned` | boolean | Bank owned |
| `isForAuction` | boolean | For auction |
| `isOpenHouse` | boolean | Open house |
| `country` | text | Country |
| `hdpVariant` | text | Listing variant — Zillow's own `FOR_SALE` / `NEW_CONSTRUCTION` / `OFF_MARKET` |
| `lastSoldDate` | text | Last sold date (ISO-8601) |
| `listedOnZillowAt` | text | First listed on Zillow (ISO-8601 UTC) |
| `mlsStatus` | text | MLS status, as the MLS itself reports it |
| `mlsProviderLogoUrl` | link | MLS provider logo, for attribution |
| `canShowAddress` | boolean | Whether the feed permits showing the exact address |
| `canShowOnMap` | boolean | Whether the feed permits showing the pin |
| `listingCategory` | text | Zillow's listing category |
| `subdivision` | text | Subdivision / community name |
| `subdivisionId` | number | Subdivision ID |
| `isFeatured` | boolean | Featured listing |
| `isShowcaseListing` | boolean | Zillow Showcase listing |
| `isPreforeclosureAuction` | boolean | Pre-foreclosure auction |
| `isUnmappable` | boolean | Zillow could not place it on the map |
| `hasFloorPlan` | boolean | A floor plan exists for this listing |
| `rentalApplicationsAccepted` | boolean | Rentals only — whether applications are open. `null` on sale listings |

`zestimate`, `rentZestimate`, the `bathrooms*` breakdown, `priceChange`, `virtualTourUrl`,
`floorPlanImageUrl`, `trueStatus`, `contingentListingType`, `homeStatusText`,
`newConstructionType`, `isZillowOwned`, the five `agent*` columns and the `is*` listing-type
flags come from the property-detail endpoint, so they are populated when **Include details** is on and on ZPID lookups
(which always return the full detail set). `timeZone` no longer needs it — the search record
carries one of its own.

**Two of these columns are legitimately empty rather than missing.** `mlsStatus` and
`mlsProviderLogoUrl` only exist where Zillow sourced the listing from an MLS, which was 1 of 3
listings sampled — a new-construction listing straight from the builder has no MLS record to
report. And `rentalApplicationsAccepted` is `null` on every sale listing deliberately: Zillow
sends `false` there, which would read as "applications are closed" for a house that was never
for rent.

Every column above is a flat, deliberately mapped value — the dataset carries no nested
API payload, so CSV and Excel exports are usable as-is. If you need a Zillow field this
table does not cover, open an issue rather than digging through a raw blob: the fields this
endpoint can actually return are tracked in
[issue #8](https://github.com/rl1987/apify-zillow-api-scraper/issues/8).

***

### How much does it cost?

The current price of every event is on the **Pricing** tab of this Actor's page in Apify Store. That tab is generated from the Actor's own pricing record, so it is always current, and your plan's rung is applied automatically. A figure repeated here would be a second copy that drifts out of date.

You can cap what any single run may spend with **Max total charge**.

### How do I run it from my own code?

Run it programmatically with the [Apify API](https://docs.apify.com/api/v2):

```bash
curl -X POST "https://api.apify.com/v2/acts/zillow-api-scraper/runs?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
  "mode": "search",
  "q": "Austin, TX",
  "deepSearch": true,
  "maxTileDepth": 4,
  "zpids": [],
  "includeDetail": false,
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}'
```

Or with the Apify CLI: `apify call zillow-api-scraper -i input.json -o`.

***

### FAQ

#### Can I paste a Zillow search URL?

No, and deliberately. A Zillow search-page URL carries its filters in a `searchQueryState`
blob that the web app rewrites whenever it ships a redesign. This Actor used to accept those
URLs and reproduced only **2 of the 11** filters a realistic one carries — price and beds —
silently dropping sqft, year built, lot size, HOA, garage, pool, days-on-Zillow, keywords and
sort. The run succeeded, returned plenty of rows, and they were the wrong rows.

Set the filters explicitly instead: `q` for the location, plus `listingType`, `homeTypes`,
`priceMin`/`priceMax`, `bedsMin` and `bathsMin`. To reproduce a panned map use the bounding box
(`north`/`south`/`east`/`west`), and to fetch specific listings use `zpids`. A run that still
passes `searchUrls` now stops and says so rather than searching somewhere else.

**Do I need a Zillow account or API key?**

No. The actor carries the Zillow Android app's own credentials and calls Zillow's mobile API for you — all you need is an Apify account.

**Do I need a residential proxy?**

Yes, and it is the default. Zillow fronts its endpoints with PerimeterX, which blocks datacenter IPs, so the input ships with Apify Proxy set to **Residential, country US**. Leave it alone unless you are supplying your own US residential pool.

**How do I search a whole city, a ZIP or one exact address?**

All three go in the **Location** field. A city or ZIP resolves to Zillow's own map region and is paginated 40 listings at a time. A full street address resolves to that one property, and the actor looks that property up directly instead of searching its neighbourhood — so you get the home you asked for, not its block.

Zillow's search box matches its own record of an address, and its idea of which city a property is in is not always the Post Office's — `7945 Lasalle Blvd` is filed under Hollywood FL, not Miramar FL. So the actor also retries the street line on its own, with the city and ZIP cut off, and still checks the house number, street, ZIP and state of whatever comes back before accepting it. If an address does not resolve at all, the run finishes with a message saying so: check the street spelling, or search the ZIP code instead.

**Can I scrape sold homes and rentals, not just for-sale?**

Yes. Set **Listing type** to `recentlySold` for sold homes, `forRent` for rentals, or the foreclosure bucket for foreclosures, pre-foreclosures and bank-owned. Zillow search-page URLs are **not** an input — see [the FAQ above](#can-i-paste-a-zillow-search-url) for why, and use `q` plus the filters instead.

**What does "Include details" actually add?**

One extra request per property against Zillow's property-detail endpoint, which fills in the Zestimate, the rent Zestimate, the bathroom breakdown, price-change info, virtual-tour and floor-plan URLs, the MLS status, and the `isFsba`/`isFsbo`/`isPending`/`isForeclosure`-style flags. It is slower and billed as a second event per row, so leave it off if the search fields are enough.

**Can I schedule it and only pick up what changed?**

Yes — run it on Apify's scheduler and join runs on `zpid`, which is stable across relistings. `daysOnZillow`, `marketingStatus` and `priceChangeAmount` are the fields to diff
(`priceChangeAmount` is signed, so a cut and a rise are distinguishable; the `priceChange`
column beside it is Zillow's own display label and carries no direction). Results come back sorted by days on Zillow ascending, so the newest listings arrive first.

**How is the cost bounded?**

Two dials and a backstop. `maxItems` is the hard ceiling on **properties** for a run (0 = every match), which is the unit you are charged in, and the mode decides whether a property carries a surcharge at all. Together those bound the bill before you start — and from 29 October 2026 the event price is the whole price, with no platform-usage charge on top and no run minimum. `maxTileDepth` bounds how many requests a Deep search makes. The backstop is the run's own **max total charge** setting: when a run hits it the actor stops and says so in the status message, rather than exiting SUCCEEDED with a short dataset that looks like the end of the results.

**Is scraping this legal?**

You are responsible for how you use the data. Scrape only public real-estate listing data and comply with Zillow.com's Terms and applicable law (e.g. GDPR/CCPA for personal data).

***

### How does it work under the hood?

The actor impersonates Zillow's official Android app and calls the same private endpoints the app does — mobile search for listings, mobile lookup for ZPIDs, and Zillow's GraphQL property-detail query for the extras — then normalises each response into the flat record above. Location strings go through the app's own autocomplete to get a region ID and viewport, which is cached for the run so a paginated job resolves once. No browser, no HTML scraping — just clean, structured data.

***

### Good to know

- Scrape only public real-estate listing data and respect Zillow.com's Terms of Service and robots policy.
- For personal data, comply with GDPR/CCPA and applicable law — you are the data controller.
- Fields can be `null` when Zillow doesn't expose them for a given record.

***

### Global real estate toolkit

Part of the **Global real estate toolkit** — real-estate listing data across major US, European, and Australian property portals:

- [Redfin API Scraper](https://apify.com/rl1987/redfin-api-scraper) — Scrape Redfin listings — address, price, beds/baths, sqft, status, agent.
- [realestate.com.au API Scraper](https://apify.com/rl1987/realestateau-api-scraper) — Scrape realestate.com.au property listings (buy/rent/sold) with full detail, media, and agent contact.
- [ImmoScout24 API Scraper](https://apify.com/rl1987/immoscout24-api-scraper) — Scrape ImmoScout24 real-estate listings (Germany) with full detail, images, and agent contact.

***

### Found this useful?

Give the **Zillow Property Listings Scraper** a star on Apify and check out my other `apify-*-api-scraper` actors.

# Actor input Schema

## `mode` (type: `string`):

Which slice of the data to return. <b>Listings</b> is the full search row. <b>Property detail</b> adds facts, Zestimate and agent contact per property. <b>Photos</b> returns one row per photo, <b>Agent contacts</b> one row per listing agent, <b>Media & tours</b> the virtual-tour, floor-plan and 3D links — each a narrow dataset instead of sixty columns.<br><br>Mode picks the <i>shape</i>; <b>Extra data per property</b> below picks how much <i>history and context</i> comes with it — price history, tax history, schools, comparables, climate risk and more, in any mode. <b>Market stats</b> is the odd one out: it returns one row per <i>place</i> rather than per home — Zillow's market-temperature index and label and the median days to pending — so it takes a list of cities or ZIPs in <b>Regions</b> and ignores the listing filters. Use <b>Deep search</b> to collect more than the 1,000 listings one Zillow search returns.

## `q` (type: `string`):

City, ZIP code, neighborhood, or street address to search — e.g. "Austin, TX", "90210", "Downtown, Austin, TX". This is the main search input; it is resolved to a map area automatically. Zillow search-page URLs are not accepted: one only ever reproduced 2 of the 11 filters it carried, so the filters below are set explicitly instead.

## `regions` (type: `array`):

Cities, ZIPs or neighborhoods to report market stats for, one per line — <code>Austin, TX</code>, <code>78745</code>, <code>Denver, CO</code>. Used by <b>Market stats</b> mode only; every other mode ignores it. Each place costs one request <i>per property type</i> and returns one row per type (see Property types below). A street address has no market of its own and is skipped. Leave empty to use the Location above.

## `useCodes` (type: `array`):

Which property types the market figures cover. <b>Market stats</b> mode only. <code>uc_sfrcondo</code> (single-family + condo, the default) is the only one that returns both the market temperature and the median days to pending; <code>uc_sfr</code> returns the days only. Anything else Zillow answers with silence rather than an error, so the run refuses an unsupported value by name. <b>Each type costs its own request and returns its own row</b>, so two types over three places is six rows.

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

What kind of listings to search for

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

Restrict results to these property types. Leave empty for all types.

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

Minimum price (USD)

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

Maximum price (USD)

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

Minimum bedrooms

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

Minimum bathrooms

## `bedsMax` (type: `integer`):

Maximum bedrooms.

## `bathsMax` (type: `integer`):

Maximum bathrooms.

## `sqftMin` (type: `integer`):

Minimum finished living area, in square feet.

## `sqftMax` (type: `integer`):

Maximum finished living area, in square feet.

## `lotSizeMin` (type: `integer`):

Minimum lot size, in square feet - not acres. One acre is 43,560 sqft.

## `lotSizeMax` (type: `integer`):

Maximum lot size, in square feet.

## `yearBuiltMin` (type: `integer`):

Earliest year built, inclusive.

## `yearBuiltMax` (type: `integer`):

Latest year built, inclusive.

## `daysOnZillowMax` (type: `integer`):

Only listings new to Zillow within this many days. Measured on Austin: 1 day matched 107 listings against 6,511 unfiltered, 7 days matched 394.

## `keywords` (type: `string`):

Free-text keyword search over the listing, as Zillow's own keyword box does it - e.g. "pool", "casita", "no HOA". A word Zillow does not index matches nothing at all, so the run ends with no rows and says so.

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

Which order Zillow returns the listings in. These are Zillow's own sort keys, taken from the API's own list of accepted values. The three rental sorts work only when Listing type is For rent - Zillow refuses them for anything else, and the run will say so rather than return the wrong order.

## `sortDescending` (type: `boolean`):

Reverse the sort - highest price first, largest lot first, and so on. Note Relevance ignores it (Zillow returns the same order either way), and sorting by Price ascending puts listings with no published price, such as auctions, at the top.

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

Look up these Zillow property IDs directly instead of searching. Works with every mode — pair it with Photos to pull the photo sets of listings you already know.

## `north` (type: `string`):

Advanced: raw bounding-box north latitude (e.g. 30.35). Optional power-user override — supply all four of north/south/east/west to search an exact map box instead of a Location.

## `south` (type: `string`):

Advanced: raw bounding-box south latitude (e.g. 30.20). Optional power-user override; used only when all four of north/south/east/west are given.

## `east` (type: `string`):

Advanced: raw bounding-box east longitude (e.g. -97.70). Optional power-user override; used only when all four of north/south/east/west are given.

## `west` (type: `string`):

Advanced: raw bounding-box west longitude (e.g. -97.85). Optional power-user override; used only when all four of north/south/east/west are given.

## `deepSearch` (type: `boolean`):

One Zillow search returns at most 1,000 listings however many match — an Austin-wide search matches ~7,700 and returns 1,000. With this on, the actor subdivides the area into tiles that each fit under that cap and merges the results, reaching several times more properties. Costs proportionally more requests, and each property is still returned — and billed — only once. Left unset, it turns on automatically when Maximum results is 0 (all) or above 1,000.

## `maxTileDepth` (type: `integer`):

How many times Deep search may halve the area. Each level quadruples the tile count, and every candidate tile costs one (free, row-less) count request — an Austin-wide run planned 151 searches. A tile still over the cap at full depth is partitioned by price band instead of being truncated, so lowering this trades requests for a coarser split rather than losing listings.

## `includeDetail` (type: `boolean`):

Fetch each property's detail page too, filling in Zestimate, rent Zestimate, the bathroom breakdown, price-change, tour and floor-plan links and the listing-type flags. <b>This is a second request and a second charge per property</b> (detail-row), so leave it off if the search fields are enough. Forced on by the Property detail, Agent contacts and Media & tours modes, which are built from it.

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

0 = all matches. The same applies to Extra data: 2 listings with every section selected measured 44 rows on new construction and 336 on sold homes - still 2 charged properties either way. The spread is because the offer curve (41 rows) is for-sale only and the demand series (120 rows) is sold-only, so no single listing gets both.

## `includeSections` (type: `array`):

Extra per-property data, as additional rows joined to the listing by <code>zpid</code> (read the <code>type</code> column to tell them apart). <b>All of these come from one Zillow request</b>, so picking five costs the same as picking one: <b>one extra request per property</b>, charged once. <ul><li><b>Price history</b> — every price change, sale and listing event with its source.</li><li><b>Tax history</b> — tax paid and assessed value per year.</li><li><b>Schools</b> — assigned schools with rating, enrollment and student-teacher ratio.</li><li><b>Nearby homes</b> — the 8 homes Zillow shows beside this one.</li><li><b>Comparables</b> — Zillow's own valuation comp set.</li><li><b>Offer-strength curve</b> — Zillow's own modelled probability of winning at each price from 80% to 120% of list, as 41 rows. <i>For-sale listings only</i>: new construction returns nothing, because the model does not cover builder listings.</li><li><b>Demand by asking price</b> — for four candidate asking prices around the property's value, a 30-day series of average page views, saves and shares for <b>similar listings nearby</b> (not this property's own traffic). <i>Sold / off-market homes only.</i></li><li><b>Nearby buildings</b> — <i>for-rent listings only</i>, from Zillow's own rental detail document: the neighbouring <i>buildings</i> with their price ranges and bedroom groups (a rental's neighbours are buildings, not comparable houses). <b>Picking either of these for a rental means that rental cannot also return climate risk</b> — the two live in different Zillow documents and the Actor makes one request per property, so it uses whichever your selection needs.</li><li><b>Climate risk</b> — fire, flood, air, wind and heat: risk score, label, FEMA zone and whether Zillow recommends insurance. Added as columns, not rows. <i>Not available for sold homes</i> — Zillow's off-market detail does not carry it.</li><li><b>Walk / bike / transit scores</b> plus the local legal protections (LGBT, source of income, housing voucher) for the listing's jurisdiction. Columns.</li><li><b>MLS attribution</b> — MLS id and name, the disclaimer text, the must-display flags a redistributor has to honour, and when Zillow last checked the listing. Columns.</li><li><b>Views & saves</b> — page views, favourites and time on Zillow. Columns, and for-sale listings only.</li></ul>New construction has no tax history and sold homes have no list price, so a section can legitimately come back empty.

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

Route requests through a proxy. Residential (US) is the default and recommended — Zillow's PerimeterX blocks datacenter IPs.

## Actor input object example

```json
{
  "mode": "search",
  "q": "Austin, TX",
  "useCodes": [
    "uc_sfrcondo"
  ],
  "homeTypes": [],
  "sortBy": "daysOn",
  "sortDescending": false,
  "zpids": [],
  "north": "",
  "south": "",
  "east": "",
  "west": "",
  "maxTileDepth": 4,
  "includeDetail": false,
  "maxItems": 100,
  "includeSections": [],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `results` (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 = {
    "q": "Austin, TX",
    "zpids": [],
    "north": "",
    "south": "",
    "east": "",
    "west": "",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("rl1987/zillow-api-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 = {
    "q": "Austin, TX",
    "zpids": [],
    "north": "",
    "south": "",
    "east": "",
    "west": "",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("rl1987/zillow-api-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 '{
  "q": "Austin, TX",
  "zpids": [],
  "north": "",
  "south": "",
  "east": "",
  "west": "",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call rl1987/zillow-api-scraper --silent --output-dataset

```

## MCP server setup

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