Apartment Building vs. Individual Unit: What Does the Zillow Data Describe?
When a lookup returns an apartment building, some fields describe the building and some describe one unit. An annotated field guide, with code to classify and key records safely.
Two kinds of page, one response shape
Zillow publishes rental apartments in two ways. A unit in a condo building or a single rental home has its own property page under zillow.com/homedetails with its own zpid. A large rental community often has a building page instead, under zillow.com/apartments, that lists floor plans and available units together. Both kinds of page come back from the API in the same response shape, but they do not mean the same thing.
This guide is about meaning: which fields describe the building, which describe a single unit, and how to store each kind so you never compare a building with a house as if they were the same type of record. Making the right address match in the first place is a separate topic, covered in the address-matching guide.
The selected-unit rule
A building page has no single price, bedroom count or floor area of its own. To keep the response useful, the API reports one unit from the building: the lowest-priced unit that is available at the time of collection, meaning it has no move-in date or a move-in date on or before that day. If no unit is available yet, it reports the lowest-priced listed unit. price, beds, baths, livingArea and unit then all describe that same unit, so the numbers are internally consistent.
Everything else describes the building listing: the address, coordinates, description, photos and schools. The other units are not returned. A response for a building therefore tells you what the cheapest available unit asked at collection time, not the building's typical rent, its range of rents or how many units are open.
The rule is documented in the property reference, implemented in the response normalizer, and covered by tests built from recorded live responses for several Chicago rental buildings. Those recordings are a snapshot from our own testing, not a guarantee about every building page, so check a few of your own buildings before relying on the pattern.
Field by field: building or unit
Use this table as the annotation key. The right column is what you should assume when a response is a building.
| Field | On a unit or home page | On a building page |
|---|---|---|
| zpid | The property's own ID | Null. A building has no zpid of its own |
| detailUrl | The homedetails page | The apartments page; use it as the building's stable ID |
| address, latitude, longitude | The home | The building |
| unit | The home's unit, if any | The selected unit, which can have leading zeros |
| price | Asking price or monthly rent | Monthly rent of the selected unit |
| beds, baths, livingArea | The home | The selected unit. Zero beds means a studio |
| homeType | For example CONDO or SINGLE_FAMILY | Usually APARTMENT |
| zestimate, rentZestimate | Shown when Zillow shows them | Usually null |
| priceHistory, taxHistory | The home's events and tax years | Often empty |
| description, photos, schools | The home's listing | The building listing |
| calculated.price_per_sqft | For sale or asking prices | Null, because price is a rent |
Why a building's zpid is null
A building page is often labeled with the zpid of one of its units, and different sources pick different units. In our testing, a building's label pointed at a unit that, looked up on its own, was an off-market home. Returning that zpid would make your database join the building to the wrong record and store it under the wrong key.
So the API returns zpid as null for a building and leaves detailUrl as its stable identifier. Units keep their own zpids as usual. In your schema, make zpid nullable and add a separate key column, as the code below does. Do not fill the null with a zpid you found elsewhere.
# Illustrative building response, trimmed (values made up, shape as returned)
{
"data": {
"zpid": null,
"detailUrl": "https://www.zillow.com/apartments/chicago-il/example-tower/AbC123/",
"address": "100 W Example St, Chicago, IL 60607",
"unit": "0910",
"homeStatus": "FOR_RENT",
"statusText": "For rent",
"homeType": "APARTMENT",
"price": 2385,
"beds": 0,
"baths": 1,
"livingArea": 480,
"zestimate": null,
"rentZestimate": null,
"priceHistory": [],
"taxHistory": [],
"photoCount": 35,
"calculated": { "price_per_sqft": null, "days_on_market": null, "...": "..." }
},
"meta": { "cache_key": "...", "fetched_at": "2026-10-20T15:04:05.000Z", "...": "..." }
}Classify each record and key it correctly
record_kind decides whether a response is a building, a unit or a home. record_key produces a stable key that never uses a unit's zpid for a building. describe renames the unit-level fields of a building with a sample_unit prefix, so a spreadsheet reader cannot mistake the cheapest unit's rent for the building's rent.
Treat zero as a value, not as missing. A studio legitimately has zero bedrooms, and the selected unit in a building is often a studio because studios are often among the lowest-priced units. Check for None, not for falsy values, when you decide whether a field is present.
"""record_kind.py: decide what a response describes before you store or compare it."""
def record_kind(data):
url = data.get("detailUrl") or ""
if "/apartments/" in url and data.get("zpid") is None:
return "building" # a zillow.com/apartments/ page; one unit's numbers inside
if data.get("unit"):
return "unit" # a single unit with its own zpid
return "home"
def record_key(data):
"""Stable key for storage and joins: never a unit's zpid for a building."""
kind = record_kind(data)
if kind == "building":
return "building:" + data["detailUrl"]
return "zpid:" + str(data["zpid"]) if data.get("zpid") else "url:" + (data.get("detailUrl") or "")
def describe(data):
kind = record_kind(data)
row = {"kind": kind, "key": record_key(data), "address": data.get("address")}
if kind == "building":
# building-level: address, coordinates, description, photos, schools
# unit-level: price, beds, baths, livingArea and unit all describe ONE unit
row.update(sample_unit=data.get("unit"), sample_unit_rent=data.get("price"),
sample_unit_beds=data.get("beds"), sample_unit_sqft=data.get("livingArea"),
note="lowest-priced unit available at collection; other units not returned")
else:
row.update(unit=data.get("unit"), price=data.get("price"), beds=data.get("beds"),
living_area=data.get("livingArea"))
return row
What a building response is good for
A building response answers questions like these well: where the building is, what the listing says about it, what the lowest available rent was on a given day, and what that unit looks like. Saving building responses over time lets you build your own record of that entry-level rent, as long as you keep the collection time and the unit number with every observation, since the selected unit can change between collections.
It is not a good source for questions it cannot answer: a building's average rent, the number of vacancies, the full unit mix or rents for a specific floor plan. Those need the building's own page or a different data source. If you need a specific unit, look up that unit's own homedetails page when one exists, which returns its own zpid and its own price.
- Label building rows clearly in exports and dashboards.
- Keep the selected unit's number with its rent in every saved observation.
- Do not average building rows with single-home rows.
- Do not compare a building's selected-unit rent with a house's rent Zestimate without saying so.
- Use detailUrl, not zpid, to deduplicate buildings.
Where this fits in rental research
Rental comparisons work best when each row is the same kind of thing. Put buildings, single units and houses in separate groups, compare monthly asking rents only within a group, and keep the unit size next to the rent. The rental data guide walks through a comparison worksheet that applies these rules.
When a building row and a single-unit row describe homes in the same complex, keep both. They answer different questions: the building row records the entry-level rent the building advertised, and the unit row records one specific home. Store the record kind next to each observation so a later analysis can filter by it instead of guessing from the address.
Sources and documentation
Product details checked against the linked documentation on October 22, 2026. Code and sample inputs are illustrative unless stated otherwise.
Written by PropertiesAPI, an independent property data service. We are not affiliated with Zillow. Source data may be incomplete or change over time.