# Crexi Commercial Real Estate Scraper & Investment Analysis (`zapticx/crexi-investment-analyzer`) Actor

Scrape Crexi commercial real-estate listings and underwrite them: price/SF, price/unit, DSCR, debt yield, cash-on-cash and a transparent 0-100 Deal Score.

- **URL**: https://apify.com/zapticx/crexi-investment-analyzer.md
- **Developed by:** [Zapticx](https://apify.com/zapticx) (community)
- **Categories:** Real estate, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Crexi Commercial Real Estate Scraper & Investment Analysis

Extract commercial real-estate listings from Crexi **and underwrite them** — price per square foot, price per unit, DSCR, debt yield, cash-on-cash return, break-even occupancy and a transparent 0–100 Deal Score.

Most Crexi scrapers hand you rows. This one hands you a screened, ranked deal list with the arithmetic already done — and tells you, listing by listing, exactly which numbers came from Crexi and which were calculated.

***

### What it does

- **Searches Crexi** by city, state, ZIP, property type, asking price and cap rate. Crexi's city matching also covers neighborhoods and nearby suburbs, so pair a city with its state for precise geographic targeting.
- **Extracts deep listing data** — location and coordinates, building size, lot size, units, year built, zoning, APN, occupancy, cap rate, NOI, auction terms, marketing copy, investment highlights, images and publicly listed broker details.
- **Calculates investment metrics** from the listing's financials plus *your* financing assumptions.
- **Scores and ranks deals** on a documented, deterministic 0–100 scale.
- **Breaks past Crexi's result ceiling.** Crexi caps any single query at 1,499 results. This Actor automatically splits a large search into smaller non-overlapping queries and merges them, de-duplicated by listing ID.

### Who it's for

| Use case | How this helps |
|---|---|
| **Deal sourcing** | Screen thousands of listings down to the handful that clear your DSCR and cap-rate thresholds |
| **Investment screening** | Rank by Deal Score instead of reading listings one at a time |
| **Underwriting prep** | Arrive at the model with price/unit, price/SF, NOI and debt service already computed |
| **Market research** | Pull an entire metro or asset class and analyse pricing distribution |
| **Broker lead generation** | Build a brokerage contact list from publicly listed broker profiles |
| **Property comparisons** | Normalised, typed fields make listings directly comparable in Excel |
| **AI & automation pipelines** | Predictable JSON for agents, n8n, Make and Zapier |

***

### Three modes

#### 1. Property Search

Find listings by location and filters.

```json
{
  "mode": "search",
  "cities": ["Dallas"],
  "states": ["TX"],
  "propertyTypes": ["Multifamily"],
  "maxPrice": 5000000,
  "maxResults": 100
}
```

#### 2. Analyze Listing URLs

Deep-extract specific listings you already have.

```json
{
  "mode": "urls",
  "listingUrls": [
    "https://www.crexi.com/properties/2522223/georgia-glenrose-apartments"
  ],
  "downPaymentPercent": 30,
  "interestRatePercent": 6.75,
  "amortizationYears": 30
}
```

#### 3. Deal Finder

Screen and rank by investment quality. Requires financing assumptions.

```json
{
  "mode": "dealFinder",
  "states": ["TX"],
  "propertyTypes": ["Multifamily"],
  "maxPrice": 4000000,
  "minCapRate": 6,
  "minDscr": 1.25,
  "downPaymentPercent": 25,
  "interestRatePercent": 6.5,
  "amortizationYears": 30,
  "closingCostsPercent": 2,
  "maxResults": 100
}
```

***

### Output structure

Every record separates what Crexi published from what this Actor computed. **A calculated value is never written into `source`.**

```jsonc
{
  "rank": 1,
  "scrapedAt": "2026-08-30T18:12:04Z",

  "source": {                       // Published by Crexi
    "listingId": 2617499,
    "listingUrl": "https://www.crexi.com/properties/2617499/texas-7-property-sfr-portfolio",
    "propertyName": "7-Property SFR Portfolio",
    "listingType": "Sale",
    "askingPrice": 1229000.0,
    "capRate": 8.84,                // Crexi's own figure
    "noi": 108686.0,                // Crexi's own figure
    "units": 7,
    "buildingSizeSqFt": 8449,
    "occupancyPercent": 85.7,
    "city": "Mesquite", "state": "TX", "zip": "75150",
    "latitude": 32.79, "longitude": -96.59,
    "brokers": [ { "name": "...", "brokerageName": "...", "profileUrl": "..." } ],
    "images": ["https://..."],
    "daysOnMarket": 53
  },

  "analysis": {                     // Calculated by this Actor
    "pricePerSqFt": 145.46,
    "pricePerUnit": 175571.43,
    "noiPerUnit": 15526.57,
    "loanAmount": 921750.0,
    "monthlyMortgagePayment": 6514.74,
    "annualDebtService": 78176.88,
    "dscr": 1.3903,
    "debtYield": 11.7913,
    "cashFlowBeforeTax": 30509.12,
    "cashOnCashReturn": 9.1942,
    "breakEvenOccupancyPercent": 61.64,
    "dealScore": 75.33,
    "dealScoreBand": "Strong",
    "dealScoreConfidence": 1.0,
    "dealScoreBreakdown": [ /* every component, weight, value and sub-score */ ]
  },

  "dataQuality": {                  // What could not be calculated, and why
    "unavailableMetrics": {},
    "derivedFromOtherFields": [],
    "hasCapRateFromSource": true,
    "hasNoiFromSource": true,
    "financingAssumptionsApplied": true
  }
}
```

Three dataset views are provided in the Output tab: **Listings overview**, **Investment analysis** and **Broker leads**.

***

### Investment analysis

Metrics are computed from the listing's own financials plus the financing assumptions you supply.

| Metric | Formula |
|---|---|
| Price per SF / per unit | Asking price ÷ building SF or unit count |
| NOI | Crexi's published NOI, or derived as price × cap rate (flagged as derived) |
| Loan amount | Asking price − down payment |
| Monthly payment | Standard amortising payment on the loan |
| Annual debt service | Monthly payment × 12 |
| **DSCR** | NOI ÷ annual debt service |
| **Debt yield** | NOI ÷ loan amount |
| Cash flow before tax | NOI − annual debt service |
| **Cash-on-cash** | Cash flow ÷ (down payment + closing costs) |
| Break-even occupancy | Debt service ÷ NOI grossed up to 100% occupancy |

#### The rules this Actor follows

1. **Nothing is invented.** If a metric needs data that isn't there, it returns `null` plus a reason in `dataQuality.unavailableMetrics` — for example `"dscr": "noi_and_cap_rate_unavailable"`.
2. **No silent assumptions.** Financing terms come only from your input. Omit them and every leverage metric returns `null` with `"financing_assumptions_not_provided"` — the Actor will not quietly pick a rate for you.
3. **Derived values are labelled.** If NOI was reconstructed from price × cap rate rather than published by Crexi, it appears in `dataQuality.derivedFromOtherFields`.
4. **Placeholders are treated as missing.** Some auction listings carry a $1 placeholder price; these never become ratios.

***

### Deal Score

A deterministic 0–100 score. The same listing with the same assumptions always scores the same — it never depends on the other listings in your run.

| Component | Weight | 0 points at | 100 points at |
|---|---|---|---|
| Cap rate | 30% | 4.0% | 9.0% |
| DSCR | 25% | 1.00 | 2.00 |
| Debt yield | 20% | 7.0% | 15.0% |
| Cash-on-cash return | 15% | 0% | 15% |
| Occupancy | 10% | 70% | 97% |

Each component is scored on a linear ramp between those anchors, clamped at both ends, then weighted.

**Why the cap-rate ramp stops at 9%.** In US commercial real estate, yield materially above ~9% usually signals higher risk — a weaker submarket, weaker tenant credit, or deferred condition — rather than a better asset. Letting it keep earning points would rank the riskiest listings highest. DSCR and cash-on-cash carry no such ambiguity, so their ramps extend further.

**Only available components are scored,** and the total is renormalised over them, so a listing isn't punished for non-disclosure. `dealScoreConfidence` reports how much of the total weight had real inputs (1.0 = everything).

**If components covering less than 50% of the weight are available, the score is `null`** with a reason — never a fabricated number. In practice a Deal Score requires (a) Crexi publishing a cap rate or NOI, and (b) you supplying financing assumptions.

`dealScoreBreakdown` returns every component, its weight, the input value and its sub-score, so any ranking can be audited. Bands: **Excellent** ≥80, **Strong** ≥65, **Moderate** ≥50, **Weak** ≥35, **Poor** <35.

***

### Important: how often financial data actually exists

Crexi listings vary enormously in disclosure. Measured across a random sample of 150 listings:

| Field | Available |
|---|---|
| Asking price, property type | ~100% |
| Lot size | ~90% |
| Year built | ~64% |
| Building size | ~62% |
| Units | ~33% |
| Occupancy | ~7% |
| **Cap rate** | **~6%** |
| **NOI** | **~5%** |

Gross income and operating expenses are not published by Crexi at all.

**This is why the `minCapRate` filter matters.** Setting it restricts results to listings that *do* disclose a cap rate — in testing, 100% of results returned under a cap-rate filter carried one. If you want investment analysis on most of your results, set `minCapRate`. Without it, expect many listings to return `null` metrics with a stated reason.

***

### Performance

- Runs on Crexi's JSON API over plain HTTP. No browser, so no browser overhead.
- Bulk fetching: up to 1,499 listings per request, so 1,000 search results arrive in a single search request. Listing details are then fetched per listing.
- A 20-listing analysed run completes in roughly **5 seconds**.
- Bounded retries with exponential backoff; repeated failures are reported, never hidden.
- Concurrency is configurable (default 8) and results are de-duplicated by listing ID.

***

### Integrations

Export as **JSON, CSV, Excel, XML or HTML**, or pull from the Apify API. Works with **Make**, **Zapier**, **n8n**, Google Sheets, and any AI agent that can read JSON. Predictable types mean no string-parsing downstream: `2450000.0`, not `"$2,450,000"`.

***

### Limitations

Stated plainly, because underwriting decisions depend on them:

- **Sale and auction listings only.** Crexi's lease inventory is served by a separate system this Actor does not cover. Lease listings also carry no cap rate or NOI, so investment analysis would not apply to them.
- **Broker phone numbers and email addresses are not included.** Crexi does not publish them anonymously, and this Actor does not attempt to access anything behind authentication.
- **Cap rate and NOI are sparse** unless you filter for them — see the table above.
- **Cap rates are as advertised by the listing broker.** They are marketing figures, not audited financials, and occasionally contain obvious errors. Treat the Deal Score as a screening aid, not investment advice.
- **Crexi caps any single query at 1,499 results.** The Actor partitions automatically, but a search too broad to subdivide will log a warning and return what it could reach — narrow by state, city or price range to reach the rest.
- **Offering memoranda and document downloads are gated** behind Crexi accounts and confidentiality agreements, and are not retrieved.
- Undocumented upstream API: field availability can change if Crexi changes its platform.

### Responsible use & data source

Data comes from Crexi's public listing endpoints — the same information any visitor sees on a listing page. The Actor does not log in, does not use credentials, does not bypass access controls, and does not retrieve anything gated behind authentication or a confidentiality agreement. Broker information is limited to what brokers publish on their own public profiles.

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Crexi. Output is for research and screening. It is not investment advice — verify every figure against the offering memorandum and your own due diligence before transacting.

***

### FAQ

**Why is `dealScore` null on some listings?**
Crexi didn't publish a cap rate or NOI for it, or you didn't supply financing assumptions. The exact reason is in `analysis.dealScoreUnavailableReason`. Set `minCapRate` to restrict results to listings that disclose one.

**Why are DSCR and cash-on-cash null?**
They need financing terms. Supply `downPaymentPercent`, `interestRatePercent` and `amortizationYears`.

**Can I get more than 1,499 results?**
Yes. Set `maxResults` higher and the Actor splits the search automatically. Very broad searches may still be limited — add a state, city or price range.

**Why does a listing show a cap rate in `source` but a different one in `analysis`?**
It shouldn't — `analysis.capRate` mirrors the source value when Crexi publishes one. It only differs when Crexi published NOI but no cap rate, in which case it's derived and flagged in `derivedFromOtherFields`.

**Does it get lease listings?**
No — see Limitations.

**Why does a city search return nearby or unexpected places?**
Crexi matches city names against neighborhoods and nearby suburbs as well as the city itself — searching "Austin" can return listings in Chicago's Austin neighborhood, and "Dallas" can return neighbouring Farmers Branch. Always pair a city with its state for precise targeting; add ZIP codes to narrow further.

**Do I need a proxy?**
Usually not. If you see access-restricted errors, enable Apify Proxy with residential groups in the Actor's proxy settings.

**Are cap rates reliable?**
They're broker-advertised marketing figures. The Deal Score deliberately stops rewarding cap rates above 9% so that implausible or high-risk yields don't top your ranking.

# Actor input Schema

## `mode` (type: `string`):

Property Search finds listings by location and filters. Analyze Listing URLs extracts deep data for specific Crexi listings you already have. Deal Finder screens and ranks listings by investment quality (requires financing assumptions).

## `cities` (type: `array`):

City names, e.g. Dallas. Crexi matches city names against neighborhoods and nearby suburbs as well as the city itself, and the same name recurs across states (Dallas exists in TX, GA and NC). Always pair a city with its state below for precise geographic targeting.

## `states` (type: `array`):

Two-letter codes (TX) or full names (Texas).

## `zipCodes` (type: `array`):

Optional. Narrows the search to specific ZIP codes.

## `propertyTypes` (type: `array`):

Leave empty to include every asset class.

## `listingType` (type: `string`):

All listings, or only those already under contract / contract pending.

## `minPrice` (type: `integer`):

Leave empty for no lower bound.

## `maxPrice` (type: `integer`):

Leave empty for no upper bound.

## `minCapRate` (type: `string`):

In percent, e.g. 6.5. Crexi publishes a cap rate on only a minority of listings - setting this filter restricts results to listings that DO disclose one, which is also what enables the investment analysis.

## `maxCapRate` (type: `string`):

In percent, e.g. 12. Useful for excluding implausible outliers.

## `includeUnpriced` (type: `boolean`):

Many Crexi listings are marketed without an asking price. These cannot be financially analysed.

## `opportunityZoneOnly` (type: `boolean`):

Restricts results to listings Crexi flags as being inside a designated Qualified Opportunity Zone.

## `listingUrls` (type: `array`):

Used only in 'Analyze Listing URLs' mode. Paste full listing URLs, e.g. https://www.crexi.com/properties/2522223/georgia-glenrose-apartments

## `maxResults` (type: `integer`):

Number of listings to return. Crexi limits any single query to 1,499 results; beyond that this Actor automatically splits the search into smaller queries and merges them.

## `sortBy` (type: `string`):

Sorting is applied by this Actor after collection, because Crexi's API does not provide reliable server-side sorting.

## `includeInvestmentAnalysis` (type: `boolean`):

Adds calculated metrics (price/SF, price/unit, DSCR, debt yield, cash-on-cash, Deal Score) in a separate 'analysis' object. Calculated values are never mixed into the scraped 'source' data.

## `includeBrokerDetails` (type: `boolean`):

Adds publicly listed broker name, brokerage, licence numbers and profile URL. Crexi does not publish broker phone numbers or emails, so those are not included.

## `includeImages` (type: `boolean`):

Adds full-resolution image URLs.

## `maxConcurrency` (type: `integer`):

Parallel requests to Crexi. Lower this if you see rate-limit warnings.

## `downPaymentPercent` (type: `string`):

Required for DSCR, debt yield and cash-on-cash. Without financing inputs these metrics are returned as null with a stated reason - never guessed. Decimals are supported.

## `interestRatePercent` (type: `string`):

Annual nominal rate, e.g. 6.75. Decimals are supported.

## `amortizationYears` (type: `integer`):

Used to compute the monthly payment.

## `loanTermYears` (type: `integer`):

Optional. Falls back to the amortization period when omitted.

## `closingCostsPercent` (type: `string`):

Included in total cash required and therefore in cash-on-cash return. Decimals are supported.

## `additionalAnnualExpenses` (type: `integer`):

Optional. Subtracted from NOI before debt service, e.g. capital reserves.

## `minDscr` (type: `string`):

Deal Finder screening. Drops listings whose DSCR falls below this, e.g. 1.25. Listings without enough data to compute DSCR are also dropped.

## `minDealScore` (type: `integer`):

Deal Finder screening. Drops listings scoring below this threshold.

## `proxyConfiguration` (type: `object`):

Optional. Enable Apify Proxy if Crexi rate-limits or blocks requests from your run.

## Actor input object example

```json
{
  "mode": "search",
  "cities": [
    "Dallas"
  ],
  "states": [
    "TX"
  ],
  "propertyTypes": [
    "Multifamily"
  ],
  "listingType": "all",
  "maxPrice": 5000000,
  "includeUnpriced": false,
  "opportunityZoneOnly": false,
  "maxResults": 20,
  "sortBy": "newest",
  "includeInvestmentAnalysis": true,
  "includeBrokerDetails": true,
  "includeImages": true,
  "maxConcurrency": 8,
  "downPaymentPercent": "25",
  "interestRatePercent": "7.0",
  "amortizationYears": 25,
  "closingCostsPercent": "2",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `listings` (type: `string`):

One record per commercial property listing: identity, location, coordinates, physical characteristics, Crexi-published financials, broker details and images, plus calculated underwriting metrics (price/SF, price/unit, DSCR, debt yield, cash-on-cash, break-even occupancy) and a transparent 0-100 Deal Score. Switch between the Listings overview, Investment analysis and Broker leads views in the Output tab.

## `runSummary` (type: `string`):

Counts, timings, how many search partitions were needed, how many listings carried a Crexi cap rate, and the exact Deal Score weights and thresholds used.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "search",
    "cities": [
        "Dallas"
    ],
    "states": [
        "TX"
    ],
    "propertyTypes": [
        "Multifamily"
    ],
    "maxPrice": 5000000,
    "maxResults": 20,
    "sortBy": "newest",
    "includeInvestmentAnalysis": true,
    "includeBrokerDetails": true,
    "includeImages": true,
    "downPaymentPercent": "25",
    "interestRatePercent": "7.0",
    "amortizationYears": 25,
    "closingCostsPercent": "2"
};

// Run the Actor and wait for it to finish
const run = await client.actor("zapticx/crexi-investment-analyzer").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "search",
    "cities": ["Dallas"],
    "states": ["TX"],
    "propertyTypes": ["Multifamily"],
    "maxPrice": 5000000,
    "maxResults": 20,
    "sortBy": "newest",
    "includeInvestmentAnalysis": True,
    "includeBrokerDetails": True,
    "includeImages": True,
    "downPaymentPercent": "25",
    "interestRatePercent": "7.0",
    "amortizationYears": 25,
    "closingCostsPercent": "2",
}

# Run the Actor and wait for it to finish
run = client.actor("zapticx/crexi-investment-analyzer").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "search",
  "cities": [
    "Dallas"
  ],
  "states": [
    "TX"
  ],
  "propertyTypes": [
    "Multifamily"
  ],
  "maxPrice": 5000000,
  "maxResults": 20,
  "sortBy": "newest",
  "includeInvestmentAnalysis": true,
  "includeBrokerDetails": true,
  "includeImages": true,
  "downPaymentPercent": "25",
  "interestRatePercent": "7.0",
  "amortizationYears": 25,
  "closingCostsPercent": "2"
}' |
apify call zapticx/crexi-investment-analyzer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zapticx/crexi-investment-analyzer"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/VScW6QrFjwd9Pt8Mo/builds/ISQLM5ERtxxPWwPhi/openapi.json
