Crexi Scraper - Commercial Property Listings for Sale
Pricing
from $1.00 / 1,000 listing results
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
Maintained by CommunityActor stats
1
Bookmarked
8
Total users
5
Monthly active users
12 days ago
Last modified
Categories
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.
| Field | Example |
|---|---|
id | 1000000 |
name | "Maple Court Apartments" |
property_type | "Multifamily" |
status | "Active" |
askingPrice | 4250000 |
capRate | 6.25 |
squareFootage | 48000 |
specifications.units | 64 |
yearBuilt | 1998 |
stories | 3 |
lotSize | 2.4 |
netOperatingIncome | 265000 |
apn | "000-000-000" |
zoning | "R-4" |
fullAddress | "100 Main St, Austin, Travis County, TX 78701" |
city / state | "Austin" / "TX" |
latitude / longitude | 30.2672 / -97.7431 |
url | Crexi link to the listing |
images | full 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
- Pick a mode:
search(build from your locations and filters) orurl(paste crexi.com listing URLs). - For search mode, set Locations or keywords and any of property types, price band or sort order.
- Turn on Fetch full details for square footage, cap rate, gallery and agent contact, and set Max items to control run size and cost.
- 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):
| Shape | Example | Result |
|---|---|---|
| Detail page, with slug | https://www.crexi.com/properties/2597217/new-york-48-w-48th-street | that listing |
| Detail page, id only | https://www.crexi.com/properties/2597217 | that listing |
| Listing data link | https://api.crexi.com/assets/2597217 | that listing |
| Search page with type filter | https://www.crexi.com/properties?types%5B%5D=Office | search for those property types |
| State / city page | https://www.crexi.com/properties/TX/Austin | search for Austin, TX |
| Lease listing pages (index, state, city) | https://www.crexi.com/lease/properties/TX/Austin | not 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 ApifyClientclient = 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 asNEW. Later runs returnNEWandUPDATEDlistings;UNCHANGEDlistings are suppressed and not billed unlessemitUnchangedis on. Each returned record also getschangeType,changedFields,firstSeenAtandlastSeenAt. Turning onemitExpiredalso returns, and bills, listings no longer found, markedEXPIRED, but only after a run that fully scanned the tracked search; a run capped by Max items, or combined with Resume, never producesEXPIREDrows.REAPPEAREDis only produced for a listing that was previously markedEXPIREDthis way and is then found again on a later run; a search that has never usedemitExpiredwill never see aREAPPEAREDrow, since nothing was ever marked gone in the first place.stateKeynames 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) decidesUPDATED, andchangedFieldslists 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 innumberOfImagesstill counts), the listing's rank in the result page (the top-levelpositionandnumberonly; anumberinside 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 withignoreFieldsForChanges. 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.
- Authorize a connector once under Apify, Settings, API & Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. If the picker is empty, you have not authorized a connector yet.
- For Notion, also set
notionParentPageUrlto 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
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search builds searches from your locations and filters, url loads the listing URLs you paste. |
locations | array | [] (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. |
propertyTypes | array | none | Property types to narrow the search, for example Multifamily, Office, Retail, Industrial, Land. Search mode only. |
states | array | none | Two-letter US state codes (for example TX). Only listings located in those states. Search mode only. |
cities | array | none | City names (for example Austin). Combine with states to disambiguate. Search mode only. |
moreResults | boolean | false | Crexi 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. |
priceMin | integer | none | Only return listings with an asking price at or above this amount. |
priceMax | integer | none | Only return listings with an asking price at or below this amount. |
includeUnpriced | boolean | true | Include listings without a published asking price (auctions, call-for-offers). |
sortBy | string | rank | rank (best match), price, activatedOn (newest), or capRate. |
sortDirection | string | Descending | Descending or Ascending. |
urls | array | one 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). |
resumeFromRunId | string | none | Paste 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. |
incrementalMode | boolean | false | Turn 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. |
stateKey | string | none | Name 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. |
emitUnchanged | boolean | false | Also return, and bill, listings unchanged since the last run, marked UNCHANGED. Incremental mode only. |
emitExpired | boolean | false | Also 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. |
ignoreFieldsForChanges | string list | none | Extra 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. |
fetchDetails | boolean | true | Load 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. |
maxItems | integer | 20 | The single cap on the run: stop after this many listings, across all searches and URLs. 0 = unlimited. |
maxPages | integer | none (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. |
proxy | object | {"useApifyProxy": true} | Connection settings. The default tier is the lowest-cost option and works on every plan. |
maxResidentialMB | integer | 0 | When using a Residential connection group, cap residential traffic at this many MB, then automatically switch to the lower-cost tier. 0 = unlimited. |
mcpConnectors | array | none | Optional MCP connectors to pipe results into (Notion, Linear, Airtable, Apify). Never changes the dataset. |
notionParentPageUrl | string | none | Notion page URL or id under which listing pages are created. Notion connector only. |
maxNotifyListings | integer | 50 | Cap 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.
Is it legal to scrape Crexi?
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.