Every real estate decision rests on one question: which way are prices moving? An investor wants to know if a market is heating up. An agent wants to show a seller how their home has appreciated. An analyst wants to compare ZIP codes.
Price history answers that question. One API call returns a property’s past sale prices, listing changes, and value over time. Aggregate that across many properties and you have a market trend.
Here is how to pull price history and build trend analysis with Python and the Zillow API.
What does price history give you?
The API returns the historical record for any property in one call. You turn that record into appreciation rates, trend lines, and market comparisons.
| Task | Data needed | API field |
|---|---|---|
| Value timeline | Dated value points | priceHistory |
| Appreciation rate | First and last value | priceHistory |
| Tax cross-check | Assessed value over time | taxHistory |
| Market trend | Many properties’ histories | priceHistory (aggregated) |
| Current value | Latest estimate | zestimate |
| Sale verification | Past sale events | priceHistory (event, date) |
One call returns 300+ fields per property at $0.005. For API key setup, see the step-by-step guide.
How do I pull a property’s price history?
Start with a single property. The price history comes back as a list of dated events, each with a price and an event type.
import requests, os
API_KEY = os.environ["ZILLAPI_KEY"]HEADERS = {"Authorization": f"Bearer {API_KEY}"}
def get_price_history(address): """Pull the price history for a single property.""" data = requests.get( "https://api.zillapi.com/v1/properties/by-address", params={ "address": address, "fields": "address,zestimate,priceHistory,taxHistory", }, headers=HEADERS, ).json()["data"]
history = data.get("priceHistory", [])
print(f"PRICE HISTORY") print(f"{'='*55}") print(f" {data['address']['streetAddress']}") print(f" Current Zestimate: ${data.get('zestimate', 0):,}") print(f"\n {'Date':<12} {'Event':<16} {'Price':>12}") print(f" {'-'*42}") for event in history: date = event.get("date", "") kind = event.get("event", "") price = event.get("price", 0) print(f" {date:<12} {kind:<16} ${price:>11,}") print(f"{'='*55}")
return history
history = get_price_history("17 Zelma Dr, Greenville, SC 29617")The function returns each event in the property’s record: sales, list-price changes, and value updates. Each row has a date, an event type, and a price.
This is the raw material for everything else. A single call gives you the full trajectory of one property.
How do I calculate appreciation?
Appreciation is the change in value over time. The simple version is the percent change from first to last. The useful version is the compound annual growth rate, which tells you the yearly pace.
from datetime import datetime
def calculate_appreciation(history): """Calculate total and annualized appreciation from price history.""" sales = [e for e in history if e.get("price")] if len(sales) < 2: return None
sales.sort(key=lambda e: e["date"]) start = sales[0] end = sales[-1]
start_price = start["price"] end_price = end["price"] start_year = int(start["date"][:4]) end_year = int(end["date"][:4]) years = max(end_year - start_year, 1)
total_change = (end_price - start_price) / start_price * 100 cagr = ((end_price / start_price) ** (1 / years) - 1) * 100
print(f"APPRECIATION") print(f"{'='*45}") print(f" {start['date']}: ${start_price:,}") print(f" {end['date']}: ${end_price:,}") print(f" Period: {years} years") print(f" Total change: {total_change:+.1f}%") print(f" Annual rate (CAGR): {cagr:+.1f}%/yr") print(f"{'='*45}")
return {"total_change": round(total_change, 1), "cagr": round(cagr, 1), "years": years}
result = calculate_appreciation(history)The function sorts the events by date, takes the first and last priced events, and computes both numbers. Total change is the headline. CAGR is the rate that matters for comparison.
A home that went from $200,000 to $280,000 over five years gained 40% total, which is about 7% per year. The annual rate lets you compare properties and markets with different time spans on equal footing.
How do I cross-check with tax history?
Tax assessments move slower than market value and update on a fixed county schedule. Comparing the two histories shows whether the market has run ahead of the assessed value.
def compare_price_vs_tax(address): """Compare market price history against tax assessment history.""" data = requests.get( "https://api.zillapi.com/v1/properties/by-address", params={"address": address, "fields": "address,zestimate,priceHistory,taxHistory"}, headers=HEADERS, ).json()["data"]
tax = data.get("taxHistory", []) zestimate = data.get("zestimate", 0)
print(f"PRICE VS TAX ASSESSMENT") print(f"{'='*50}") print(f" Current Zestimate: ${zestimate:,}") print(f"\n {'Year':<8} {'Tax Assessed':>14} {'vs Market':>12}") print(f" {'-'*36}") for t in tax[:6]: year = str(t.get("time", ""))[:4] assessed = t.get("value", 0) gap = round((zestimate - assessed) / assessed * 100) if assessed else 0 print(f" {year:<8} ${assessed:>13,} {gap:>+11}%") print(f"{'='*50}") print(f" A large gap means market value has outpaced the assessment.")
compare_price_vs_tax("17 Zelma Dr, Greenville, SC 29617")The function lines up the assessed value each year against the current market estimate. A widening gap means the market has appreciated faster than the county has reassessed.
This matters for two reasons. Buyers see how much room is between the tax basis and market value. Owners spot when a reassessment might be coming.
How do I track market trends across an area?
A single property tells you about one home. To read a market, aggregate the price histories of many properties in a ZIP code and build a median trend line.
from collections import defaultdict
def build_market_trend(bbox, sample_size=50): """Build a median value trend for an area from many price histories.""" sold = requests.post( "https://api.zillapi.com/v1/search", json={"bbox": bbox, "listingStatus": "RECENTLY_SOLD", "homeType": ["SINGLE_FAMILY"]}, headers=HEADERS, ).json()["data"][:sample_size]
# Collect prices by year across all properties by_year = defaultdict(list) for prop in sold: addr = prop.get("address", {}) full = f"{addr.get('streetAddress')}, {addr.get('city')}, {addr.get('state')} {addr.get('zipcode')}" detail = requests.get( "https://api.zillapi.com/v1/properties/by-address", params={"address": full, "fields": "priceHistory"}, headers=HEADERS, ).json()["data"]
for event in detail.get("priceHistory", []): if event.get("price") and event.get("date"): year = int(event["date"][:4]) by_year[year].append(event["price"])
print(f"MARKET TREND (median value by year)") print(f"{'='*45}") prev_median = None for year in sorted(by_year): prices = sorted(by_year[year]) median = prices[len(prices) // 2] change = "" if prev_median: pct = (median - prev_median) / prev_median * 100 change = f" ({pct:+.1f}%)" print(f" {year}: ${median:>10,}{change} (n={len(prices)})") prev_median = median print(f"{'='*45}")
return by_year
bbox = {"north": 34.90, "south": 34.80, "east": -82.35, "west": -82.45}trend = build_market_trend(bbox)The function pulls recent sales in the area, fetches each property’s price history, and groups every priced event by year. The median value per year forms the trend.
Sample size is a tradeoff. A 50-property sample costs 50 credits and gives a stable median. A larger sample smooths the line further at a higher cost.
How do I compare multiple markets?
Investors want to know which markets are appreciating fastest. Run the trend for several ZIP codes and rank them by their annual rate.
def rank_markets(markets): """Rank markets by their recent appreciation rate.""" results = [] for market in markets: trend = build_market_trend(market["bbox"], sample_size=40) years = sorted(trend) if len(years) < 2: continue
first_year, last_year = years[0], years[-1] first_med = sorted(trend[first_year])[len(trend[first_year]) // 2] last_med = sorted(trend[last_year])[len(trend[last_year]) // 2] span = max(last_year - first_year, 1) cagr = ((last_med / first_med) ** (1 / span) - 1) * 100
results.append({"market": market["name"], "cagr": round(cagr, 1), "current": last_med})
results.sort(key=lambda r: r["cagr"], reverse=True)
print(f"MARKET RANKING (by appreciation rate)") print(f"{'='*50}") for i, r in enumerate(results, 1): print(f" {i}. {r['market']:<24} {r['cagr']:+.1f}%/yr (${r['current']:,})") print(f"{'='*50}")
return results
markets = [ {"name": "Greenville, SC", "bbox": {"north": 34.90, "south": 34.80, "east": -82.35, "west": -82.45}}, {"name": "Columbia, SC", "bbox": {"north": 34.05, "south": 33.95, "east": -80.95, "west": -81.05}}, {"name": "Charleston, SC", "bbox": {"north": 32.82, "south": 32.72, "east": -79.90, "west": -80.00}},]ranking = rank_markets(markets)The function builds a trend for each market and computes the annual rate from the first and last year of data. Markets sort from fastest appreciation to slowest.
This is how you find where to deploy capital. The market at the top of the list is heating up. The market at the bottom may be cooling or stable.
How does this compare to trend platforms?
Dedicated trend platforms offer deep historical indexes and forecasts. The API gives you the raw price histories to build your own analysis at a lower cost.
| Feature | Trend platforms (HouseCanary, RentCast) | Zillapi |
|---|---|---|
| Cost | Monthly subscription | $5/mo or $54/yr |
| Per-property price history | Some | Yes |
| Market aggregation | Built-in | Build your own |
| Historical index (HPI) | Yes (decades) | No |
| Forecasts | Yes | No |
| Raw data access | Limited | Full |
| Setup time | Days | 60 seconds |
For long-range indexes and price forecasts, the specialized platforms do more. For pulling raw price histories into your own scripts and building custom trend analysis, the API does it for $0.005 a call.
Tracking 20 markets with a 50-property sample each uses 1,000 credits, about $5 for a full scan. Run it monthly and you have a rolling read on every market you care about.
How do I get started?
Go to zillapi.com and sign up. You get 100 free credits with no credit card required.
Start with a single property’s price history. Pull one address you know and calculate its appreciation rate. Then build a trend for one ZIP code. That trend line shows what the API does for market analysis.
For current valuations, see the Zestimate guide. For the full Python setup, see the Python tutorial. For property field documentation, see the property data guide. For tax data detail, see the tax data guide.
Frequently asked questions
Can I get price history from the Zillow API?
Yes. Third-party REST APIs like Zillapi return a property’s price history, including past sale prices, listing changes, and Zestimate values over time. One call returns the price history array along with 300+ other fields. You use this to chart a property’s value trajectory, calculate appreciation, and spot trends. One call costs 1 credit ($0.005), and the free tier gives 100 credits at signup.
How do I calculate property appreciation with the API?
Pull the price history for a property, take the earliest and latest values, and compute the percent change. For an annual rate, use the compound annual growth rate formula. Divide the ending value by the starting value, raise it to one over the number of years, and subtract one. A home that went from $200,000 to $280,000 over five years appreciated at about 7 percent per year.
How do I track market trends across an area?
Pull the price history for many properties in a ZIP code, then aggregate the values by year to build a median trend line. Comparing the median value across years shows whether the market is appreciating, flat, or cooling. You can run this for several ZIP codes and rank them by appreciation rate. A 50-property sample costs $0.25.
What is the difference between price history and tax history?
Price history shows sale prices, listing changes, and Zestimate values over time, reflecting market value. Tax history shows the assessed value the county sets for property tax purposes, which usually lags market value and updates on a fixed schedule. The API returns both. Use price history for market trends and tax history as an independent cross-check.
How far back does price history go?
Coverage varies by property, but price history often spans from the last sale or listing event through the present, and Zestimate values can cover several years. Older homes with multiple transactions have deeper histories. Newer construction or homes that rarely sell have shorter records. The API returns whatever history exists for each property in one call.
How much does market trend data cost?
Dedicated trend platforms like HouseCanary and RentCast charge monthly subscriptions. Zillapi costs $5 per month for 1,000 property lookups or $54 per year for 12,000. Tracking 20 markets with a 50-property sample each costs 1,000 credits, or about $5 for a full market scan. The free tier gives 100 credits at signup with no credit card.