Zillow Property Search API with Python: Listings, Details, and Zestimates

Published October 8, 2026

The official Zillow API was discontinued years ago, but the need for programmatic US property data never went away. Teams still need listings for home-search apps, property facts for underwriting forms, and value estimates for portfolio tracking — and the remaining options (scraping HTML, rotating proxies, decoding anti-bot walls) are fragile full-time jobs. A Zillow API alternative that returns clean JSON over plain GET requests is the practical path.

This tutorial builds a small Python pipeline against the Zillow Real Estate Data API: search for-sale listings with filters, resolve a property by plain address or zpid into full facts, and finish by pulling its current value estimate. Error handling and pagination are included throughout.

Endpoints at a glance

OperationRequired inputRole in the workflow
searchlocationCity, county, neighborhood or ZIP search with buyer-style filters
search-by-coordinateslat, longRadius search around a map point (0.1–100 mile diameter)
search-by-polygonpolygonEvery listing inside a hand-drawn lng/lat boundary
property-detailszpid or urlFull fact sheet: beds, baths, sqft, HOA, parking, appliances
property-by-addressaddressThe same fact sheet from one free-text street address
property-zestimatezpidCurrent estimated market value in a minimal payload
agent-searchlocationReviewed agent directory for a city with sales stats and ratings

All seven were verified live: a Los Angeles search returned 41 real listings on page 1, an address lookup resolved the correct home with accurate bed/bath counts, and a point Zestimate exactly matched the estimate on that property's search card.

Step 1 — Prepare an authorized platform key

Sign up and copy an active key from your dashboard. Free credits are included for testing. The examples read the key from the OPENDATA_API_KEY environment variable; substitute YOUR_API_KEY only when you actually run a request.

No Zillow account, cookie or session token is involved — the platform key is the only credential. Because the key travels in the query URL, keep URLs out of shared links, screenshots and logs, and run production calls server-side.

Step 2 — Search listings with filters

The search operation takes a free-text location plus portal-style filters. home_status switches between FOR_SALE, FOR_RENT and RECENTLY_SOLD inventory:

curl --get 'https://api.opendata-api.com/api/project/zillow/search?key=YOUR_API_KEY' \
  --data-urlencode 'location=Los Angeles, CA' \
  --data-urlencode 'home_status=FOR_SALE' \
  --data-urlencode 'min_price=300000' \
  --data-urlencode 'max_price=850000' \
  --data-urlencode 'min_bedrooms=2' \
  --data-urlencode 'sort=NEWEST' \
  --data-urlencode 'page=1'

The envelope carries status (OK/ERROR), a request_id, an echo of your parameters, and a data array of listing cards. Each card includes zpid, full address fields, price, zestimate, rentZestimate, bedrooms, bathrooms, livingArea, lot size, daysOnZillow, latitude/longitude, homeType, imgSrc and a detailUrl. Pages return up to 41 listings (pages 1–100).

Two honest behaviors to program against: a valid filter that matches nothing returns HTTP 200 with data: [], and an unrecognizable location returns a precise Invalid location error. Keep price ranges plausible — absurd values (a trillion-dollar minimum) can surface as a temporary 503 instead of an empty result.

Step 3 — Resolve a property into full facts

Search cards already carry headline fields; property-details is the hydration step for the homes a user actually opens. Pass a zpid from any card (or a pasted listing-page url):

curl --get 'https://api.opendata-api.com/api/project/zillow/property-details?key=YOUR_API_KEY' \
  --data-urlencode 'zpid=20171939'

The data object adds the deep resoFacts block: bedroom and bathroom breakdowns, appliances, association name/fee/phone, parking, accessibility features and at-a-glance facts. Field population varies by property — preserve nulls instead of inventing values.

When your input is an address from a form or spreadsheet rather than an ID, property-by-address skips the search step entirely:

curl --get 'https://api.opendata-api.com/api/project/zillow/property-by-address?key=YOUR_API_KEY' \
  --data-urlencode 'address=1443 Bellevue Ave Los Angeles, CA 90026'

Step 4 — Pull the current Zestimate

property-zestimate returns just the current estimated market value for a zpid, verified to match the estimate shown on that property's search card:

curl --get 'https://api.opendata-api.com/api/project/zillow/property-zestimate?key=YOUR_API_KEY' \
  --data-urlencode 'zpid=20171939'

The deliberately small payload makes this the cheapest way to refresh valuations across a portfolio on a schedule. Search cards already carry zestimate and rentZestimate, so reserve point lookups for refreshes.

Step 5 — Assemble the pipeline in Python

This bounded script searches a city, walks pages until the results run out, hydrates the first hit's details, and refreshes its estimate. No response is hard-coded; missing fields stay missing.

import os
import requests

BASE = "https://api.opendata-api.com/api/project/zillow"
API_KEY = os.environ["OPENDATA_API_KEY"]


def call(operation, **params):
    try:
        response = requests.get(
            f"{BASE}/{operation}",
            params={**params, "key": API_KEY}, timeout=30,
        )
    except requests.RequestException:
        raise RuntimeError("Request failed; use redacted diagnostics") from None
    if not response.ok:
        raise RuntimeError(f"HTTP {response.status_code}")
    gateway = response.json()
    if gateway.get("code") != 200:
        raise RuntimeError("Gateway business failure; inspect code/message safely")
    payload = gateway.get("data") or {}
    if payload.get("status") == "ERROR":
        raise RuntimeError("Search error; inspect message safely")
    return payload.get("data")


def search_all(location, max_pages=3, **filters):
    listings = []
    for page in range(1, max_pages + 1):
        rows = call("search", location=location, page=page, **filters) or []
        listings.extend(rows)
        if len(rows) < 41:  # short page means the inventory ran out
            break
    return listings


homes = search_all("Los Angeles, CA", home_status="FOR_SALE",
                   min_price=300000, max_price=850000, sort="NEWEST")
print(f"{len(homes)} listings collected")

if homes:
    first = homes[0]
    zpid = first.get("zpid")
    details = call("property-details", zpid=zpid) or {}
    facts = details.get("resoFacts") or {}
    estimate = call("property-zestimate", zpid=zpid) or {}
    print({
        "address": details.get("streetAddress"),
        "bedrooms": facts.get("bedrooms"),
        "bathrooms": facts.get("bathrooms"),
        "hoa_fee": facts.get("associationFee"),
        "zestimate": estimate.get("zestimate"),
    })

The same flow from JavaScript is one fetch per step — for example fetch(`https://api.opendata-api.com/api/project/zillow/search?key=${key}&location=${encodeURIComponent(city)}`) — keeping the key in server-side environment configuration, never in shipped client code.

HTTP status versus business status

Two layers matter. The HTTP status reflects transport; the gateway envelope carries code, message and data, where code 200 indicates gateway business success — HTTP 200 alone does not. Inside a successful envelope, search payloads add their own status field: OK with a populated (or legitimately empty) array, or ERROR with a precise message such as Invalid location. Check the HTTP status first, then the business code, then the payload status before reading listing fields.

Use cases

  • Consumer home search — city/ZIP search with price, beds, baths, sqft, HOA and status filters over live inventory.
  • Investor deal alerts — poll target ZIPs sorted by NEWEST nightly and notify on fresh or distressed listings (foreclosure, pre-foreclosure, auction flags).
  • Map-first apps — radius search around a user's GPS position, or polygon search behind a draw-an-area UI.
  • Automated valuation models — training data from listing cards plus scheduled property-zestimate refreshes as labels.
  • Lead enrichment and intake — property-by-address turns mailing lists and loan/insurance intake forms into structured property records.
  • Agent analytics — agent-search ranks reviewed agents per metro by ratings and formatted sales stats for recruiting or referral products.

FAQ

Is there an official Zillow API still available?

No — the official public API was discontinued. This project is an unofficial data service that returns live property data as documented JSON, so you do not maintain scrapers, proxy pools or cookie handling yourself.

What happened to the Zillow API and what are the alternatives?

Zillow retired its public endpoints, leaving scraping or licensed data feeds as the main options. An unofficial JSON API like this one sits in the middle: real listing, detail and estimate data without scraper maintenance or enterprise licensing.

How do I get Zillow property data into my app without scraping?

Call the project endpoints over plain GET requests with your platform API key, as shown above. Python requests, JavaScript fetch or curl all work; there is no SDK to install.

How do I get a Zestimate for a property programmatically?

Call property-zestimate with a zpid (from any search card) or a listing-page URL. For rentals, read the rentZestimate field already present on search cards.

How do I search homes for sale by zip code or city via API?

Pass the city or ZIP as location to search with home_status=FOR_SALE, then narrow with min_price, max_price, min_bedrooms and friends. Use home_status=FOR_RENT or RECENTLY_SOLD for rental and sold inventory.

How do I find properties near a latitude and longitude point?

Use search-by-coordinates with lat, long and a diameter in miles (0.1–100, default 1.0). All listing filters from the location search apply identically.

How do I look up property details (beds, baths, sqft, HOA) by address?

Call property-by-address with one free-text street address. The response carries price, address components and the resoFacts block with beds, baths, appliances, association fees and parking.

How do I get a real estate agent's listings and sold history via API?

agent-search returns reviewed agents for a city with ratings, review counts, formatted 12-month sales stats and a stable encodedZuid per agent — enough to rank and match agents. Per-agent listing feeds are not part of this project; inventory is searched by location, point or polygon instead.

How much does a real estate data API cost per request?

This project is priced per request at a flat credit rate — a search page, a detail lookup and an estimate all cost the same — so a page of 41 listings costs one request, not 41. See pricing for current per-request costs and bundles.

Use search-by-polygon: pass comma-separated coordinate pairs, longitude first then latitude (lng1 lat1,lng2 lat2,...). The ring closes automatically, so irregular boundaries like school districts or flood zones work directly, and standard filters plus pagination apply inside the shape.

Try the listing search, property details, address lookup and Zestimate references, or start from the Zillow project page. APIs for similar platforms — Redfin, Realtor.com, Trulia, Apartments.com, Homes.com, Zumper, HotPads, Rent.com, LoopNet, Rightmove and others — can be added on request through the contact page; private source-code deployment is also available.