Skip to main content
GET
List MLS sale listings near a location
Each row contains the newest MLS listing on one parcel. The default includes ACTIVE and PENDING sale listings. The route excludes rentals. A SOLD record is a feed status. It does not prove a recorded closing deed.

Choose a location

Use a point, viewport, parcel ID, ZIP list, city, or street address. An address needs one five-digit ZIP or a city. ZIP takes precedence when both are present. An explicit unit takes precedence over a unit in the street line. Do not combine an address with coordinates, a viewport, or a parcel ID. The API returns candidates when the address identifies several parcels. Distance is the default order with a reference point. Other locations use listed_desc. All sorts put null values last. Every sort breaks ties on the market, then on the parcel ID. meta.geometry shows the resolved location.

Read the response

  • property contains assessor facts, including bedrooms, bathrooms, area, and year built.
  • holder identifies the current registry holder. It does not prove who offered an older listing.
  • listing contains the newest MLS status, price, dates, and ownership test.
  • investorlift counts public listings on the parcel at any time. Its as_of dates that source.
  • summary covers all filtered matches before the cursor and page limit.
  • summary.as_of_by_market lists every selected market, including markets with no matches.
Use status=sold with sold_since and sold_until for an inclusive closing-date range. Use sold_price_min and sold_price_max for closing-price bounds. Use sold_price_asc or sold_price_desc to sort by closing price. The existing price sorts and list_price_* filters still read asking prices. Use status=off_market with off_market_since for ended records. A date filter does not change the status selection. All filters intersect. A range filter on an assessor fact removes a parcel that has no value for that fact. This rule applies to the bedroom, bathroom, area, and year built bounds. on_investorlift=false excludes parcels with any public Investorlift listing. That filter needs wholesale coverage in every selected market.

Sold statistics

summary.sold describes sold records within the complete matching set. It gives closing-price, closing-price-per-square-foot, and selling-time medians with separate usable sample counts. Each median needs five usable observations. Counts remain visible below the floor. The square-foot metric includes only houses and condos with a positive price and living area. Missing closing prices remain null. The API does not substitute asking prices or deed prices. A mixed status request with a closing-date bound excludes rows without a closing date. For current inventory and recent sold properties, send separate requests with their respective status and date filters.

Limits

This source retains the newest listing per parcel. It does not retain every listing cycle. Use historical sold listings for earlier observed sales after relisting, within the published history ZIPs. The source dates describe observations. They do not establish live availability. Use the rental route for asking rents. Use property comps for comparable sales. A cursor belongs to its filters and dataset versions. Restart pagination after invalid_cursor. A source refresh during the request returns an error. The page and summary never mix source snapshots. These routes validate the source on every request, including requests with If-None-Match. Search nearby inventory shows the three listing routes together.

Authorizations

Authorization
string
header
required

API key from the developer console (starts with zpka_). Create one at https://developers.investorlift.com/get-a-key. The same key works on POST /mcp.

Query Parameters

lat
number

Point latitude (with lng). With radius_miles it is the search geometry. With bbox it is the reference point only.

Required range: -90 <= x <= 90
lng
number

Point longitude (with lat).

Required range: -180 <= x <= 180
radius_miles
number

Search radius in miles around the point or around the centre of the property_id parcel, 0.25 to 20 (default 2). Not allowed with bbox.

Required range: 0.25 <= x <= 20
bbox
string

Viewport as west,south,east,north (WGS84 degrees). West must be less than east, south less than north, and the diagonal at most 40 mi. It must intersect a loaded market's coverage bbox (422 outside_coverage otherwise). It can carry lat + lng (without radius_miles) as the reference point for distances and sort=distance.

property_id
string

Parcel geometry: search around the centre of that parcel, with radius_miles. Not allowed with lat, lng or bbox.

Pattern: ^prop_[0-9a-f]{32}$
zip
string[]

ZIP list for a place search. With address, use exactly one five-digit ZIP. ZIP takes precedence over city for an address.

Required array length: 1 - 50 elements
Pattern: ^\d{5}$
city
string

Postal city for a place search or address resolution. With address, ZIP takes precedence when both are present.

Required string length: 1 - 100
address
string

Street address to resolve before the radius search. Needs one five-digit ZIP or a city. ZIP takes precedence over city.

Required string length: 1 - 200
unit
string

Unit of the address. Only with address. An explicit unit takes precedence over a unit in the street line.

Required string length: 1 - 16
listed_since
string<date>

Keep listing dates on or after this date. This filter does not change the status filter.

segment
enum<string>[]

Keep these assessor segments: SFR, CONDO_TH, or OTHER.

Minimum array length: 1

Parcel segment: SFR, CONDO_TH (condo or townhouse) or OTHER, a mixed bucket of manufactured, multi-family, land and commercial parcels.

Available options:
SFR,
CONDO_TH,
OTHER
beds_min
integer

Minimum assessor bedroom count, inclusive.

Required range: 0 <= x <= 9007199254740991
beds_max
integer

Maximum assessor bedroom count, inclusive.

Required range: 0 <= x <= 9007199254740991
sqft_min
integer

Minimum living area in square feet, inclusive.

Required range: 0 <= x <= 9007199254740991
sqft_max
integer

Maximum living area in square feet, inclusive.

Required range: 0 <= x <= 9007199254740991
baths_min
number

Minimum assessor bathroom count, inclusive. A half bathroom counts as 0.5.

Required range: 0 <= x <= 9007199254740991
baths_max
number

Maximum assessor bathroom count, inclusive. A half bathroom counts as 0.5.

Required range: 0 <= x <= 9007199254740991
year_built_min
integer

Earliest assessor year built, inclusive.

Required range: 1600 <= x <= 2100
year_built_max
integer

Latest assessor year built, inclusive.

Required range: 1600 <= x <= 2100
investor_held
enum<string>

True keeps a PASSED ownership test. False keeps other results. Omit the filter to keep every result.

Available options:
true,
false
limit
integer
default:100

Page size, 1-500 (default 100).

Required range: 1 <= x <= 500
cursor
string

Opaque cursor from page.next_cursor of the previous page. A change of query, sort, weights or data version invalidates it (400 invalid_cursor). Then restart from page 1.

Required string length: 1 - 4096
status
enum<string>[]

Statuses to keep. Default on_market means active or pending. Every status excludes rentals.

Minimum array length: 1
Available options:
on_market,
active,
pending,
sold,
off_market
status_detail
enum<string>[]

Keep these listing details. Only active and pending records carry these values.

Minimum array length: 1
Available options:
NEW,
PRICE_CHANGE,
COMING_SOON,
BACK_ON_MARKET,
CONTINGENT,
UNDER_CONTRACT
off_market_reason
enum<string>[]

Keep these end reasons. Use status=off_market because this filter does not change the default status.

Minimum array length: 1
Available options:
CANCELED,
EXPIRED,
OFF_MARKET
sold_since
string<date>

Keep sold dates on or after this date. Use status=sold to include sold rows.

sold_until
string<date>

Keep sold dates on or before this date. Use status=sold to include sold rows.

sold_price_min
integer

Minimum closing price in whole US dollars, inclusive. Missing prices do not match. Use status=sold to include sold rows.

Required range: 0 <= x <= 9007199254740991
sold_price_max
integer

Maximum closing price in whole US dollars, inclusive. Missing prices do not match. Use status=sold to include sold rows.

Required range: 0 <= x <= 9007199254740991
off_market_since
string<date>

Keep end dates on or after this date. Use status=off_market to include ended rows.

list_price_min
integer

Minimum asking sale price in whole US dollars, inclusive.

Required range: 0 <= x <= 9007199254740991
list_price_max
integer

Maximum asking sale price in whole US dollars, inclusive.

Required range: 0 <= x <= 9007199254740991
days_on_market_min
integer

Minimum days on market at the source date, inclusive.

Required range: 0 <= x <= 9007199254740991
days_on_market_max
integer

Maximum days on market at the source date, inclusive.

Required range: 0 <= x <= 9007199254740991
on_investorlift
enum<string>

True requires a public Investorlift listing on this parcel at any time. False requires none. Missing source coverage is an error.

Available options:
true,
false
sort
enum<string>

Order by distance, newest listing, newest sale, newest end, price, or descending duration. Price sorts use asking prices unless named sold_price. Null values come last. Every sort breaks ties on the market, then on the parcel id.

Available options:
distance,
listed_desc,
sold_desc,
off_market_desc,
price_asc,
price_desc,
sold_price_asc,
sold_price_desc,
dom_desc

Response

Default Response

data
object[]
required

The rows of this page.

page
object
required

Pagination: the page size, the rows returned and the cursor for the next page. Paged lists carry no total. The summary block does.

meta
object
required

Response metadata: when the API produced it, which markets it covers, and how fresh they are.

summary
object
required