Crexi Commercial Real Estate Listings & Broker Scraper
Commercial property listings from Crexi with address, county, asking price, coordinates and the listing broker — and paywall placeholders dropped instead of passed through.
Verification
Output fields
Authoritative field set from the dataset schema.
| Field | Type |
|---|---|
listing_id | string|null |
name | string|null |
description | string|null |
property_types | array|null |
status | string|null |
asking_price | number|null |
square_footage | integer|null |
price_per_sqft | number|null |
address | string|null |
full_address | string|null |
city | string|null |
county | string|null |
state | string|null |
state_name | string|null |
zip | string|null |
latitude | number|null |
longitude | number|null |
brokerage_name | string|null |
broker_name | string|null |
broker_profile_url | string|null |
broker_listing_count | integer|null |
in_opportunity_zone | boolean|null |
has_offering_memorandum | boolean|null |
has_flyer | boolean|null |
image_count | integer|null |
thumbnail_url | string|null |
activated_on | string|null |
updated_on | string|null |
marketing_description | string|null |
details | object|null |
market_url | string|null |
notice | string|null |
notice_type | string|null |
source_url | string|null |
scraped_at | string|null |
Sample row
{
"source_url": "https://api.crexi.com/assets/2661656",
"listing_id": "2661656",
"name": "6229 Old Lockhart Road",
"description": null,
"property_types": [
"Retail",
"Industrial",
"Land"
],
"status": "On-Market",
"asking_price": 650000,
"square_footage": null,
"price_per_sqft": null,
"address": "6229 Old Lockhart Road",
"full_address": "6229 Old Lockhart Road, Buda, Travis County, TX 78610",
"city": "Buda",
"county": "Travis County",
"state": "TX",
"state_name": "Texas",
"zip": "78610",
"latitude": 30.0733165,
"longitude": -97.7213696,
"brokerage_name": "John B. Sanford Real Estate",
"broker_name": "J.D. Sanford",
"broker_profile_url": "https://www.crexi.com/brokers/jdsanf",
"broker_listing_count": 21,
"in_opportunity_zone": false,
"has_offering_memorandum": false,
"has_flyer": true,
"image_count": 4,
"activated_on": "2026-08-13T18:35:24.429Z",
"updated_on": "2026-08-19T14:40:14.293Z",
"details": {
"Asking Price": "$650,000",
"Property Type": "Retail, Industrial, Land",
"Zoning": "Residential",
"Lot Size (acres)": "8.32",
"Ground Lease": "No"
},
"market_url": "https://www.crexi.com/properties/2661656/texas-6229-old-lockhart-road"
} Inputs
| Field | Type | Req | Default | Description |
|---|---|---|---|---|
query | string | no | "austin texas" | Where to search — a city, county, state or free-text phrase: 'austin texas', 'harris county', 'miami'. Leave blank to sweep the whole catalogue (242,488 listings on 2026-09-07), though Crexi will only page 1,499 deep into any one search, so a location is usually what you want. Kept forever alongside 'Locations' — saved tasks and API callers send it. |
queries | array | no | ["austin texas","san antonio texas"] | Search many locations in ONE run instead of starting a run per city. Takes the same values as 'Location or keyword'. Combined with it if you fill both, and duplicates are removed. The result cap below applies PER location, so one city cannot eat the whole run and leave the others looking empty. |
types | array | no | ["Retail","Office"] | Crexi's own property types, exactly as it spells them: Land, Retail, Office, Industrial, Multifamily, MixedUse, SpecialPurpose, Hospitality, SelfStorage, MobileHomePark, SeniorLiving. Anything else is rejected by Crexi, so an unrecognised value returns a free row naming the valid ones rather than failing the run. Leave empty for all types. |
minPrice | integer | no | — | Applied after fetching, because Crexi accepts askingPriceFrom and then ignores it — the total comes back unchanged either way. Setting any band drops listings with no published price ('Call for offers'), since a band is a statement about price that an unpriced listing cannot satisfy. |
maxPrice | integer | no | — | See 'Minimum asking price'. Leave both empty to keep unpriced listings. |
includeDetails | boolean | no | false | Adds the marketing description and Crexi's detail table (zoning, lot size, year built, cap rate, APN and so on) with one extra request per listing. Only the entries Crexi actually publishes come through: it writes paywalled values as runs of asterisks ('Year Built': '****'), and those are dropped rather than handed to you as text that survives an is-not-null filter. |
includeBrokers | boolean | no | false | Adds the listing broker's name, brokerage, public profile link and how many listings they hold, with one extra request per listing. Crexi publishes no broker email or phone number on this API at any tier, so those are not returned and are not implied. |
maxResults | integer | no | 200 | Cap on listings returned PER location, not per run — so a batch cannot silently return only the first rows overall and read as 'these cities have nothing'. Crexi refuses to page beyond 1,499 into any single search, so that is the hard ceiling per location whatever you set; when a search matches more, the run says how many it matched. |
proxyConfiguration | object | no | {"useApifyProxy":false} | Optional and off by default. Crexi's search API answered a plain client in about 0.24s from an ordinary connection, so a proxy is not needed and only adds latency (#145: never reach for one before the plain path is proven to fail). |
Pricing
Pay per result (listing) — no charge for empty runs.
Example: 1000 listings at $0.003/result ≈ $3.00.
FAQ
What are the asterisks in Crexi's data, and what do you do with them?
Crexi puts some listing attributes behind its own paywall, and instead of leaving them empty it returns a string of asterisks: 'Year Built': '****', 'APN': '********', 'Cap Rate': '*******'. Over 12 retail listings sampled on 2026-09-07 that happened on 9 of the 9 listings that carried Year Built, 6 of 6 for APN, and every occurrence of Cap Rate, Parking, Tenancy, Units, Beds, Net Rentable and Price per SqFt. This Actor drops them, so a field is either a real published value or absent. It matters more than it sounds: '****' is a non-empty string, so it passes an is-not-null filter, sorts as text, and quietly ruins any count of how complete your data is.
How many listings can one search return?
Up to 1,499 per location, and that ceiling is Crexi's rather than ours — the API rejects any request where offset plus count reaches 1500. The catalogue held 242,488 listings on 2026-09-07, and a single city can exceed the window: Austin matched 2,367 with no type filter. When a search matches more than can be paged, the run returns a free row saying how many matched against how many were reachable, so you know to narrow by property type, city or price band rather than assuming you have everything.
Can I filter by price, or by lease versus sale?
Price yes, lease-versus-sale no. Crexi's API accepts askingPriceFrom and askingPriceTo and then ignores them — with or without a band the match count is identical — so price is filtered here, on the values actually returned. Its isLease parameter behaves the same way, and rather than offer a switch that silently does nothing, this Actor does not have one. Setting any price band excludes listings marketed without a published price, since 'call for offers' cannot satisfy a band.
Do I get broker email addresses and phone numbers?
No, and no Crexi scraper can give you them from this API — the broker endpoint publishes a first and last name, the brokerage, a headshot, a public profile id and a listing count, and no contact fields at any tier. What you get is the broker's name, their brokerage, a link to their public Crexi profile and how many listings they currently hold, which is enough to identify and rank who is active in a market. Anything advertising CRE broker emails is getting them from somewhere other than here.
Which property types can I search?
Crexi's own eleven, spelled its way: Land, Retail, Office, Industrial, Multifamily, MixedUse, SpecialPurpose, Hospitality, SelfStorage, MobileHomePark and SeniorLiving. Their live counts on 2026-09-07 were Land 118,855, Retail 57,038, Office 38,854, Industrial 29,049, Multifamily 28,964, MixedUse 22,304, SpecialPurpose 15,999, Hospitality 5,711, SelfStorage 2,367, MobileHomePark 1,097 and SeniorLiving 838. A value Crexi does not recognise is rejected by Crexi with an error, so this Actor checks first and returns a free row naming the valid ones rather than failing the run.
What happens if Crexi is down or changes its response?
You get one free, unbilled row saying so — notice_type 'blocked' for an outage or refusal, 'source_changed' if the response no longer carries the result envelope this Actor reads. That distinction is the point: a commercial-listing scraper that starts returning nothing still completes as a SUCCEEDED run, so nothing on the Store's own failure rate can reveal it, and 'the source moved' and 'there are no listings here' look identical in a dataset unless one of them says which it is.
Do the coordinates come from Crexi or from a geocoder?
From Crexi. Latitude, longitude and county are on its own listing record, so every row carries a point the source itself published rather than one derived from an address string. That matters for mapping: a geocoded address can land on a street centroid or a city centroid and still look plausible.
How do I get only listings that are new since my last run?
Every row carries activated_on, the date Crexi made the listing live, and updated_on for its last change. Run on a schedule and filter on activated_on downstream. Rows carry Crexi's own stable listing id, so de-duplicating between runs is exact rather than address matching.
Do I pay per location or per listing?
Per listing returned. Submitting ten cities that hold 1,000 listings between them costs the same as one city with 1,000. The result cap applies per location rather than per run, so a busy city cannot use up the run and leave the others looking empty. Detail and broker enrichment add one request each per listing but no extra charge, and marker rows are free.
Is a proxy needed?
No. Crexi's search API answered a plain client in about 0.24 seconds from an ordinary connection, so the proxy input is off by default and enabling it only adds latency.
Guides & use cases
Related actors