How do you get Zillow listings and Zestimate-style property data via API?
Call ReefAPI's zillow search action with a location and read listings with price, beds, baths, sqft, coordinates and days on market back as JSON, then property_detail on the zpid for the Zestimate, price history and description. The two surfaces carry different fields, and neither is a superset of the other.
This guide demonstrates the real Zillow API engine with a captured response from . The example is only published because the engine passed the SEO snapshot gate.
Real-estate analytics, lead generation, valuation modelling and market monitoring.
Call the live endpoint
- 1
Search by location, coordinates or URL
search takes a place string, search_by_coordinates takes an explicit map_bounds box, and search_by_url takes a Zillow URL you already have. All three accept status ('for_sale', rentals, sold) and the usual price, beds and home_type filters.
- 2
Read count, map_pins and total_reported together
41 detailed rows, 502 pins and 5,721 reported matches came from one call. The pins are the cheap way to cover a whole city geographically.
- 3
Subdivide the box rather than paginating deeper
One map box tops out around 860 list results. fetch_all tiles the bounding box for you; meta.map_bounds tells you what box you were given so you can split it yourself.
- 4
Call property_detail for valuation
zestimate_usd was populated on 6 of 41 search rows and on the detail call. price_history, tax_history, schools and climate risk exist only there.
- 5
Keep days_on_market from the search row
It was present on all 41 search results and null on detail. If you drop the search row after enriching, you lose it.
Copy the request
These snippets use the captured request params for zillow/v1/search.
curl -X POST https://api.reefapi.com/zillow/v1/search \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"location":"Austin, TX"}'import requests
r = requests.post(
"https://api.reefapi.com/zillow/v1/search",
headers={"x-api-key": REEF_KEY},
json={
"location": "Austin, TX"
},
)
print(r.json()["data"])const res = await fetch("https://api.reefapi.com/zillow/v1/search", {
method: "POST",
headers: {
"x-api-key": process.env.REEF_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
"location": "Austin, TX"
}),
});
const { ok, data, meta, error } = await res.json();Ask your MCP-connected assistant: call reefapi.zillow.search with {"location":"Austin, TX"}.Captured output from ReefAPI
Captured on UTC. The response below is the committed snapshot, including the API envelope and metadata.
{
"method": "POST",
"url": "https://api.reefapi.com/zillow/v1/search",
"headers": {
"x-api-key": "$REEF_KEY",
"content-type": "application/json"
},
"body": {
"location": "Austin, TX"
}
}{
"ok": true,
"meta": {
"api": "zillow",
"endpoint": "search",
"mode": "live",
"latency_ms": 5517,
"record_count": 41,
"bytes": 1119692,
"cache_hit": false,
"completeness_pct": 100,
"completeness_fields": 12,
"completeness_basis": "search",
"location": "Austin, TX",
"map_bounds": {
"west": -98.246758,
"east": -97.346758,
"south": 29.93245,
"north": 30.63245
},
"filters_applied": null,
"charged_credits": 2,
"version": "1.5.0"
},
"data": {
"items": [
{
"zpid": 29558116,
"source": "zillow",
"url": "https://www.zillow.com/homedetails/12600-Oro-Valley-Cv-Austin-TX-78729/29558116_zpid/",
"list_price_usd": 549000,
"price_display": "$549,000",
"status": "FOR_SALE",
"status_text": "Active",
"property_type": "SINGLE_FAMILY",
"beds": 4,
"baths": 3,
"sqft": 2472,
"lot_sqft": 9199.872,
"zestimate_usd": null,
"rent_zestimate_usd": null,
"tax_assessed_value_usd": 496142,
"days_on_market": 6,
"address_line": "12600 Oro Valley Cv",
"city": "Austin",
"state_code": "TX",
"postal_code": "78729",
"address": "12600 Oro Valley Cv, Austin, TX 78729",
"latitude": 30.444155,
"longitude": -97.765144,
"broker_name": "Compass RE Texas, LLC",
"is_showcase": true,
"photos": [
"https://photos.zillowstatic.com/fp/484019a1bc47e283f3cb3d6f79663997-p_e.jpg"
],
"is_building": false,
"identifier_type": "zpid"
},
{
"zpid": 29596334,
"source": "zillow",
"url": "https://www.zillow.com/homedetails/12712-Twisted-Briar-Ln-Austin-TX-78729/29596334_zpid/",
"list_price_usd": 680000,
"price_display": "$680,000",
"status": "FOR_SALE",
"status_text": "Active",
"property_type": "SINGLE_FAMILY",
"beds": 5,
"baths": 4,
"sqft": 3154,
"lot_sqft": 7871.292,
"zestimate_usd": null,
"rent_zestimate_usd": null,
"tax_assessed_value_usd": 646643,
"days_on_market": 6,
"address_line": "12712 Twisted Briar Ln",
"city": "Austin",
"state_code": "TX",
"postal_code": "78729",
"address": "12712 Twisted Briar Ln, Austin, TX 78729",
"latitude": 30.453896,
"longitude": -97.78103,
"broker_name": "Keller Williams Realty",
"is_showcase": true,
"photos": [
"https://photos.zillowstatic.com/fp/2b100a3981b1fa014c2b3895b4c60abd-p_e.jpg"
],
"is_building": false,
"identifier_type": "zpid"
},
{
"zpid": 185179147,
"source": "zillow",
"url": "https://www.zillow.com/homedetails/12500-Turkey-Ridge-Ct-Austin-TX-78729/185179147_zpid/",
"list_price_usd": 524900,
"price_display": "$524,900",
"status": "FOR_SALE",
"status_text": "Active",
"property_type": "SINGLE_FAMILY",
"beds": 4,
"baths": 2,
"sqft": 2314,
"lot_sqft": 11835.25,
"zestimate_usd": null,
"rent_zestimate_usd": null,
"tax_assessed_value_usd": 490354,
"days_on_market": 7,
"address_line": "12500 Turkey Ridge Ct",
"city": "Austin",
"state_code": "TX",
"postal_code": "78729",
"address": "12500 Turkey Ridge Ct, Austin, TX 78729",
"latitude": 30.444427,
"longitude": -97.76878,
"broker_name": "Keller Williams Realty",
"is_showcase": true,
"photos": [
"https://photos.zillowstatic.com/fp/a20103b3130fef4ef77ac2123aa4792d-p_e.jpg"
],
"is_building": false,
"identifier_type": "zpid"
}
],
"count": 41,
"map_pins": [
{
"zpid": "29558116",
"latitude": 30.444155,
"longitude": -97.765144,
"price_display": "$549,000"
},
{
"zpid": "29596334",
"latitude": 30.453896,
"longitude": -97.78103,
"price_display": "$680,000"
},
{
"zpid": "185179147",
"latitude": 30.444427,
"longitude": -97.76878,
"price_display": "$524,900"
}
],
"total_reported": 5723,
"pages_fetched": 1,
"tiles_fetched": null,
"stop_reason": "max_pages",
"partial": false,
"location": "Austin, TX",
"status": "for_sale",
"filters_applied": null,
"harvest_note": "SRP caps ~860 listResults/~500 map pins per box; fetch_all bbox-tiles to exceed (faceted-search-pagination)."
}
}Why this is hard manually
The intuition that detail is search-plus-more is wrong on Zillow, in both directions. days_on_market was populated on all 41 of our Austin search results (60 days, 6 days, and so on) and came back null on the property_detail call. The Zestimate went the other way: populated on 6 of 41 search rows and present on detail. tax_assessed_value_usd appeared on 36 of 41 search rows and has no equivalent field on detail at all, which carries tax_history instead. Whichever call you skip, you lose something.
The second thing to plan around is that a map-based search has a ceiling that has nothing to do with how many homes match. Our 'Austin, TX' for-sale search reported total_reported 5,721 and returned 41 detailed items - because a single map box serves roughly 860 list results and about 500 map pins before it stops, and one page of that box is 41 rows. Scraping deeper is not a matter of asking for a higher page number.
The third is that 'Zestimate' and 'list price' are two different claims about the same house, and the interesting number is the gap. On 13109 Sinton Ln the list price was 539,900 against a Zestimate of 535,500, with a price_change_usd of -9,100 dated 2026-08-13. That combination - listed above the estimate, already cut once - is a story no single field tells.
Why ReefAPI solves it
Search returns three different quantities and you need all three to know what you have. count is the detailed rows you got (41), map_pins is the lightweight markers in the same box (502), and total_reported is Zillow's own count of matches (5,721). If you only need location and price for a heat map, the 502 pins are twelve times the coverage for the same call. stop_reason names why the run ended - 'max_pages' in our case - and partial tells you whether it terminated early.
The bounding box is echoed back so any search is reproducible. We passed location 'Austin, TX' and meta.map_bounds came back as west -98.246758, east -97.346758, south 29.93245, north 30.63245. Store that with your results and you can re-run the identical geography later, or hand it to search_by_coordinates to subdivide it. fetch_all does that subdivision for you when one box is not enough.
property_detail is where valuation lives: zestimate_usd, zestimate_history, rent_zestimate_usd, last_sold_price_usd and last_sold_date, price_change_usd with price_change_date, price_history, tax_history, schools, walk_transit_score, climate_risk, mls_id, parcel_id and reso_facts. Not all of them are filled on every property - our test returned 18 of 50 fields null, which is exactly why meta reports completeness_pct 80 over completeness_fields 20 with completeness_basis 'detail'. That percentage is measured against a core set, not against every field in the object.
The same completeness fields on search read 100 percent over 12 fields with basis 'search'. Two responses can both say 100 and mean very different things, so read completeness_basis and completeness_fields alongside the number rather than treating the percentage as absolute.
lot_sqft comes back with decimals (6046.128, 24668.03) because Zillow publishes lot size in acres and we convert. Rounding it to an integer is safe; assuming it was an integer to begin with is not. beds and baths are floats too, which is how a half-bath is represented.
Fourteen actions share the engine, and several of them replace whole workflows: comps returns comparable homes around a zpid with a radius you set, market_trends takes a location, home_value_chart returns a Zestimate time series over a window you choose, agent and agents_by_location cover the listing side, and new_listings takes a changed_after timestamp so a monitor can ask only for what appeared since its last run. Search answered in about 8.5 seconds, detail in 9.4 - these are heavy pages.
Questions developers ask
Do I need a Zillow API key or partner agreement?
No. You send a ReefAPI key. There is no Zillow developer programme, partner approval or Bridge API contract involved.
Why is the Zestimate missing from most search results?
Because Zillow's map cards mostly do not carry it - it was populated on 6 of our 41 rows. property_detail returns it reliably, along with the rent Zestimate and the Zestimate history.
Why is days_on_market null on the detail call?
Because that field lives on the search card, not the property page. It was populated on all 41 of our search results and null on detail. Carry it forward from the search row rather than expecting detail to repeat it.
The search says 5,721 matches but I got 41 rows. Where are the rest?
A single map box serves roughly 860 list results and about 500 map pins, and one page is 41 rows. total_reported is Zillow's match count, not a retrievable set. Raise max_pages, use fetch_all to tile the box, or narrow the geography.
What are map_pins for?
They are lightweight markers for every property in the box - 502 of them against 41 detailed items in our run. If you need geographic coverage rather than full records, they are far more of the market per call.
completeness_pct says 100 on search and 80 on detail. Is search better data?
No - the two are measured against different field sets. Search reports 100 over 12 core fields, detail reports 80 over 20. Read completeness_basis and completeness_fields with the number; a percentage on its own is not comparable across endpoints.
Can I monitor only what changed?
Yes. new_listings takes a changed_after timestamp plus a region or postal prefix, which is much cheaper than re-searching a metro and diffing it yourself.