Crexi Scraper - Commercial Property Listings for Sale avatar

Crexi Scraper - Commercial Property Listings for Sale

Pricing

from $1.00 / 1,000 listing results

Go to Apify Store
Crexi Scraper - Commercial Property Listings for Sale

Crexi Scraper - Commercial Property Listings for Sale

Scrape crexi.com commercial listings: asking price, cap rate, square footage, units, year built, lot size, APN, zoning, address, coordinates, status, photos plus agent and brokerage contacts and 100+ fields. Search by city, ZIP or keyword with type, price and sort filters, or paste URLs.

Pricing

from $1.00 / 1,000 listing results

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

1

Bookmarked

8

Total users

5

Monthly active users

12 days ago

Last modified

Share

Crexi Scraper: Commercial Property Listings, Pricing & Broker Contacts

Crexi Scraper turns Crexi into structured commercial real estate data you can pull on demand. Search any city, ZIP or keyword with property type, price and sort filters, or paste listing URLs directly, and get a clean JSON record per listing: asking price, cap rate, square footage, units, year built, lot size, APN, zoning, full address with coordinates, status, the complete photo gallery, and the listing agent and brokerage contact details. Resume an interrupted crawl, or turn on Incremental mode for recurring monitoring, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.

Why This Scraper?

  • Two ways to start. Search by location, property type and price filters, or paste crexi.com listing URLs directly. No need to build search queries by hand.
  • Deep per-listing detail. Square footage, units, year built, stories, lot size, cap rate, net operating income, APN, zoning and the full marketing description.
  • Agent and brokerage contact cluster on every listing, including license numbers and the public profile id.
  • The complete photo gallery, not just the thumbnail.
  • Filter and sort control. Property type, asking-price band, priced-only, and four sort orders in either direction.
  • Resume or monitor. Resume an interrupted crawl with a pasted run id, or turn on Incremental mode for recurring runs that return only what is new or changed.
  • Optional export into the apps you already use, via MCP connectors, without ever changing the dataset.

Use Cases

  • Commercial real estate investors: track new listings and price or cap-rate changes across markets to spot deals early.
  • Brokerages and appraisers: build comparable-listing and market datasets by property type and location.
  • PropTech and data teams: feed listing data into internal tools, dashboards or CRMs through the API or MCP connectors.
  • Market researchers: monitor inventory levels, pricing and days-on-market trends over time with Incremental mode.
  • Lead generation: collect agent and brokerage contact details tied to currently active listings.

Data You Get

Sample shape: values are illustrative placeholders, not from a live listing.

FieldExample
id1000000
name"Maple Court Apartments"
property_type"Multifamily"
status"Active"
askingPrice4250000
capRate6.25
squareFootage48000
specifications.units64
yearBuilt1998
stories3
lotSize2.4
netOperatingIncome265000
apn"000-000-000"
zoning"R-4"
fullAddress"100 Main St, Austin, Travis County, TX 78701"
city / state"Austin" / "TX"
latitude / longitude30.2672 / -97.7431
urlCrexi link to the listing
imagesfull gallery image URLs (when Fetch full details is on)
brokerageName"Example Commercial Realty"
agent_info.name"Jane Doe"
agent_info.phone"+10000000000"

Each listing also carries subtypes, marketingDescription, licenseNumbers, numberOfAssets, createdOn, activatedOn, updatedOn, detailFetched and a set of boolean flags such as isSold and isPrivate; detail-only fields stay empty when Fetch full details is off, since only the search card is returned in that case.

How to Use

  1. Pick a mode: search (build from your locations and filters) or url (paste crexi.com listing URLs).
  2. For search mode, set Locations or keywords and any of property types, price band or sort order.
  3. Turn on Fetch full details for square footage, cap rate, gallery and agent contact, and set Max items to control run size and cost.
  4. Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.

Search by location and property type:

{
"mode": "search",
"locations": ["Austin, TX"],
"propertyTypes": ["Multifamily"],
"maxItems": 20,
"fetchDetails": true
}

Search a price band, priced listings only, sorted by cap rate:

{
"mode": "search",
"locations": ["Dallas, TX"],
"priceMin": 1000000,
"priceMax": 25000000,
"includeUnpriced": false,
"sortBy": "capRate",
"sortDirection": "Descending",
"maxItems": 50
}

Supported URL shapes (URL mode):

ShapeExampleResult
Detail page, with slughttps://www.crexi.com/properties/2597217/new-york-48-w-48th-streetthat listing
Detail page, id onlyhttps://www.crexi.com/properties/2597217that listing
Listing data linkhttps://api.crexi.com/assets/2597217that listing
Search page with type filterhttps://www.crexi.com/properties?types%5B%5D=Officesearch for those property types
State / city pagehttps://www.crexi.com/properties/TX/Austinsearch for Austin, TX
Lease listing pages (index, state, city)https://www.crexi.com/lease/properties/TX/Austinnot supported: skipped with a warning, because the search has no lease filter and would return all listings (lease detail URLs work)

Default change: the urls field now defaults to one example of each distinct shape (detail page, property-type search, state/city page), so a URL-mode run with the field left untouched scrapes those examples instead of nothing. Query parameters or extra path parts on a pasted search URL that the search does not use (for example ?page=2) are ignored and listed in a log warning. Search-mode filters (states, cities, price band, sort, Get more than ~1,500 results) are ignored for pasted URLs; the URL alone defines the search, so use search mode for price and sort. A search that matches nothing returns zero listings and the run finishes successfully with a warning in the log and the status message "No listings returned for the given search or URLs" (exit code 0), so check the status message for empty results.

Paste listing URLs directly:

{
"mode": "url",
"urls": [
"https://www.crexi.com/properties/1000000/austin-maple-court-apartments",
"https://www.crexi.com/properties/1000001/dallas-oak-plaza"
],
"fetchDetails": true
}

Monitor a search on a schedule and get only what changed:

{
"mode": "search",
"locations": ["Austin, TX"],
"propertyTypes": ["Multifamily"],
"maxItems": 200,
"incrementalMode": true
}

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/crexi-commercial-listings-scraper").call(
run_input={"mode": "search", "locations": ["Austin, TX"], "maxItems": 20}
)
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
print(listing["name"], listing["askingPrice"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/crexi-commercial-listings-scraper').call({
mode: 'search',
locations: ['Austin, TX'],
maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Resume and recurring updates

  • Resume (resumeFromRunId) continues ONE specific interrupted crawl: paste a previous run id or dataset id and the actor skips every listing already collected there, so a large pull that got cut off can finish without duplicating or re-charging what it already returned.
  • Incremental mode (incrementalMode) is for running the SAME search again and again, for example daily. The actor remembers what it saw itself, in its own storage, so there is no id to paste. The first run returns everything as NEW. Later runs return NEW and UPDATED listings; UNCHANGED listings are suppressed and not billed unless emitUnchanged is on. Each returned record also gets changeType, changedFields, firstSeenAt and lastSeenAt. Turning on emitExpired also returns, and bills, listings no longer found, marked EXPIRED, but only after a run that fully scanned the tracked search; a run capped by Max items, or combined with Resume, never produces EXPIRED rows. REAPPEARED is only produced for a listing that was previously marked EXPIRED this way and is then found again on a later run; a search that has never used emitExpired will never see a REAPPEARED row, since nothing was ever marked gone in the first place. stateKey names or shares the stored state; leave it empty and the actor derives one automatically from the search or URL, the States, Cities and Get more than ~1,500 results settings and the detail settings, so different searches never mix state with each other. Turning on Incremental mode together with Resume on a search that already has saved state fails the run immediately, before anything is collected or charged. What is compared: every output field of a listing (price, status, type, size, cap rate, address, description, agent and brokerage details and so on) decides UPDATED, and changedFields lists the ones that moved. What is excluded (kept in the output, never a change): scrapedAt, the result-count metadata, image, logo and photo URLs and the photo gallery list (thumbnailUrl, imageUrl, brokerLogoUrl, brokerTeamLogoUrl, logoUrl, photoUrl, images; the gallery size in numberOfImages still counts), the listing's rank in the result page (the top-level position and number only; a number inside agent details, such as a licence number, still counts as a change), session-specific flags (isSavedToBoards, userIsAssetOwner, userCanDownloadMd, userExecutedCA), and the order of lists. The other exclusions apply at any depth. Add your own with ignoreFieldsForChanges. With Incremental mode off, output is exactly as before.

Send results into your apps (MCP connectors)

Optionally send the scraped listings into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape, and the Apify dataset is never changed.

What gets written to the connector: a condensed, human-readable summary of each listing, not the full JSON. Each listing becomes one entry with a title and its key fields, asking price, property type, square footage, cap rate, year built, address, status, brokerage, agent and URL, flattened to plain text, plus the opening of the marketing description. The complete record always stays in the Apify dataset.

  1. Authorize a connector once under Apify, Settings, API & Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into your apps" input field. If the picker is empty, you have not authorized a connector yet.
  3. For Notion, also set notionParentPageUrl to the page where listing pages should be created; Notion gets a full page per listing, other connectors get a best-effort write or digest.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

Input Parameters

ParameterTypeDefaultDescription
modestringsearchsearch builds searches from your locations and filters, url loads the listing URLs you paste.
locationsarray[] (prefill: ["Austin, TX"])Locations or keywords to search, for example Austin, TX or 90001. Each entry is its own search. Empty searches all listings nationwide. Search mode only.
propertyTypesarraynoneProperty types to narrow the search, for example Multifamily, Office, Retail, Industrial, Land. Search mode only.
statesarraynoneTwo-letter US state codes (for example TX). Only listings located in those states. Search mode only.
citiesarraynoneCity names (for example Austin). Combine with states to disambiguate. Search mode only.
moreResultsbooleanfalseCrexi stops paging at about 1,500 results per search. Turn on to split each search into asking-price bands (plus one pass for unpriced listings) and merge the de-duplicated results; each band is still capped at about 1,500. Fills bands in order up to Max items. Search mode only.
priceMinintegernoneOnly return listings with an asking price at or above this amount.
priceMaxintegernoneOnly return listings with an asking price at or below this amount.
includeUnpricedbooleantrueInclude listings without a published asking price (auctions, call-for-offers).
sortBystringrankrank (best match), price, activatedOn (newest), or capRate.
sortDirectionstringDescendingDescending or Ascending.
urlsarrayone example of each supported shape (see the URL table)crexi.com links to load. URL mode only. Leaving the field untouched scrapes those examples (default changed from empty).
resumeFromRunIdstringnonePaste a previous run id or dataset id to continue a large crawl without returning or re-charging for listings already collected there. For recurring monitoring of the same search, use Incremental mode instead.
incrementalModebooleanfalseTurn on for recurring monitoring. Returns only NEW/UPDATED listings (plus REAPPEARED where it applies) after the first run; state is remembered by the actor itself.
stateKeystringnoneName a monitoring campaign to keep its state stable, or deliberately share state across differently-configured runs. Leave empty to derive a key automatically from the search or URL and detail settings. Incremental mode only.
emitUnchangedbooleanfalseAlso return, and bill, listings unchanged since the last run, marked UNCHANGED. Incremental mode only.
emitExpiredbooleanfalseAlso return, and bill, listings no longer found, marked EXPIRED. Only produced after a run that fully scanned the tracked search, not when Max items capped it or Resume was used. Incremental mode only.
ignoreFieldsForChangesstring listnoneExtra field names to leave out of change detection. A plain name applies at any depth, a dotted path like details.zoning targets one path. Excluded fields stay in the output but never trigger UPDATED. Incremental mode only.
fetchDetailsbooleantrueLoad each listing page for square footage, units, year built, stories, lot size, cap rate, APN, marketing description, the full image gallery and agent and brokerage contact details. Adds a small per-item charge.
maxItemsinteger20The single cap on the run: stop after this many listings, across all searches and URLs. 0 = unlimited.
maxPagesintegernone (unlimited; defers to Max items)Optional safety bound on result pages per search. Left empty or 0, it does not cap the run; the run stops at Max items.
proxyobject{"useApifyProxy": true}Connection settings. The default tier is the lowest-cost option and works on every plan.
maxResidentialMBinteger0When using a Residential connection group, cap residential traffic at this many MB, then automatically switch to the lower-cost tier. 0 = unlimited.
mcpConnectorsarraynoneOptional MCP connectors to pipe results into (Notion, Linear, Airtable, Apify). Never changes the dataset.
notionParentPageUrlstringnoneNotion page URL or id under which listing pages are created. Notion connector only.
maxNotifyListingsinteger50Cap on items written to each connector per run. Does not affect the dataset.

Agent fields and what is not available

With fetchDetails on, each row also carries agentSlug (the agent public profile id), profile_url, licenseNumber and licenseState (first licence) next to the existing licenseNumbers, licenseStateCode and numberOfAssets. Agent email addresses are not published by the listing data source and are not returned. A sale-versus-lease choice is not offered: the search data source exposes no lease filter. Property-record, tax-history and demographics data are not offered either (no open data path).

Output Example

Sample shape: values are illustrative placeholders, not from a live listing.

{
"id": 1000000,
"name": "Maple Court Apartments",
"url": "https://www.crexi.com/properties/1000000/austin-maple-court-apartments",
"property_type": "Multifamily",
"subtypes": ["Apartment Building"],
"status": "Active",
"askingPrice": 4250000,
"capRate": 6.25,
"squareFootage": 48000,
"specifications": { "units": 64, "year_built": 1998 },
"yearBuilt": 1998,
"stories": 3,
"lotSize": 2.4,
"netOperatingIncome": 265000,
"apn": "000-000-000",
"zoning": "R-4",
"marketingDescription": "Well-maintained 64-unit garden-style community in central Austin.",
"fullAddress": "100 Main St, Austin, Travis County, TX 78701",
"city": "Austin",
"state": "TX",
"zip": "78701",
"latitude": 30.2672,
"longitude": -97.7431,
"images": [
"https://images.crexi.com/assets/1000000/placeholder_3000x2000_resize.jpg"
],
"brokerageName": "Example Commercial Realty",
"agent_info": { "name": "Jane Doe", "company": "Example Commercial Realty", "phone": "+10000000000" },
"firstName": "Jane",
"lastName": "Doe",
"publicProfileId": "janedoe",
"licenseNumbers": ["TX 000000"],
"numberOfAssets": 12,
"scrapedAt": "2026-01-01T00:00:00.000Z"
}

Output views

The dataset has three tabs: Overview (price, cap rate, size, type, change type), Agents and brokerages (agent and brokerage contact cluster) and Changes (incremental mode) (changeType and changedFields).

Plan Requirement

This actor runs on any Apify plan, including the free plan with no residential access. Listing data is read from an open data service that the default datacenter connection reaches, so no residential connection is needed. The actor tries the connections in order and moves to a backup when one is refused. If the selected connection is unavailable (for example a residential group on a free plan), the run log says: "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." and after serving a page that way: "Served over the backup connection (the default connection was unavailable)." A residential group is available if you prefer it: pick it under Connection and optionally set a residential traffic budget, after which the run switches back to the lower-cost tier.

If nothing can be read the run fails with one of these messages instead of returning a silent empty dataset: "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required" (the account has no proxy access, or your own proxy URLs have invalid credentials); "No results: residential proxies are unavailable on this run ... and the backup connection could not load the requested Crexi pages" (upgrade or supply your own proxy URLs); or "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search." (try again later).

FAQ

How much does it cost?

Pay per event. Three events are registered: Listing result (apify-default-dataset-item) is charged once for each listing row returned to the dataset; Detail enrichment (detail-enrichment) is charged once per listing for which the full detail fetch (square footage, cap rate, gallery, agent contact) was actually delivered, and only when Fetch full details is on; Actor Start (apify-actor-start) is charged once per run. The Pricing tab shows the current rates. Unchanged, suppressed or failed listings are not charged. Use Max items to cap the cost of any run.

This actor collects only publicly available commercial listing data. You are responsible for how you use it: follow Crexi's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing facts such as price and square footage are generally not protected, but photos, marketing descriptions and agent details may be subject to third-party rights.

Can I get only new or changed listings on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only NEW and UPDATED listings by default, and unchanged ones are not billed unless you turn on emitUnchanged. A listing only comes back marked REAPPEARED if it was previously marked EXPIRED by a run that had emitExpired on and completed a full scan of the tracked search; if you never turn on emitExpired, a listing that drops out and later comes back is simply classified UPDATED or UNCHANGED like any other rescrape, never REAPPEARED.

What is the difference between Resume and Incremental mode?

Resume from a previous run continues ONE specific interrupted crawl using a pasted run or dataset id, and does not remember anything between separate scheduled runs. Incremental mode is for running the SAME search again and again, for example daily; the actor remembers what it saw itself, so there is no id to paste. Using both together on a search that already has saved incremental state fails the run immediately rather than mixing the two.

Why did my run fail instead of returning an empty dataset?

If every request was refused before any data could be read, the run fails with a clear connection-problem message, so "no listings today" is never confused with "nothing could be read". Check the connection warnings in the run log and try again. A genuinely empty search, where requests went through but the query just has no matches, succeeds with zero rows and a status message suggesting a broader location or different property types. An invalid Resume id also fails the run immediately, before anything is collected or charged.

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push a summary of each listing into Notion, Linear or Airtable once the run finishes.

๐Ÿ”— Want more real estate data?

Pair this actor with these related scrapers from the same team:

๐Ÿ  Redfin Scraper
Scrape redfin.com home listings: price, beds, baths, sqft, lot, year built, coordinates...
๐Ÿ  Trulia Scraper
Scrape Trulia.com property listings by location, filters, or URL. Extract prices, beds...
๐Ÿ“‡ BizBuySell Scraper
From $1.5/1K. Extract business-for-sale and franchise listings from bizbuysell.com with...
๐Ÿ“‡ BizQuest Scraper
Extract business-for-sale, franchise, and asset listings from BizQuest. Search by...
๐Ÿจ LoopNet Scraper
Scrape LoopNet commercial listings across the US, including office, retail, industrial...
๐Ÿ  Realtor.ca
From $1/1K. Scrape Realtor.ca property listings with prices, descriptions, images, agent...

๐Ÿ‘‰ Browse all abotapi scrapers

๐Ÿ’ฌ Support

  • ๐Ÿž Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
  • ๐Ÿ› ๏ธ Need another site, extra fields or a private build? Email contact@abotapi.com.
  • โญ Enjoying it? A quick review on the actor page helps other users find it.