GET /v1/listings
Search and page through listings. Every parameter is optional; combine as many as you like. Unknown parameters return 400.
Query parameters
This table is generated from the same allowlist the server validates against.
| Name | Type | Description |
|---|---|---|
state | string | Two-letter state code(s), comma-separated. Example: TX,OK |
city | string | City name (case-insensitive exact match). |
zip | string | ZIP code(s), comma-separated; trailing * matches a prefix (e.g. 973*). |
county | string | County name (case-insensitive). Populated for a subset of listings. |
property_type | string | One or more of: land, industrial, office, retail, multifamily, hospitality, special, other |
property_subtype | string | Substring match on the source's subtype (e.g. warehouse). |
transaction | string sale · lease | sale = for sale (incl. sale/lease); lease = for lease (incl. sale/lease). |
status | string | One or more of: active, under_contract, sold, leased, withdrawn, closed |
min_price | number | Minimum asking sale price (USD). Accepts 1.5m / 250k. |
max_price | number | Maximum asking sale price (USD). |
min_sf | number | Minimum building square feet. |
max_sf | number | Maximum building square feet. |
min_acres | number | Minimum land acres. |
max_acres | number | Maximum land acres. |
min_cap | number | Minimum cap rate (percent). |
max_cap | number | Maximum cap rate (percent). |
bbox | string | west,south,east,north in decimal degrees. |
near | string | lat,lng centre for a radius search (use with radius_mi). |
radius_mi | number | Radius in miles around `near` (max 200). |
updated_since | string | Only listings changed after this ISO-8601 timestamp. |
first_seen_since | string | Only listings first observed after this timestamp. |
brokerage | string | Brokerage public id (from /v1/brokerages). |
has_price | boolean | Only listings with a numeric asking price. |
has_brochure | boolean | Only listings with a brochure/flyer link. |
q | string | Full-text search over name, address, city and description (web-search syntax: quoted phrases, OR, -excluded). |
include_inactive | boolean | Include listings no longer published (plan-gated). |
sort | string updated_at · first_seen · sale_ask_total · building_sf · land_acres | Sort column (default updated_at). |
order | string asc · desc | Sort direction (default desc). |
limit | integer | Page size (plan-capped; default 100). |
cursor | string | Opaque cursor from the previous page's meta.next_cursor. |
fields | string | Comma list of fields to return, or `all`. |
format | string json · geojson · csv | Response format (default json). |
include_total | boolean | Add meta.total (a count query; rate-limited harder). |
Numbers accept shorthand: min_price=1.5m, min_sf=50k, $1,500,000. Lists are comma-separated: state=TX,OK,AR, property_type=industrial,land.
Response envelope
{
"data": [ …listings… ],
"meta": {
"count": 100, // records in this page (what you were billed)
"records_billed": 100,
"next_cursor": "eyJz…", // null on the last page
"total": 4821, // only with include_total=1
"quota": {"limit": 10000, "used": 1300, "remaining": 8700, "hard_cap": 50000, "period_end": "2026-10-01"},
"source": "managed listings; health-checked and refreshed weekly",
"notice": "Broker email/phone are included on paid plans." // Free plan only
},
"links": {"next": "https://listingsfeed.com/v1/listings?…&cursor=eyJz…"}
}
Headers: X-Quota-Limit, X-Quota-Used, X-Quota-Remaining, X-Next-Cursor, X-RateLimit-Limit, X-RateLimit-Remaining.
Pagination
Keyset cursors, signed and opaque. Pass meta.next_cursor back as cursor with the same filters, sort and order; a cursor from a different sort is rejected with 400. Pages are stable while you walk them: new listings arriving mid-walk do not shift rows. limit is capped by plan (Free 100, pay-as-you-go 1,000).
Sorting
sort one of updated_at (default), first_seen, sale_ask_total, building_sf, land_acres; order=asc|desc. Nulls sort last in descending order.
Field selection
By default a listing omits description, highlights, photos and attributes to keep pages small. Ask for them with fields=all or a comma list, e.g. fields=hash_id,address,city,state,lat,lng,building_sf,brokers. hash_id is always included.
Formats
format=json(default) — the envelope above.format=geojson— aFeatureCollection; each feature hasid = hash_id, a Point geometry[lng, lat]and every other field as properties. Listings without coordinates getgeometry: null.format=csv— one row per listing, brokers flattened tobroker_name / broker_email / broker_phone(semicolon-separated when several). Plan-capped rows per call; continue withX-Next-Cursor.
Geography
bbox=west,south,east,north— decimal degrees.near=lat,lng&radius_mi=25— great-circle radius, max 200 miles.zip=761*— prefix match;zip=76137,76140exact list.county— populated for a growing subset; checkfill_ratesin/v1/stats.
Change detection
updated_since compares against updated_at, which moves only when a field actually changes (not on every weekly re-observation). first_seen_since finds new arrivals. With include_inactive=1 (pay-as-you-go) retired listings are returned with is_active=false and, when merged, superseded_by.
Contacts
brokers[].email and brokers[].phone are returned on paid plans; the Free plan gets names only and a meta.notice. Contacts that a broker has asked us to suppress are removed for everyone.
GET /v1/listings/{hash_id}
Returns one listing with every field (equivalent to fields=all) and bills one record. Retired listings still resolve, with is_active=false. Unknown id → 404.
Examples
# Land over 100 acres for sale in the Southeast, newest first
/v1/listings?state=GA,SC,NC,TN,AL&property_type=land&transaction=sale&min_acres=100&sort=first_seen
# Everything a brokerage has active
/v1/listings?brokerage=7c1d0a9b3e5f2c4d8a1b&include_total=1
# Office for lease near downtown Seattle
/v1/listings?near=47.6062,-122.3321&radius_mi=5&property_type=office&transaction=lease
# Priced industrial with brochures, as CSV
/v1/listings?property_type=industrial&has_price=1&has_brochure=1&format=csv&limit=1000