Blog/Local data
11 min read

Zillow Scraper: What One Listing Record Carries

A live search returns zpid, unformatted price, beds and the detail URL. Five endpoints split by intent, and the sold one is the underrated half.

Zillow Scraper: What One Listing Record Carries

Copy this line to your agent to pull property listings for an area without writing a scraper.

set up https://monid.ai/SKILL.md and use zillow /search_homes_for_sale to pull listings for a location

Search for a Zillow scraper and you get store listings for one actor, which describes what it does and never shows you a response. That matters because the shape of the record decides what you can build, and because Zillow is not one dataset: for sale, for rent, recently sold, agents and mortgage rates are five separate questions that refresh at five different speeds. This guide shows a live record and sorts the five, running through Monid, the OpenRouter for agent tools.

What does a Zillow scraper return?

A structured listing record with far more in it than a rendered page shows, and two fields that decide whether the data is usable in code.

One live record, field by field

Running zillow/search_homes_for_sale against Austin, Texas on 2026-08-31 returned listings carrying zpid, id, palsId, rawHomeStatusCd, marketingStatusSimplifiedCd, statusType, statusText, imgSrc, hasImage, detailUrl, countryCurrency, price, unformattedPrice, address, addressStreet, addressCity, addressState, addressZipcode, isUndisclosedAddress, beds and more.

Four are worth calling out.

zpid is Zillow's own property identifier and it is the join key for everything else. It survives across searches and it is what you deduplicate on, because the same property appears in overlapping area searches and the address strings will not match reliably.

unformattedPrice is the number. price is the string, $750,000, which is what a page scrape gives you and what somebody then has to parse. Having both means the parsing has already been done and done consistently, which sounds trivial until you have written the regex that handles ranges, "contact agent" and a currency symbol.

addressStreet, addressCity, addressState, addressZipcode arrive split. A page scrape gives you one address string and leaves you to guess where the city ends. Pre-split components are the difference between joining to your own data and not.

isUndisclosedAddress is a boolean nobody expects and it matters: some listings deliberately withhold the street address, and a pipeline that treats a missing address as a parse failure will keep retrying a record that is working exactly as intended.

What the store card does not show

The Apify listing that holds page one for this term is a product card. It says the actor scrapes Zillow. It does not show you unformattedPrice, it does not mention isUndisclosedAddress, and it cannot tell you whether the fields support the thing you are about to build.

📖 See also Google Maps Scraper Alternatives: Building a Local Lead List

Why are there five endpoints instead of one?

Because "Zillow data" is five questions and they refresh at completely different speeds, so bundling them would mean paying the fastest cadence for the slowest data.

For sale: changes daily

New listings, price cuts, status changes to pending. This is the surface people mean and it is the one that genuinely rewards a daily pull, because a price cut two days late is a lead somebody else already called.

For rent: changes daily and differently

Separate inventory, separate seasonality, separate buyers of the data. Rental yield work and sales comparables are different products and merging the two feeds produces a table where half the rows mean something else.

Recently sold: changes weekly and is worth more

Closed transactions. This is the section after next, because it is the one that gets skipped and the one that answers the questions the others cannot.

Agents: changes monthly

Who is listing in an area, with volume. That is a lead list rather than a property dataset, and treating it as property data is a category error.

Mortgage rates: changes constantly and is not property data at all

A market rate, not a listing. It is in the same catalog because it is on the same site, and it belongs in a different table.

The five, side by side

EndpointWhat changesSensible cadenceThe job it serves
Homes for saleDailyDailyLead generation, price monitoring
Homes for rentDailyDailyYield analysis, rental comps
Recently soldWeeklyWeeklyValuation, settling arguments
AgentsMonthlyMonthlyProspecting agents, not properties
Mortgage ratesContinuouslyAs neededAffordability modelling

The same per-surface reasoning, applied where the surfaces are job boards rather than property types, is in Job Scraping Software: There Is No Single Job Feed.

The column that saves money is the third. Pulling recently sold daily costs seven times what pulling it weekly costs and tells you the same thing, because the data itself only moves weekly.

How do you pull listings for an area?

Three steps, two of them free.

For agents

Grab an API key at app.monid.ai, then paste this to your agent and hand it the key:

set up https://monid.ai/SKILL.md

It learns the whole discover, inspect, run workflow itself. More in the agent quickstart.

For humans

npm install -g @monid-ai/cli
monid keys add -k <your-api-key> -l main

Step 1. See which property surfaces are covered

What it does. Tells you in one command which sites have purpose-built endpoints and which would need generic extraction, which is the difference between a maintained parser and one you own.

The call.

monid discover -q "zillow real estate listings"
monid discover -q "airbnb listings"

What comes back. Running exactly this on 2026-08-31 returned five dedicated Zillow endpoints and a homes.com pair. The second query returned nothing for Airbnb: only the rental endpoints from Zillow and homes.com came back, which tells you immediately that Airbnb is a generic-extraction job rather than a purpose-built one.

An empty result is a real answer and it took five seconds to get. It is the same check that removed a whole cluster from this queue the day before, described in Web Scraping Tools: Which Kind Do You Actually Need?.

What it costs. Nothing. Discovery never bills.

Step 2. Pull one area, small

What it does. Produces real records so you can judge the fields against your use.

The endpoints. zillow/search_homes_for_sale, billed per call.

The call.

monid run -p zillow -e /search_homes_for_sale -w --query '{"location": "Austin, TX"}'

What comes back. The field set above, one record per listing, with the price both formatted and numeric and the address pre-split.

What it costs. A few cents per call, billed per call rather than per listing, which is the shape that matters here: a broad area search costs the same as a narrow one. That inverts the usual advice. Widen the query rather than running several narrow ones. Current figures at monid.ai/tools.

Step 3. Key on zpid, not on address

What it does. Handles the deduplication problem before it becomes a data-quality problem.

The call. No endpoint. A decision:

seen = {}
for listing in results:
    seen[listing["zpid"]] = listing   # last write wins, same property

There is a worked local-data pipeline with the same deduplication problem, keyed on a place id rather than a property id, in Google Maps Reviews Without a Places API Key.

What comes back. One row per property. Overlapping area searches return the same home repeatedly, address strings vary between records, and isUndisclosedAddress means some rows have no address to key on at all. zpid is stable and present on every record, which is what makes it the key.

📖 See also Automate Google Maps Business Listings Into a Table

Give this to your agent

$Set up https://monid.ai/SKILL.md, and then use Monid to pull homes for sale in Austin TX and Round Rock TX, deduplicate on zpid, and give me a CSV of address, beds, unformatted price and detail url sorted by price.

Why is recently sold the useful one?

Because asking prices are opinions and sold prices are transactions, and only one of the two settles an argument.

What a for-sale feed can and cannot tell you

It tells you what sellers hope for. That is genuinely useful for lead generation, for spotting price cuts, and for knowing what is available right now. It is not a valuation, because the distribution of asking prices includes every listing that will sit unsold for six months.

What sold data adds

A closed price is a number two parties agreed on. Comparable sales are the basis of every valuation method that works, and they are the only way to measure the gap between asking and achieving in an area, which is the single most informative number in a local market.

Why almost nobody pulls it

Two reasons, both mundane. It is less visible in the interface, so it is less top-of-mind. And the questions it answers are analytical rather than operational, so it loses to the feed that produces leads this week.

That is a real trade-off if leads are the product, and the same asking-versus-achieved gap shows up wherever a listing price meets a transaction price, including the Amazon side of ecommerce data. If anything you build makes a claim about value, sold data is not optional and no amount of asking-price history substitutes for it.

The cadence saving

Sold data updates as transactions close and record, which is weekly at best. Pulling zillow/search_recently_sold daily is paying seven times for one week of change. Weekly is the correct cadence and it is the cheapest section of this whole pipeline.

Which endpoint should I use for which job?

EndpointWhat it doesInputOutputBest forBilling
zillow/search_homes_for_saleActive sale listingsA locationListing records with zpid and numeric priceLeads, price monitoringPer call
zillow/search_recently_soldClosed transactionsA locationSold recordsValuation and compsPer call
zillow/search_homes_for_rentRental inventoryA locationRental recordsYield and rental compsPer call
zillow/search_agentsAgents by areaA locationAgent recordsProspecting agentsPer call
homes.com/search_properties_for_saleA second sourceA locationListing recordsCross-checking coveragePer call
context.dev/web/scrape/markdownAny property site with no endpointA URLMarkdown, metadataAirbnb and the long tailPer call

Every row was verified with monid inspect on 2026-08-31. The table gives billing shape rather than figures; shape drives design and current numbers live on monid.ai/tools.

Everything here is per call, which is unusual in this catalog and changes the strategy. Per-call billing means the size of the answer is free, so the optimisation is fewer, wider queries rather than many narrow ones, and the cost driver is how many areas and how often rather than how many listings.

When should you not scrape Zillow?

Four cases, and the first is the one professionals land on.

You are licensed and can get MLS data. If you hold a licence or work with somebody who does, MLS through an IDX feed is the authoritative source: it is what Zillow itself is downstream of, it is more complete, and it carries fields no portal exposes. Anything here is a lossy copy by comparison.

You need a legal record. County recorder data is the transaction of record. Portals are convenient and they are not the register, and for anything with a compliance dimension that distinction matters.

You need national coverage guaranteed. Portal coverage varies by market and by listing agreement, and a missing property is indistinguishable from a property that does not exist. If completeness is the requirement, buy a compiled dataset rather than assembling one, and the trade-offs there are in B2B Data Providers: Buy the Dataset or Call the API?.

The site has no endpoint. Airbnb is the example from this article's own coverage check. There the honest answer is generic extraction plus your own parser, with everything that implies about maintenance, which we set out in Browser Automation When the Site Has No API.

And the disclosure: this is Monid's blog and we sell per-call access to these endpoints. If you can get MLS, get MLS. We are the answer when you cannot, or when the job is small enough that a licence and a feed integration would be absurd.

Conclusion

A Zillow scraper is five products, and choosing between them is the decision that matters more than choosing a vendor. For sale answers what is available, for rent answers a different market, agents is a lead list wearing property clothes, and recently sold answers the valuation questions none of the others can. They also change at different speeds, and matching the pull cadence to the data's own cadence is the cheapest saving in the whole pipeline.

The field worth carrying past this is zpid. Address strings vary between records, some listings deliberately withhold the address entirely, and overlapping area searches return the same property repeatedly. A stable identifier on every record is what makes the difference between a table of properties and a table of sightings.

Free next step: run monid discover -q "zillow real estate listings" and see the five endpoints side by side with their prices. It costs nothing, and seeing recently sold sitting next to homes for sale is usually the moment people realise they were about to build the wrong half. Start at monid.ai.

FAQ

Is MLS data better than a Zillow scraper?

Yes, materially, if you can get it. MLS through an IDX feed is the source portals are downstream of, it carries fields that never reach a public listing page, and it comes with a licence rather than a question mark. The catch is the gate: it requires a licence or a licensed partner and an integration that is not an afternoon's work. Portal data is the answer when that gate is closed to you or when the job is too small to justify passing through it.

How often should you re-pull property listings?

Match the cadence to the surface rather than picking one for everything. For-sale and for-rent genuinely move daily, so a daily pull earns its cost when leads are the product. Recently sold updates weekly at best and pulling it daily is paying seven times for the same data. Agents change monthly. Running one cadence across all of them is the most common avoidable cost in this category, and every endpoint here bills per call, so the cadence is the whole bill.

Can you use the same data for rentals and sales?

No, and merging them is a mistake that produces plausible-looking nonsense. They are separate inventories with separate endpoints, separate seasonality and separate meanings for price: one is a monthly rent, the other a purchase price. If you need both, keep them in separate tables and join only where you have deliberately decided what the join means, such as computing a yield for a specific property.

Is the Zillow price field usable as a number?

Both forms come back. price is the display string with a currency symbol and thousands separators; unformattedPrice is the integer. Use the second and keep the first only if you are rendering it back to a person. This is worth checking on any property source you evaluate, because a source that returns only the formatted string is handing you a parsing job that has to handle ranges, missing values and "contact agent", and that parser will be wrong in a way you find out about later.

Last updated August 2026.

zillow scraperreal estate apiproperty datalocal datalistings