---
title: "List Properties Sold in a ZIP — Market Data API"
description: "List residential properties sold in a US ZIP code over the last 365 days, each with sale price, sale date, and characteristics. Costs 1 credit per 20 results."
---

# List residential properties sold in a ZIP in the last 365 days

`GET /api/properties/sold/by-zip/`

1 credit per 20 properties

## What it returns

Every residential property sold in a given US ZIP code within the last 365 days, with sale date, sale price, coordinates, and basic property characteristics (bed/bath, square footage, year built, lot size). Sourced from the `residential_sold` collection — a deduplicated rollup of MLS sold records refreshed on demand when the per-ZIP cache is older than 4 hours.

## When to use it

- Build a "recent sales in my neighborhood" widget on a property page.
- Estimate market velocity for a ZIP code (sales per month, median DOM).
- Seed a comps analysis without specifying individual subject properties.
- Sanity-check listing prices against actual sale prices.

## Date range

By default the endpoint returns the full trailing 365 days. Pass `start_date`, `end_date`, or both (`YYYY-MM-DD`) to narrow that to a specific window — for example one calendar month at a time instead of re-pulling the whole year on every poll:

```
?zip=95630&start_date=2026-08-01&end_date=2026-08-31
```

Both bounds are optional and inclusive, and both are independent: pass only `start_date` for an open-ended window, or only `end_date` to cut off recent sales. Passing neither leaves existing behaviour unchanged.

Either bound must fall within the last 365 days. An older date returns `400` naming the earliest date we can serve, rather than quietly returning a partial set.

## Property type

Pass `property_type` to keep only the types you model. One value or a comma-separated list, matched against the same `property_type` field each row returns:

```
?zip=95630&property_type=single_family
?zip=95630&property_type=single_family,townhomes&start_date=2026-08-01&end_date=2026-08-31
```

Omitting it returns every type, exactly as before. An unknown value returns `400` listing the accepted values rather than an empty result.

## Pricing

**1 credit per 20 properties returned**, rounded up. A request that returns 100 properties costs 5 credits. Use the `limit` parameter to cap the cost — a `limit=20` always costs exactly 1 credit. Charged only on `200`; `402`/`400`/`5xx` cost nothing.

## Errors

| Status | Meaning |
|---|---|
| 400 | Missing `zip` parameter, non-integer `limit`, a malformed / out-of-window `start_date` or `end_date`, or an unknown `property_type`. |
| 401 | Authentication failed or account missing. |
| 402 | No subscription, or insufficient credits for the property count this ZIP would return. The error body includes the credit estimate so you can lower `limit` and retry. |
| 503 | Upstream datastore or refresh error. Safe to retry. |

## FAQ

### How fresh is the data?
The per-ZIP cache refreshes when stale (older than 4 hours) on demand. A first request to a cold ZIP may take longer while the refresh runs; subsequent requests within the 4-hour window hit cache.

### Why do some properties have `sold_price: null`?
The MLS suppresses price on private sales, off-market transfers, and some agent-sale records. The property still appears (it sold), but the price field is `null`. Filter on your side if you need price-bearing records only.

### What counts as "residential"?
Everything the sold feed files as residential: single-family, condos, townhomes, multi-family, apartments, mobile homes, plus a small tail of other types. Each row's `property_type` says which, and the `property_type` filter takes the same values. Commercial and land-only records are rare here — they live in other collections.

### Why is `dom` sometimes `null`?
Days-on-market requires listing-history dates that older records may not carry. For records that lack `dom` natively, we compute it best-effort from `list_date` to `sold_date`; when neither path resolves, the field is `null`.
