Zillow API for real estate investors
You already have a list of candidate homes. Pull their Zillow listing data in one batch, rank them by gross yield, price cuts and time on market, and spend your underwriting time on the few that pass.
The job
Most investors find deals in many places: Zillow saved searches, wholesaler emails, a driving-for-dollars list, an agent's spreadsheet. The slow part is opening every link to copy the asking price, the rent Zestimate and the price history into a sheet before you can tell which ones are worth a real look.
PropertiesAPI does that copying. You send the links or addresses you already have, and each home comes back with the listing fields and a set of calculated figures, so your first filter runs on data instead of tabs.
The workflow
- Collect the candidates. Paste Zillow links from saved searches and emails, or addresses from any list, into one column. Links are the most precise; addresses are matched to a Zillow property first.
- Batch the lookups. Send up to 50 items per batch request. Each result has its own ok flag, so a bad address only drops its own row.
- Screen on calculated fields. data.calculated has gross yield from the rent Zestimate, price versus Zestimate, the number of price drops in the current listing and days on market. They are plain arithmetic on the returned fields, and null when an input is missing.
- Underwrite the short list. Take the homes that pass into your own model with real rent comps, repair estimates, taxes and financing. The screen tells you where to look, not what to pay.
Rank a deal list by gross yield and price cuts
Reads deals.txt (one link or address per line), batches the lookups and prints the homes that clear your thresholds. Change the thresholds to your own buy box.
import os, requests
BASE = "https://api.propertiesapi.com/v1"
HEADERS = {"x-api-key": os.environ["PROPERTIESAPI_KEY"]}
MIN_YIELD, MIN_DROPS = 7.0, 1 # your buy box
inputs = [l.strip() for l in open("deals.txt") if l.strip()]
homes = []
for i in range(0, len(inputs), 50):
r = requests.post(f"{BASE}/zillow/batch", json={"items": inputs[i:i + 50]}, headers=HEADERS, timeout=320)
r.raise_for_status()
homes += [item["data"] for item in r.json()["data"] if item["ok"]]
def keep(h):
c = h["calculated"]
return (c["gross_yield_pct"] or 0) >= MIN_YIELD or (c["price_drops_count"] or 0) >= MIN_DROPS
for h in sorted(filter(keep, homes), key=lambda h: h["calculated"]["gross_yield_pct"] or 0, reverse=True):
c = h["calculated"]
print(f'{h["address"]}: ask {h["price"]}, rent est {h["rentZestimate"]}, '
f'yield {c["gross_yield_pct"]}% ({c["rent_to_price_basis"]}), '
f'drops {c["price_drops_count"]}, on market {c["days_on_market"]} days')Fields that matter here
| Field | Why it matters |
|---|---|
| calculated.gross_yield_pct | The rent Zestimate for a year as a percent of the price (or of the Zestimate when there is no price). Before vacancy, taxes, insurance and repairs. |
| calculated.price_vs_zestimate_pct | How far the asking price sits above or below Zillow's Zestimate. Negative means listed below it. |
| calculated.price_drops_count | Price cuts in the current listing, from the price history. Motivated sellers often show up here first. |
| calculated.days_on_market | Days on Zillow while the home is listed. Null for sold or off-market homes. |
| priceHistory | Every listing, price change and sale event Zillow shows, newest first, for checking the story behind a price. |
| taxAnnualAmount | The annual tax figure from the page, for a first pass at carrying costs. |
What it won't do
- It doesn't find deals for you. There is no search by area, price or criteria; you bring the links or addresses.
- It doesn't return comps. For comparables, pick the homes yourself and look each one up.
- Zestimate and rent Zestimate are Zillow's estimates, returned when Zillow shows them. PropertiesAPI has no valuation model of its own.
- No owner names, mortgage data or skip tracing.
Real estate investors: questions
Can I search Zillow for all homes under a price in a ZIP code?+
No. PropertiesAPI looks up homes you already know. Use Zillow or your agent's MLS search to build the list, then screen it here.
How is gross yield calculated?+
rentZestimate times twelve, divided by the price, or by the Zestimate when there is no price. rent_to_price_basis tells you which. It is a screening number before any costs.
What does screening a list cost?+
Zillow links and addresses cost the same: 1 credit per successful lookup. Repeat lookups of the same home within 24 hours come from our cache and are free. Failed lookups cost nothing, and you start with 50 free lookups.
Can I do this without code?+
Yes, in a spreadsheet: the Google Sheets formulas or Excel Power Query return the same fields for a column of links.
Try it on your own list
50 free lookups, no card, no approval.
Zillow is a trademark of Zillow, Inc. PropertiesAPI is an independent service and is not affiliated with, endorsed by or sponsored by Zillow.