Pull only the Zillow listings that changed since your last sync
The Zillow API returns US real-estate data — for-sale, rental and sold listings — as clean JSON.
14 active endpoints, on 1, 2 and 3 credit tiers.
- POST/zillow/v1/new_listings
- POST/zillow/v1/search
- POST/zillow/v1/search_by_coordinates
- POST/zillow/v1/search_by_url
- POST/zillow/v1/property_detail
- POST/zillow/v1/comps
- POST/zillow/v1/sold
- +7 more
What Zillow endpoints does ReefAPI ship?
14 live read endpoints. Read-only data API: no writes, no account actions, no dashboard access on the target site.
Zillow API
3 of 14 endpoints, ready to run
The change feed: homes that entered, sold or came up for rent in the last couple of days. Pass changed_after and you get only what is new since your last run.
{ "ok": true, "meta": { "api": "zillow", "endpoint": "new_listings", "mode": "live", "latency_ms": 1721.1, "record_count": 10, "cache_hit": false }, "data": { "listings": [ { "zpid": "80530081", "url": "https://www.zillow.com/homedetails/8711-Sandusky-Ct-Tomball-TX-77375/80530081_zpid/", "address": "8711 Sandusky Ct Tomball TX 77375", "region": "TX", "postal_code": "77375", "country": "US", "changed_at": "2026-08-28T07:34:00Z" }, { "zpid": "27165703", "url": "https://www.zillow.com/homedetails/821-Dublin-Dr-APT-249-Richardson-TX-75080/27165703_zpid/", "address": "821 Dublin Dr APT 249 Richardson TX 75080", "region": "TX", "postal_code": "75080", "country": "US", "changed_at": "2026-08-28T07:26:00Z" }, { "zpid": "50732612", "url": "https://www.zillow.com/homedetails/2800-Gulf-Blvd-UNIT-307-South-Padre-Island-TX-78597/50732612_zpid/", "address": "2800 Gulf Blvd UNIT 307 South Padre Island TX 78597", "region": "TX", "postal_code": "78597", "country": "US", "changed_at": "2026-08-28T07:26:00Z" } ], "category": "for_sale", "window": { "oldest": "2026-08-25T22:11:00Z", "newest": "2026-08-28T08:00:00Z", "is_recent_window": true } } }
How the Zillow API works
Zillow is a normal ReefAPI surface — the same four rules that hold for every other engine on the key.
No OAuth app, no request signing, no per-site account. One key covers all 440 engines.
Every route is a POST with a JSON body. Parameters are validated against the published schema before anything is charged.
Credits, not seats. Failed and blocked calls are never charged, and cache hits cost nothing.
One envelope everywhere. meta carries latency_ms, record_count and the endpoint that answered.
Sync a market daily without re-reading it
The expensive way to watch a market is to sweep it every morning. The cheap way is to read the change feed, keep the newest timestamp it reports, and hand that timestamp back tomorrow.
{"category": "for_sale", "region": "TX", "country": "US", "max_results": 500}Store data.window.newest from the response. The row is identity only — zpid, canonical url, address, region, postal code, changed_at.
{"category": "for_sale", "region": "TX", "country": "US", "changed_after": "…"}Same feed, only the rows that appeared after the timestamp you stored. On our run an hour-wide cutoff took a 49,975-row feed down to 216.
{"zpid": "…"}Three credits, and only for the ones you care about. This is where price, beds, sqft, Zestimate and the dated price history live.
You pay 2 credits per 500 feed rows plus 3 per home you actually open, instead of sweeping a whole metro to find the dozen homes that moved.
curl -X POST https://api.reefapi.com/zillow/v1/new_listings \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"category":"for_sale","max_results":5}'{
"ok": true,
"data": { … },
"meta": {
"api": "zillow",
"endpoint": "new_listings",
"mode": "live",
"latency_ms": …,
"record_count": …
},
"error": null
}Vocabularies and types that change between actions
Zillow's filter words are not Zillow's response words, and the same field can be a float in one action and an int in another. Everything below was measured on Austin, TX inventory — 123 listings across three search pages plus a property_detail on zpid 464181906.
| Field | What comes back | Watch out for |
|---|---|---|
| home_type (filter) | house · condo · townhouse · multi_family · land · manufactured · apartment | these are the words you send |
| property_type (response) | SINGLE_FAMILY · CONDO · TOWNHOUSE · MULTI_FAMILY · MANUFACTURED | these are the words you get back — 'house' becomes SINGLE_FAMILY, and matching the filter string against the response never hits |
| status | FOR_SALE (uniform across all 123 measured rows) | the enum. Filter on this. |
| status_text | Active · House for sale · Home for sale · Zillow Preview | free display text, four distinct values under one status. Never filter on it. |
| beds / baths / sqft | floats from search (4.0, 3.0, 2586.0), ints from property_detail (4, 3, 2586) | same property, two numeric types |
| zpid | integer in items[], string in map_pins[] | both in the same response — normalize before you join them |
| lot_sqft | 24668.03 — square feet, always | 43,560 sqft = 1 acre. That measured lot is 0.57 acres, not 24,668 of anything a customer recognizes. |
| days_on_market | present on both since 2026-09-24 — search 574/574, property_detail 13/14 | it used to be null on property_detail and the docs told you to use search; both carry it now |
| zestimate_usd | null on 104 of 123 measured search rows | not a search limitation — see the FAQ below |
A search page is atomic: asking for max_results 3 returned 41 listings, because Zillow serves ~41 per page and the page comes back whole. max_results caps across pages, not within one. Requesting 200 across 3 pages returned 123.
What Zillow covers, what changes, and the two places the number is missing
Measured on 2026-08-28 against Austin TX, Seattle WA and the national feeds, and re-measured on 2026-09-24 across eight metros. Some of these lines go against us; a real-estate product built on a field that is quietly empty is worse than one that never had it.
The United States, and Canada in the same feed — a for-sale sweep returned an Alberta address in the first ten rows, tagged country CA. Statuses are for-sale, for-rent and recently-sold. Pass country to keep a US sweep American; region takes a two-letter state or province and the codes overlap, so pass both.
new_listings reads Zillow's own published inventory drop rather than paging the site. The response says how big the file was and how wide the window is: 49,975 rows in the for-sale feed and 49,959 in the sold one on our run, with oldest and newest timestamps about three days apart and the newest stamp roughly sixteen hours old. for_sale, sold and for_rent are recent-window feeds; for_sale_by_owner, auction, pending and other are the whole category rather than a delta, and the schema says so.
Hand back the previous run's window.newest and the feed keeps only rows changed strictly after it. One hour of cutoff took 49,975 rows down to 216 matched. region plus country narrowed the same feed to 3,836 for Texas. Both filters run before paging, so pagination.total_matched is the honest size of your delta.
zpid, canonical url, address as Zillow writes it, region, postal code, country, changed_at. No price, no beds, no status detail. That is the trade: you find out what moved for 2 credits per 500 rows, then pay 3 credits per home to find out what it moved to.
In twelve non-disclosure states — AK, ID, KS, LA, MS, MO, MT, NM, ND, TX, UT, WY — what a home sold for is not a public record, so Zillow has no number to publish and neither do we. Measured 2026-09-24, 41 recently-sold rows per metro: priced 41/41 in Columbus OH, Charlotte NC, Phoenix AZ and Denver CO, but 0/41 Austin TX, 1/41 Houston TX, 1/41 Salt Lake City UT, 1/41 Jackson MS. We checked it against Zillow's own record rather than assuming: on nine TX/UT/MS homes every Sold event in Zillow's price history, back to 2010 and including ones it sources from Public Record, carries no price, while three Columbus OH homes carry one on every Sold event back to 1996. So every unpriced row now tells you which case it is in price_unavailable_reason — 'non_disclosure_state' means the figure does not exist and retrying is pointless; 'not_published_by_source' means a disclosure state where this particular card has none, and property_detail may have it. sold_date, tax_assessed_value_usd and zestimate_usd fill everywhere. For a sale price in a non-disclosure state, no source has one.
Against us. status=for_rent returned rows whose zpid is a coordinate pair — 30.43234--97.6959 — with status_text holding a building name. Across 82 rows in two cities, price was present on 4, and beds, baths, sqft, property_type and days_on_market were null on all 82. It is usable as a map of what rents where; it is not a price feed. rent_zestimate on the for-sale grid is the rental number that does come back.
This used to be against us and is not any more. property_detail returned dated price points with event null and source null on every row, and tax_history, schools and the listing agent came back empty — the page said Zillow did not publish them. It did; we were reading the wrong surface. Fixed 2026-09-24: on a Denver home the history came back with all 30 rows carrying both the event ('Listed for sale') and the source ('REcolorado as distributed by MLS GRID'), and across 14 homes tax_history, schools, price_history and the listing agent filled on 14 of 14, with climate risk and the Zestimate series added. When that second read fails on a call, the response says so in meta.fields_not_retrieved instead of handing you a silent empty array.
A city name, a ZIP, a map viewport and a pasted Zillow search URL are all accepted, and the response echoes the bounding box it used. Same afternoon, same status: Austin TX reported 5,757 matches, the ZIP 78704 reported 344, a 0.1-degree box reported 1,501, and the pasted URL resolved to the city and reported 5,759. The URL is the safest input if your user copied it from a browser; the box is the only one you control exactly.
Austin reported 5,757 for-sale homes. With days_on_max 1 it reported 89, and every row that came back had days_on_market 0. If all you want is today's arrivals in one metro, this is cheaper than the change feed and gives you the whole row rather than just the identity.
About 41 listings per page, and the response carries total_reported, pages_fetched and a stop_reason, plus a note saying where Zillow's own result cap sits and that fetch_all tiles the box to get past it. search, sold, comps and market_trends are 2 flat credits, property_detail 3, autocomplete and the walk/transit and home-value charts 1, and new_listings is 2 credits per 500 rows with a 2-credit floor.
What people build with Zillow
The jobs this data is most often used for.
endpoints
credits per call
Real-estate investors call search then comps to find and value properties in a target market.
Property dashboards use property_detail and the Zestimate to enrich a listing with price and rent estimates.
Lead-gen tools use agent and sold to identify active agents and recent transactions in an area.
What Zillow data costs
The cheapest call here is 1 credit, so $15/mo (Pro) buys 10,000 of them — $1.50 per 1,000 credits. Credits roll over and never expire, and failed or blocked calls are not charged.
Full pricing →- 1,000 free credits on signup, no card
- One key, all 440 APIs, one credit pool
- Failed and blocked calls are never charged
- Credits roll over and never expire
Call it in two lines
Sign up, get 1,000 credits and one key that works on every engine. Then this is the whole protocol.
curl -X POST https://api.reefapi.com/zillow/v1/new_listings \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"category":"for_sale","max_results":5}'import requests
r = requests.post(
"https://api.reefapi.com/zillow/v1/new_listings",
headers={"x-api-key": REEF_KEY},
json={
"category": "for_sale",
"max_results": 5
},
)
print(r.json()["data"])Have a question? We got answers.
The questions people actually ask before wiring up Zillow.
Get a free key →I asked for 3 results and got 41 — is that a bug?▾
No, it is how the paging works and we would rather over-deliver than silently drop rows. Zillow's result page holds roughly 41 listings and it is fetched whole; max_results caps how many pages get fetched, not how many rows come out of one. A measured request for max_results 3 returned 41; max_results 200 with max_pages 3 returned 123. You are not charged per row, so slice the array to what you need.
Why is zestimate_usd null on most listings?▾
On the search grid, because Zillow does not put a Zestimate on every card and we return null rather than an estimate of our own — measured, only 19 of 123 Austin search rows carried one, with newer construction and just-listed homes the usual gaps. property_detail is the place to ask again: it reads Zillow's valuation surface directly and returned zestimate_usd on 12 of 14 homes and the full zestimate_history series (35 to 121 monthly points) on 14 of 14, measured 2026-09-24. If a home still has no Zestimate after that, Zillow genuinely publishes none for it, so plan for the null rather than retrying.
Why does filtering on home_type return properties whose property_type does not match?▾
Because they are two different vocabularies. You filter with lowercase words — house, condo, townhouse, multi_family, land, manufactured, apartment — and the response speaks SCREAMING_SNAKE: SINGLE_FAMILY, CONDO, TOWNHOUSE, MULTI_FAMILY, MANUFACTURED. 'house' and 'SINGLE_FAMILY' are the same thing under two names, so a string comparison between what you sent and what you got will always fail. Map them explicitly.
What is the difference between status and status_text?▾
status is the machine enum and status_text is the label Zillow prints on the card. Across 123 measured rows every single status was FOR_SALE while status_text split four ways: Active (118), House for sale (2), Home for sale (2) and Zillow Preview (1). Those are not different states — they are different phrasings, plus one marketing badge. Branch on status; show status_text.
What unit is lot size in, and how do I get acres?▾
Square feet, always, including the lot_min and lot_max filters. Divide by 43,560 for acres — a measured lot_sqft of 24,668.03 is 0.57 acres. This trips people up because US listing sites present large lots in acres and small ones in square feet, so a filter written as lot_min: 1 returns essentially everything rather than one-acre-plus properties.
How do I get more than Zillow's result cap for an area?▾
Set fetch_all to true. Zillow's own search caps out around 860 results for any one query no matter how you page, so a busy metro is truncated well before you have the market. fetch_all works around that at the cost of runtime — a plain 3-page search already measured 6.8 seconds. The cheaper alternative for wide areas is to split the query geographically by ZIP and merge, which also keeps each response small enough to process incrementally.
What does property_detail add over a search row, and what does it drop?▾
It adds price_per_sqft_usd, year_built, price_change_usd with its date, dated price_history with the event and source on each row, tax_history, schools with GreatSchools ratings, climate_risk, the Zestimate series, county, parcel_id, property_tax_rate, the full description, every photo with a count, and the listing agent block. Measured 2026-09-24 over 14 homes: tax_history, schools and price_history now fill on 14 of 14 and the listing agent on 14 of 14. Those arrays were empty for a while and the docs blamed Zillow for it — that was our bug, not Zillow's: the blocks had moved to a second request that we were not making, and we make it now. If that second request ever fails on a call, meta.fields_not_retrieved names the fields it cost you and says to retry, so an empty array is never ambiguous.
When should I use new_listings instead of search?▾
When you want inventory that just appeared rather than everything that matches a place. new_listings reads Zillow's published feed and returns only identity and location — zpid, url, address, region, postal_code, changed_at — which you then resolve through property_detail for the numbers. Watch window.is_recent_window on each response: for_sale, sold and for_rent are large enough that you get a genuine recent slice, while the smaller categories (for_sale_by_owner, auction, pending, other) fit inside the window entirely, so what you get is the whole category rather than new arrivals. Poll incrementally by passing the previous call's window.newest as changed_after.
What is the Zillow API?▾
Zillow API is a ReefAPI endpoint group for us homes with prices, history and estimates. It returns live JSON through POST requests under /zillow/v1.
Is the Zillow API free to try?▾
Yes. ReefAPI starts with 1,000 free credits, no card required. Zillow calls use the same shared credit balance as every other ReefAPI engine.
Do I need a Zillow login or account?▾
No login to Zillow is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.
How fresh is the Zillow data?▾
The page example is captured from a live new_listings call, and production requests fetch live data through ReefAPI rather than a static sample.
How many credits does the Zillow API use?▾
Zillow actions currently cost 1-3 credits per successful call. Failed or blocked calls are free. All APIs draw from one credit pool.
Can I call Zillow from an AI assistant or MCP client?▾
Yes. Connect ReefAPI once through MCP and your assistant can call zillow actions with the same key, credit pool and JSON envelope used by normal REST requests.
21 Real Estate APIs on the same key
One key, one credit pool, one response envelope. If you are pulling Zillow, you are one call away from the rest of the category — no second contract, no second integration.
Need something this API does not do?
Name the endpoint, the field, or a source we do not carry yet. We ship new APIs every week and you would be first to get the key. Real people read every message and reply the same day.
Try it on your own data before you pay anything
The call above is the real endpoint, not a recording. A free key gives you 1,000 credits, the other 439 APIs, and the same envelope everywhere.
Endpoints, parameters and credit costs on this page are read from the live catalog and cannot drift from what the API accepts. Field notes were captured on 2026-08-28.