Property managers juggle dozens of tasks across dozens of units. Setting rent prices, tracking property values, screening new acquisitions, reporting to owners. Most of that work involves looking up the same data over and over: what is this property worth, what should the rent be, how does it compare to the market.
That lookup process is manual for most managers. Open Zillow, search the address, write down the Zestimate, check a few rental comps, paste everything into a spreadsheet. Do that for 50 units and half your morning is gone.
One API call returns the rent estimate, Zestimate, tax value, and property details for any address. Connect that to your workflow and the data fills itself in.
Here is how to automate the most common property management tasks with Python and the Zillow API.
What data do property managers need from an API?
Property managers pull data at different points in the management cycle. The same API covers all of them.
| Management task | Data needed | API field |
|---|---|---|
| Set rent price | Rent estimate, nearby listings | rentZestimate, search (FOR_RENT) |
| Track property value | Current valuation | zestimate |
| Owner reports | Value, rent, comps, equity | zestimate, rentZestimate, search (RECENTLY_SOLD) |
| New acquisition screening | Price, rent, cap rate | price, rentZestimate, zestimate |
| Lease renewal pricing | Market rent, rent growth | rentZestimate (current vs. prior) |
| Maintenance budgeting | Year built, sqft, property type | yearBuilt, livingArea, homeType |
One API call returns 300+ fields per property. That call costs 1 credit ($0.005).
For your API key, follow the step-by-step guide.
How do I pull rent estimates for my properties?
The rent Zestimate is the most useful field for property managers. It tells you what a property should rent for based on its size, location, and comparable rentals.
import requests, os
API_KEY = os.environ["ZILLAPI_KEY"]HEADERS = {"Authorization": f"Bearer {API_KEY}"}
def get_rent_estimate(address): """Pull rent estimate and property details for a managed property.""" r = requests.get( "https://api.zillapi.com/v1/properties/by-address", params={ "address": address, "fields": "address,rentZestimate,zestimate,bedrooms,bathrooms,livingArea,yearBuilt,homeType", }, headers=HEADERS, ) data = r.json()["data"]
rent = data.get("rentZestimate", 0) value = data.get("zestimate", 0) sqft = data.get("livingArea", 0)
gross_yield = round(rent * 12 / value * 100, 1) if value and rent else 0
print(f"{data['address']['streetAddress']}") print(f" {data.get('bedrooms')}bd/{data.get('bathrooms')}ba | {sqft:,} sqft | Built {data.get('yearBuilt', 'N/A')}") print(f" Rent Estimate: ${rent:,}/mo") print(f" Property Value: ${value:,}") print(f" Gross Yield: {gross_yield}%") print(f" Rent per Sqft: ${round(rent / sqft, 2)}/mo" if sqft else "")
return {"address": address, "rent": rent, "value": value, "yield": gross_yield}
estimate = get_rent_estimate("17 Zelma Dr, Greenville, SC 29617")One call. You get the rent estimate, property value, and enough data to calculate gross yield. Run this for every property in your portfolio and you have a rent pricing sheet in seconds.
How do I check if my rents are at market rate?
The rent estimate tells you the automated market value. But you also want to see what competing landlords are actually asking. Search for active rentals nearby and compare:
def rent_market_check(address, current_rent): """Compare current rent to market estimate and nearby listings.""" # Get property details and rent estimate prop = requests.get( "https://api.zillapi.com/v1/properties/by-address", params={ "address": address, "fields": "address,rentZestimate,bedrooms,bathrooms,livingArea,latitude,longitude", }, headers=HEADERS, ).json()["data"]
rent_estimate = prop.get("rentZestimate", 0) beds = prop.get("bedrooms", 3) lat = prop.get("latitude", 0) lng = prop.get("longitude", 0)
# Search for active rentals nearby rentals = requests.post( "https://api.zillapi.com/v1/search", json={ "bbox": { "north": lat + 0.015, "south": lat - 0.015, "east": lng + 0.015, "west": lng - 0.015, }, "listingStatus": "FOR_RENT", "homeType": ["SINGLE_FAMILY", "TOWNHOUSE"], "minBeds": max(beds - 1, 1), "maxBeds": beds + 1, }, headers=HEADERS, ).json()["data"]
# Analyze competing rentals asking_rents = [] for rental in rentals[:10]: rent_price = rental.get("price", 0) if rent_price > 0: asking_rents.append({ "address": rental["address"]["streetAddress"], "asking": rent_price, "beds": rental.get("bedrooms"), "sqft": rental.get("livingArea", 0), })
print(f"Rent Market Check: {prop['address']['streetAddress']}") print(f" Current Rent: ${current_rent:,}/mo") print(f" Rent Estimate: ${rent_estimate:,}/mo")
if asking_rents: avg_asking = int(sum(r["asking"] for r in asking_rents) / len(asking_rents)) print(f" Avg Nearby Ask: ${avg_asking:,}/mo ({len(asking_rents)} listings)")
print(f"\n Nearby Rentals:") for r in asking_rents[:5]: print(f" {r['address']} - ${r['asking']:,}/mo | {r['beds']}bd | {r['sqft']:,} sqft") else: avg_asking = rent_estimate
# Pricing recommendation gap = current_rent - rent_estimate gap_pct = round(gap / rent_estimate * 100, 1) if rent_estimate else 0
print(f"\n Gap: ${gap:+,}/mo ({gap_pct:+}%)") if gap_pct < -10: print(f" Recommendation: BELOW MARKET. Consider raising rent ${abs(gap):,}/mo at renewal.") elif gap_pct > 10: print(f" Recommendation: ABOVE MARKET. May face longer vacancy if tenant leaves.") else: print(f" Recommendation: AT MARKET. Current pricing is competitive.")
return {"current": current_rent, "estimate": rent_estimate, "gap_pct": gap_pct}
check = rent_market_check("17 Zelma Dr, Greenville, SC 29617", current_rent=1400)If your rent is 10% or more below the estimate, you are leaving money on the table. If it is above, you may face longer vacancy at turnover. Either way, now you have the numbers to back up the conversation with your owner.
How do I track portfolio values over time?
Owners want to know what their properties are worth. A monthly value check takes one API call per property and gives you appreciation data for every unit you manage.
import jsonfrom datetime import date
def monitor_portfolio(properties, history_file="portfolio_history.json"): """Track Zestimates across a managed portfolio.""" # Load previous history try: with open(history_file, "r") as f: history = json.load(f) except FileNotFoundError: history = {}
today = str(date.today()) results = []
print(f"{'='*65}") print(f"PORTFOLIO VALUE REPORT - {today}") print(f"{'='*65}")
total_value = 0 total_prev = 0
for prop in properties: address = prop["address"] units = prop.get("units", 1)
data = requests.get( "https://api.zillapi.com/v1/properties/by-address", params={"address": address, "fields": "address,zestimate,rentZestimate"}, headers=HEADERS, ).json()["data"]
current_value = data.get("zestimate", 0) rent = data.get("rentZestimate", 0) total_value += current_value
# Compare to previous check prev_values = history.get(address, {}) prev_dates = sorted(prev_values.keys()) prev_value = prev_values[prev_dates[-1]] if prev_dates else current_value total_prev += prev_value
change = current_value - prev_value change_pct = round(change / prev_value * 100, 1) if prev_value else 0
print(f"\n {data['address']['streetAddress']} ({units} unit{'s' if units > 1 else ''})") print(f" Value: ${current_value:,} ({change_pct:+}% since last check)") print(f" Rent: ${rent:,}/mo")
if change_pct < -5: print(f" ** VALUE ALERT: Down ${abs(change):,} **")
# Save to history if address not in history: history[address] = {} history[address][today] = current_value
results.append({ "address": address, "value": current_value, "rent": rent, "change_pct": change_pct, })
# Portfolio summary portfolio_change = total_value - total_prev portfolio_pct = round(portfolio_change / total_prev * 100, 1) if total_prev else 0
print(f"\n{'='*65}") print(f"Portfolio Total: ${total_value:,} ({portfolio_pct:+}%)") print(f"Monthly Rent Roll: ${sum(r['rent'] for r in results):,}") print(f"{'='*65}")
# Save history with open(history_file, "w") as f: json.dump(history, f, indent=2)
return results
portfolio = [ {"address": "17 Zelma Dr, Greenville, SC 29617", "units": 1}, {"address": "100 Main St, Greenville, SC 29601", "units": 1}, {"address": "45 Oak Ave, Taylors, SC 29687", "units": 1},]results = monitor_portfolio(portfolio)Run this on the first of every month. The script saves each value to a JSON file, so the next time it runs, it calculates the change since the last check. Properties that drop more than 5% get flagged.
A portfolio of 50 properties costs 50 credits ($0.25) per monitoring cycle. Run it monthly for a year and you have spent $3 total on portfolio tracking.
How do I generate owner reports?
Owners expect regular updates on their properties. A good report shows the current value, rent performance, market comps, and equity position. The API gives you all of that in a few calls.
def generate_owner_report(properties, owner_name): """Build a property report for an owner.""" print(f"{'='*65}") print(f"OWNER REPORT: {owner_name}") print(f"Date: {date.today()}") print(f"{'='*65}")
total_value = 0 total_rent = 0 total_equity = 0
for prop in properties: address = prop["address"] mortgage_balance = prop.get("mortgage_balance", 0)
data = requests.get( "https://api.zillapi.com/v1/properties/by-address", params={ "address": address, "fields": "address,zestimate,rentZestimate,taxAssessedValue,bedrooms,bathrooms,livingArea,yearBuilt", }, headers=HEADERS, ).json()["data"]
value = data.get("zestimate", 0) rent = data.get("rentZestimate", 0) tax = data.get("taxAssessedValue", 0) equity = value - mortgage_balance cap_rate = round((rent * 12 * 0.60) / value * 100, 1) if value else 0
total_value += value total_rent += rent total_equity += equity
print(f"\n {data['address']['streetAddress']}") print(f" {data.get('bedrooms')}bd/{data.get('bathrooms')}ba | {data.get('livingArea', 0):,} sqft | Built {data.get('yearBuilt', 'N/A')}") print(f" Market Value: ${value:,}") print(f" Tax Assessment: ${tax:,}") print(f" Monthly Rent: ${rent:,}") print(f" Annual Gross: ${rent * 12:,}") print(f" Cap Rate (est): {cap_rate}%") print(f" Mortgage Balance: ${mortgage_balance:,}") print(f" Equity: ${equity:,}")
print(f"\n{'='*65}") print(f"PORTFOLIO SUMMARY") print(f" Total Value: ${total_value:,}") print(f" Total Equity: ${total_equity:,}") print(f" Monthly Rent: ${total_rent:,}") print(f" Annual Rent: ${total_rent * 12:,}") print(f"{'='*65}")
return { "owner": owner_name, "total_value": total_value, "total_equity": total_equity, "monthly_rent": total_rent, }
owner_props = [ {"address": "17 Zelma Dr, Greenville, SC 29617", "mortgage_balance": 180000}, {"address": "45 Oak Ave, Taylors, SC 29687", "mortgage_balance": 145000},]report = generate_owner_report(owner_props, "Anderson Properties LLC")Two properties, two API calls, one complete owner report. Value, equity, rent, cap rate, and a portfolio summary. Send this as a monthly PDF or paste it into an email. Owners who see regular data reports stay longer with their property manager.
The cap rate uses a 60% operating expense ratio as a quick estimate. For a more precise calculation using actual expenses, see the NOI guide.
How do I screen new acquisitions?
When an owner asks you to evaluate a rental property, you need the purchase price, rent estimate, and comps in one place. This function scores a property as a rental investment:
def screen_acquisition(address, asking_price, down_pct=25, rate=7.0): """Evaluate a property for rental acquisition.""" data = requests.get( "https://api.zillapi.com/v1/properties/by-address", params={ "address": address, "fields": "address,zestimate,rentZestimate,taxAssessedValue,bedrooms,bathrooms,livingArea,yearBuilt,homeType,schools", }, headers=HEADERS, ).json()["data"]
rent = data.get("rentZestimate", 0) value = data.get("zestimate", 0) sqft = data.get("livingArea", 0)
# Financing math down_payment = asking_price * down_pct / 100 loan = asking_price - down_payment monthly_rate = rate / 100 / 12 payments = 360 mortgage = loan * (monthly_rate * (1 + monthly_rate) ** payments) / ((1 + monthly_rate) ** payments - 1)
# Expense estimates vacancy = rent * 0.05 management = rent * 0.08 maintenance = rent * 0.10 insurance = 120 taxes_monthly = data.get("taxAssessedValue", 0) * 0.012 / 12
total_expenses = mortgage + vacancy + management + maintenance + insurance + taxes_monthly cash_flow = rent - total_expenses
noi = (rent - vacancy - management - maintenance - insurance - taxes_monthly) * 12 cap_rate = round(noi / asking_price * 100, 1) if asking_price else 0 cash_on_cash = round(cash_flow * 12 / down_payment * 100, 1) if down_payment else 0 grm = round(asking_price / (rent * 12), 1) if rent else 0
print(f"ACQUISITION SCREENING") print(f"{'='*55}") print(f" {data['address']['streetAddress']}") print(f" {data.get('bedrooms')}bd/{data.get('bathrooms')}ba | {sqft:,} sqft | Built {data.get('yearBuilt', 'N/A')}") print(f"\n Asking Price: ${asking_price:,}") print(f" Zestimate: ${value:,}") print(f" Rent Estimate: ${rent:,}/mo") print(f"\n Down Payment: ${down_payment:,} ({down_pct}%)") print(f" Mortgage: ${mortgage:,.0f}/mo") print(f" Cash Flow: ${cash_flow:,.0f}/mo") print(f"\n Cap Rate: {cap_rate}%") print(f" Cash-on-Cash: {cash_on_cash}%") print(f" GRM: {grm}")
# Verdict if cap_rate >= 8 and cash_flow > 0: print(f"\n Verdict: STRONG BUY. Positive cash flow with {cap_rate}% cap rate.") elif cap_rate >= 6 and cash_flow > 0: print(f"\n Verdict: CONSIDER. Moderate returns but cash flow positive.") elif cash_flow > 0: print(f"\n Verdict: MARGINAL. Cash flow positive but cap rate below 6%.") else: print(f"\n Verdict: PASS. Negative cash flow at this price and rate.")
# Schools for tenant appeal schools = data.get("schools", []) if schools: print(f"\n Nearby Schools:") for s in schools[:3]: print(f" {s.get('name', '?')} - Rating: {s.get('rating', 'N/A')}/10")
return {"cap_rate": cap_rate, "cash_flow": round(cash_flow), "cash_on_cash": cash_on_cash}
result = screen_acquisition("17 Zelma Dr, Greenville, SC 29617", asking_price=285000)The function pulls the rent estimate, calculates expenses using industry benchmarks (5% vacancy, 8% management, 10% maintenance), runs the mortgage math, and gives you cap rate, cash flow, and cash-on-cash return. School ratings are included because properties near good schools attract longer-tenured families.
For the complete ARV methodology for value-add acquisitions, see the ARV guide.
How much does this cost compared to property management platforms?
Dedicated property management data tools charge per unit or per portfolio. The API approach costs a fraction of that.
| Solution | Monthly cost | What you get |
|---|---|---|
| RentCast | $25 to $150 | Rent estimates, comps, portfolio tracking |
| CoStar (commercial) | $300+ | Market data, rent comps, analytics |
| AppFolio data add-ons | Varies by unit count | Screening, rent comps |
| Zillapi | $5 | 1,000 lookups (rent, value, comps, details) |
| Zillapi (annual) | $4.50/mo | 12,000 lookups per year |
A manager with 50 units running monthly portfolio checks and quarterly rent pricing reviews uses about 800 credits per year. That costs $4 on the annual plan.
The trade-off is that the API gives you raw data. You write the scripts and build the reports. The paid platforms give you a polished interface. If you are comfortable with Python or can hire someone who is, the API saves thousands per year.
What does an automated property management workflow look like?
Here is how the data fits into a monthly management cycle:
On the first of each month, the portfolio monitor runs. It pulls current Zestimates for every managed property, compares them to the previous month, and flags anything that dropped more than 5%. That takes 30 seconds and costs one credit per property.
Before each lease renewal, the rent market check runs. It pulls the rent estimate and nearby listings, compares them to the current rent, and generates a pricing recommendation. If the market moved up, you have data to support a rent increase. If it stayed flat, you have data to explain why the rent is not changing.
When an owner asks about a new property, the acquisition screener runs. One call gives you rent estimate, cap rate, cash flow, and a buy/pass verdict.
At the end of each quarter, the owner report generator pulls fresh data for every property in an owner’s portfolio. Value, equity, rent, and cap rate, all in one document.
For scheduling these scripts automatically, see the automation guide.
How do I get started?
Go to zillapi.com and sign up. You get 100 free credits with no credit card required.
Start with the rent estimate function. Pull estimates for a few of your managed properties and compare them to what you are charging. That single check often pays for a full year of API access by identifying one underpriced unit.
For the full Python setup, see the Python tutorial. For property field documentation, see the property data guide. For building a visual dashboard with this data, see the Streamlit tutorial.
Frequently asked questions
Can property managers use the Zillow API?
Yes. Third-party REST APIs like Zillapi return rent estimates, Zestimates, property details, comparable sales, and listing data for any U.S. property. Property managers use this data to set rental pricing, track portfolio values, generate owner reports, and screen new acquisitions. One API call costs 1 credit ($0.005). The free tier gives 100 credits at signup with no credit card required.
How do I get rent estimates through an API?
Call the property endpoint with the address and include rentZestimate in the fields parameter. The API returns a monthly rent estimate based on the property size, location, bedroom count, and comparable rentals nearby. One call returns the rent estimate alongside the Zestimate, tax value, and 300+ other property fields. Each call costs 1 credit.
Can I track property values across a portfolio with the API?
Yes. Store the addresses for every property you manage. Run a weekly or monthly script that pulls the current Zestimate for each one. Compare against the previous value to track appreciation or depreciation. Flag properties where the value drops more than 5% for owner notification. A portfolio of 100 properties costs $0.50 per monitoring cycle.
How do I set rental pricing with property data?
Pull the rent estimate for your property and compare it to asking rents for similar active listings nearby. If your current rent is 10% or more below the market estimate, that signals room for a rent increase at renewal. If it is above the estimate, expect longer vacancy. The API gives you both the automated estimate and the raw market data to make pricing decisions.
How much does a property data API cost for property managers?
Zillapi costs $5 per month for 1,000 property lookups, or $54 per year for 12,000 lookups. A property manager with 50 units needs about 200 credits per month for portfolio monitoring and rent checks. That is $1 per month. The free tier gives 100 credits at signup. Dedicated property management data platforms charge $50 to $500 per month for similar data.
Can I generate owner reports with the API?
Yes. Pull the current Zestimate, rent estimate, and comparable sales for each property in an owner’s portfolio. Calculate equity, cap rate, and year-over-year appreciation. Format the results into a monthly or quarterly report. The API provides the data. You format it however your owners prefer, whether that is a PDF, email, or dashboard.