Zillow API price history toolkit built in Python, showing a property value timeline, an appreciation rate calculation, and a market trend comparison across ZIP codes
Track property values and market trends with the Zillapi Zillow API.

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.

Pipeline showing a property address sent to the Zillow API, which returns a price history array of dated value points, feeding three outputs: an appreciation rate, a value timeline, and an aggregated market trend
One price history array feeds appreciation, timelines, and market trends.
TaskData neededAPI field
Value timelineDated value pointspriceHistory
Appreciation rateFirst and last valuepriceHistory
Tax cross-checkAssessed value over timetaxHistory
Market trendMany properties’ historiespriceHistory (aggregated)
Current valueLatest estimatezestimate
Sale verificationPast sale eventspriceHistory (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.

Appreciation calculation showing a home that rose from 200,000 dollars to 280,000 dollars over five years, a 40 percent total gain, converted by the compound annual growth rate formula into about 7 percent per year
Total gain converts to a yearly rate with the CAGR formula.
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.

Two rising lines over five years, the market value line climbing faster than the slower tax assessed value line, with the widening gap between them highlighted to show market value outpacing the county assessment
Market value usually outruns the slower tax assessment, widening the gap.
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.

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.

Line chart showing the median home value in a ZIP code rising year over year from 2021 to 2026, built by aggregating the price histories of many properties into one trend line
Aggregating many property histories produces a market 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.

Comparison matrix of trend platforms versus Zillapi across cost, per-property price history, market aggregation, historical indexes, forecasts, and setup time, showing Zillapi covers raw price history and custom aggregation at a fraction of the cost
The API supplies raw price history; you build the aggregation you need.
FeatureTrend platforms (HouseCanary, RentCast)Zillapi
CostMonthly subscription$5/mo or $54/yr
Per-property price historySomeYes
Market aggregationBuilt-inBuild your own
Historical index (HPI)Yes (decades)No
ForecastsYesNo
Raw data accessLimitedFull
Setup timeDays60 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.

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.