Last sale: dateSold, lastSoldPrice and calculated last sale
Four fields answer when the home last sold and for how much: Zillow's own dateSold and lastSoldPrice, and the last sale that PropertiesAPI reads from priceHistory in data.calculated.
| Field | Type |
|---|---|
data.dateSold | string (YYYY-MM-DD) or null |
data.lastSoldPrice | number or null |
data.calculated.last_sale_price | number or null |
data.calculated.last_sale_date | string or null |
data.calculated.change_since_last_sale_pct | number or null |
data.calculated.change_since_last_sale_basis | "price", "zestimate" or null |
Fields under data.calculated are calculated by PropertiesAPI from the other fields in the same response. They are not Zillow estimates.
What it means
lastSoldPrice is Zillow's last sold price for the home. It is often present on homes listed for sale again, where it is the price of the previous sale. dateSold is the date of the last sale and is filled for sold and off-market homes; on a home listed for sale it is usually null even when lastSoldPrice has a value.
calculated.last_sale_price and calculated.last_sale_date come from the most recent Sold event in priceHistory, so they work the same way for every status. When that sale's price wasn't disclosed, last_sale_price is null; an older sale's price is never used instead.
calculated.change_since_last_sale_pct compares a value now with that last sale price: the asking price for a home listed for sale, otherwise the Zestimate. change_since_last_sale_basis says which one was used.
Recorded example
{
"homeStatus": "RECENTLY_SOLD",
"price": 385000,
"dateSold": "2026-10-16",
"lastSoldPrice": 385000,
"calculated": {
"last_sale_price": 385000,
"last_sale_date": "2026-10-16"
}
}{
"homeStatus": "FOR_SALE",
"price": 487500,
"dateSold": null,
"lastSoldPrice": 415000,
"calculated": {
"last_sale_price": 415000,
"last_sale_date": "2023-07-10",
"change_since_last_sale_pct": 17.47,
"change_since_last_sale_basis": "price"
}
}{
"homeStatus": "RECENTLY_SOLD",
"price": null,
"dateSold": "2026-02-24",
"lastSoldPrice": null,
"calculated": {
"last_sale_price": null,
"last_sale_date": "2026-02-24"
}
}Values are copied from recorded lookups of public Zillow pages, with the other fields of data left out. They show the shape and typical values, not today's data for these homes.
When it is null
- The sale price isn't disclosed. A recorded sold home in New Mexico, a state without public sale prices, had a Sold event with no price, so lastSoldPrice and last_sale_price were null while the date was present.
- The home has never sold in the period the page covers.
- change_since_last_sale_pct is null when either side is missing: no disclosed last sale price, or no asking price and no Zestimate.
Read it in code
Send a Zillow link, a zpid or an address to GET /v1/zillow/property with your key in the x-api-key header, then read the field from data:
curl -sG "https://api.propertiesapi.com/v1/zillow/property" \
-H "x-api-key: $PROPERTIESAPI_KEY" \
--data-urlencode "url=https://www.zillow.com/homedetails/1704-Shelbourne-Dr-Austin-TX-78752/29412761_zpid/" \
| jq '.data | {dateSold, lastSoldPrice, calculated: (.calculated | {last_sale_price, last_sale_date, change_since_last_sale_pct, change_since_last_sale_basis})}'import os
import requests
r = requests.get(
"https://api.propertiesapi.com/v1/zillow/property",
params={"url": "https://www.zillow.com/homedetails/1704-Shelbourne-Dr-Austin-TX-78752/29412761_zpid/"},
headers={"x-api-key": os.environ["PROPERTIESAPI_KEY"]},
timeout=120,
)
r.raise_for_status()
home = r.json()["data"]
c = home["calculated"]
print("last sale:", c["last_sale_date"], c["last_sale_price"])
if c["change_since_last_sale_pct"] is not None:
print(f"change: {c['change_since_last_sale_pct']}% vs {c['change_since_last_sale_basis']}")const url = "https://www.zillow.com/homedetails/1704-Shelbourne-Dr-Austin-TX-78752/29412761_zpid/";
const res = await fetch(
`https://api.propertiesapi.com/v1/zillow/property?url=${encodeURIComponent(url)}`,
{ headers: { "x-api-key": process.env.PROPERTIESAPI_KEY } },
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { data: home } = await res.json();
const c = home.calculated;
console.log("last sale:", c.last_sale_date, c.last_sale_price);
if (c.change_since_last_sale_pct != null) console.log(`change: ${c.change_since_last_sale_pct}% vs ${c.change_since_last_sale_basis}`);Common mistakes
- Reading dateSold on a listed home. It is usually null there; use calculated.last_sale_date.
- Treating change_since_last_sale_pct as appreciation. When the basis is zestimate, one side is Zillow's estimate, not a sale.
- Assuming price equals the sold price for every status. It only does for RECENTLY_SOLD homes with a disclosed price.
Related
- priceHistory
- price and currency
- zestimate
- Every field of the property endpoint
- Guide: Zillow price history API: turn events into a timeline
- All response fields
Zillow is a trademark of Zillow, Inc. PropertiesAPI is an independent service and is not affiliated with, endorsed by or sponsored by Zillow.
Last sale: dateSold, lastSoldPrice and calculated last sale questions
Why is lastSoldPrice null for a sold home?+
The sale price isn't public. Some states don't disclose sale prices, so Zillow shows the sale without a price.
Which field should I use for the last sale date?+
calculated.last_sale_date. It is read from priceHistory and works for listed, sold and off-market homes.
Is change_since_last_sale_pct adjusted for inflation?+
No. It is plain arithmetic on the two values, with no adjustment.