Crexi Scraper at a glance
Crexi Scraper is a ready-to-run Apify actor from abotapi for collecting real estate data. Its documented output includes id, name, change type (incremental mode), type and asking price, which you can export as JSON, CSV or Excel or read through the Apify API. It costs $1.80 per 1,000 results on the Apify Free plan and from $1.00 on paid plans, with smaller per-run or optional feature charges listed on Apify.
- Runs on
- Apify cloud: console, API, schedules and integrations
- Data type
- Real Estate, Lead Generation, Automation
- Pricing
- $1.80 per 1,000 results (Free plan), from $1.00 on paid plans
- Output
- Documented below · JSON, CSV, Excel
- Inputs
- 24 options
- Run success
- 100.0% of completed public runs in the last 30 days
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.
What Crexi Scraper can do
- 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.
Choose what to collect
These inputs let you configure Crexi Scraper - Commercial Property Listings for Sale. Open the actor on Apify to enter your values and review the current options.
| Input | What it controls |
|---|---|
modestring · required | How to start the scrape. 'search' builds crexi.com searches from your locations, property types and filters. 'url' loads the crexi.com listing URLs you paste (filter fields below are ignored in URL mode). |
locationsarray | One or more locations or keywords to search (e.g. 'Austin, TX', '90001', 'warehouse Dallas'). Each entry is scraped as its own search. Leave empty to search all listings nationwide. |
propertyTypesarray | Optional property types to narrow the search (e.g. 'Multifamily', 'Office', 'Retail', 'Industrial', 'Land'). Applied across all locations. |
statesarray | Only return listings located in these US states (e.g. TX, CA). Applied across all locations. Verified against the live search: TX narrows 256,631 listings to about 34,000. |
citiesarray | Only return listings in these cities (e.g. Austin). Combine with States to disambiguate a city name. |
priceMininteger | Only return listings with an asking price at or above this amount. |
priceMaxinteger | Only return listings with an asking price at or below this amount. |
Show 17 more inputs
| Input | What it controls |
|---|---|
includeUnpricedboolean | Include listings that do not publish an asking price (auctions, call-for-offers). Turn off to return only priced listings. |
moreResultsboolean | Crexi refuses to page past about 1,500 results for a single search. Turn this 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. Search mode only. |
sortBystring | Order crexi.com returns results in. |
sortDirectionstring | Ascending (low to high / oldest first) or descending (high to low / newest first). |
urlsarray | Paste crexi.com links. Supported: listing detail pages (with or without the slug), api.crexi.com/assets/, search pages with a property-type filter (?types[]=Office) and state/city pages (/properties/TX/Austin). Lease listing pages (index, state and city) are not supported and are skipped with a warning. Leave the default to scrape one example of each distinct shape (a listing detail page, a property-type search and a state/city page). |
fetchDetailsboolean | Load each listing page to add square footage, units, year built, stories, lot size, cap rate, APN, marketing description, the full image gallery and the agent and brokerage contact details. Slower and adds a small per-item charge; leave off for fast basic listings. |
maxItemsinteger | The single cap on this run: stop after collecting this many listings (across all searches and URLs). Set 0 for unlimited. |
maxPagesinteger | Optional safety bound on how many result pages to include per search. Defaults to unlimited (leave empty or set 0) and does NOT cap the run; it defers to Max items, which is the single cap. The run stops at Max items, not here. |
resumeFromRunIdstring | Paste a previous run ID or dataset ID to continue a large crawl of listings without returning or charging for listings already collected there. Use this after an interrupted run, or when continuing a listing pull in another run. For recurring daily monitoring of the same search, use Incremental mode below instead. |
incrementalModeboolean | Turn this on for daily or recurring monitoring. The first run returns all matching listings as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED listings. Turn on "Emit unchanged" or "Emit expired" only when you also want those listings returned (and billed). State is kept separately for each search/URL and filter setup; use State key when you want to name or deliberately share a monitoring campaign. What is compared: every output field of the listing (price, status, type, size, cap rate, address, description, agent and brokerage details and so on) decides UPDATED, and changedFields names the ones that moved. What is excluded: scrapedAt, the result-count metadata, image, logo and photo URLs and the photo gallery list, the listing's rank in the result page, session-specific flags (saved to board, owner, download rights), and the order of lists. Use "Ignore fields when detecting changes" to exclude more. To continue one specific interrupted run instead, use Resume from a previous run above. |
stateKeystring | Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the location/property-type/price/sort/URL and detail settings, different searches then never mix state with each other. |
emitUnchangedboolean | Off by default. Turn on to also return listings that have not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have, so leave it off unless you specifically want the full snapshot every run. |
emitExpiredboolean | Off by default. Turn on to also return listings that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search, not when Max items capped it or when Resume was used. This returns, and bills, extra synthetic rows, so leave it off unless you need expiry tracking. |
ignoreFieldsForChangesarray | Optional. Field names to also exclude from UPDATED detection, on top of the built-in exclusions. A plain name (for example "description") is ignored wherever it appears, including inside nested lists; a dotted path (for example "details.zoning") ignores just that path. Excluded fields stay in the output; they only stop triggering UPDATED and stop being listed in changedFields. |
mcpConnectorsarray | Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a rich page-per-item export; other connectors get a best-effort write/digest. Leave empty to skip; never changes the dataset output. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com). |
notionParentPageUrlstring | URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors. |
maxNotifyListingsinteger | Cap on items written to each connector per run. Does not affect the dataset. |
Fields in the output
The documented dataset includes the fields below. Availability can depend on the source page and selected mode.
idID (number)nameName (text)changeTypeChange type (incremental mode) (text)property_typeType (text)askingPriceAsking price (number)capRateCap rate (number)squareFootageSq ft (number)yearBuiltYear built (number)statusStatus (text)cityCity (text)stateState (text)zipZIP (text)Show 20 more fields
latitudeLat (number)longitudeLng (number)urlURL (link)imageUrlImage (image)brokerageNameBrokerage (text)agent_infoAgent (object)changedFieldsChanged fields (incremental mode) (array)firstSeenAtFirst seen (incremental mode) (date)lastSeenAtLast seen (incremental mode) (date)firstNameFirst name (text)lastNameLast name (text)phonePhone (text)publicProfileIdProfile (text)licenseStateCodeLicense state (text)licenseNumbersLicenses (array)numberOfAssetsListings count (number)agentSlugProfile slug (text)profile_urlProfile URL (link)licenseNumberLicense number (text)licenseStateLicense state (code) (text)Example output
An example from this actor’s documentation. Values are illustrative; this is not a fresh live result.
{
"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"
}Ways to use this data
- 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.
Before you run
Start with a small input and check the resulting records against your expected fields. Source content and actor options can change; the current Apify listing is the reference for availability and pricing.
Questions about this actor
What data does Crexi Scraper return?
The documented output for Crexi Scraper includes id, name, change type (incremental mode), type, asking price, cap rate, sq ft and year built. Availability of each field depends on the source page and the selected mode.
How much does Crexi Scraper cost?
It costs $1.80 per 1,000 results on the Apify Free plan and from $1.00 on paid plans, with smaller per-run or optional feature charges listed on Apify. The Apify listing shows the current rate for every plan.
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.
