# LoopNet + Crexi Scraper · CRE Listings · CoStar Alternative (`kazkn/commercial-real-estate-brokerage-intel`) Actor

LoopNet + Crexi commercial real estate scraper. Aggregates listings, normalizes cap rates (NOI/price + asset-class median fallback), tracks days-on-market, dedups cross-platform, exposes broker contacts. The CoStar alternative for SMB CRE brokers — pay-per-result, $5/1K listings.

- **URL**: https://apify.com/kazkn/commercial-real-estate-brokerage-intel.md
- **Developed by:** [KazKN](https://apify.com/kazkn) (community)
- **Categories:** Lead generation, Real estate, Automation
- **Stats:** 3 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 results

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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

## LoopNet + Crexi Scraper · Commercial Real Estate Listings · CoStar Alternative

> 🏢 The autonomous **commercial real estate API** that aggregates LoopNet and Crexi listings, normalizes cap rates, and deduplicates cross-platform. **$5 per 1,000 results** — 1/40th of CoStar's $40k/yr median.

---

### 💰 Why CRE brokers switch to this

CoStar gates the U.S. commercial real estate listings market behind **$14k–$40k/year per seat**. Every existing scraper on Apify Store covers a single source (LoopNet OR Crexi). **None** normalize cap rates. **None** track days-on-market. **None** deduplicate cross-platform.

This actor does. And it runs **fully autonomously, 24/7**.

| 💵 Annual cost | 🏢 Coverage | 📊 Intelligence layer |
|---|---|---|
| **This actor** ~$1,500/yr (typical Pro broker use) | LoopNet + Crexi | ✅ Cap rate, DOM, dedup |
| CoStar Suite entry | $14,000–$20,000/yr | ⚠️ Web UI only, no API |
| CoStar Suite median | $40,000/yr | ⚠️ Multi-seat |
| Reonomy / Compstak | $5,000–$18,000/yr | Off-market focus |

---

### ⚡ Quick output preview

```json
{
  "source": "loopnet",
  "source_listing_id": "29769721",
  "address": {
    "street": "2921 E 17th St",
    "city": "Austin",
    "state": "TX",
    "zip": "78702",
    "lat": 30.278541,
    "lng": -97.709889
  },
  "asset_class": "office",
  "sub_type": "Loft/Creative Space",
  "sqft": 7500,
  "asking_price_usd": 2500000,
  "cap_rate_listed": null,
  "cap_rate_normalized": 7.5,
  "cap_rate_estimated": true,
  "price_per_sqft": 333,
  "days_on_market": 32,
  "status": "active",
  "broker": {
    "name": "Isaac Gutierrez",
    "company": "ECR",
    "phone": null
  },
  "also_listed_on": [],
  "photo_urls": ["https://images1.loopnet.com/i2/.../900x675/image.jpg"],
  "description": "Creative office building in the East Austin submarket available for sale."
}
````

***

### 🎯 Quick start

#### 🏙️ Search by city + state (most common)

```json
{
  "city": "Austin",
  "state": "TX",
  "sourcesEnabled": ["loopnet", "crexi"],
  "assetClasses": ["office", "retail"],
  "priceMin": 500000,
  "priceMax": 5000000,
  "maxResults": 200
}
```

#### 🔄 Daily monitoring — only NEW listings

```json
{
  "city": "Dallas",
  "state": "TX",
  "monitoringMode": true,
  "maxResults": 500
}
```

Run on a daily schedule. The actor maintains an internal snapshot per query. **Only listings not seen in previous runs are emitted.**

#### 📊 Track CLOSED deals

```json
{
  "city": "Phoenix",
  "state": "AZ",
  "transactionTrackingMode": true
}
```

Emits ONLY listings that disappeared since the last run (likely sold/leased) or whose status flipped to `sold` / `under_contract`. Each item carries `transaction_tracking.close_status`, `close_detected_at`, `previous_price`.

#### 🔗 Paste specific listing URLs

```json
{
  "startUrls": [
    {"url": "https://www.loopnet.com/Listing/12345678/example-property/"},
    {"url": "https://www.crexi.com/properties/87654321/example-listing"}
  ]
}
```

***

### 🏢 Who uses this

| Persona | Use case |
|---|---|
| 💼 **CRE investment broker** | Daily morning brief: new sub-$5M listings in target market |
| 🏛️ **REIT acquisition team** | Track multifamily + retail across 5 metros, normalize cap rates |
| 🔍 **ETA / search fund** | Identify owner-operated deals, dedupe LoopNet+Crexi to skip duplicate underwriting |
| 📈 **CRE consulting firm** | Build normalized comp datasets for client reports |
| 🏰 **Family office** | Filter for opportunity-zone office, weekly digest |
| 🏦 **Lender / appraiser** | Days-on-market by asset class for valuation models |

***

### 📊 The intelligence layer (the actual moat)

#### Cap-rate normalization

Listing data is **inconsistent across platforms**. Some declare cap rate, others NOI only, auctions and unpriced listings have neither. The actor handles all 4 cases:

| Source data | Action | Output `cap_rate_estimated` |
|---|---|---|
| NOI + price both declared | **Recompute** cap = NOI/price × 100 | `false` ✅ |
| Only cap\_rate declared | Pass-through, derive implied NOI | `false` ✅ |
| Neither declared, price known | **Estimate** with asset-class median (US Q1 2024-25) | `true` ⚠️ |
| No price + no cap\_rate | — | `null` |

**Asset-class medians used:**

| Class | Median | Class | Median |
|---|---|---|---|
| 🏠 Multifamily | 5.4% | 🛍️ Retail | 6.8% |
| 🏭 Industrial | 5.9% | 🏢 Office | 7.5% |
| 🏗️ Mixed-use | 6.5% | 🎯 Specialty | 7.0% |
| 🏨 Hotel | 8.5% | | |

#### 🔄 Cross-platform deduplication

Property listed on both LoopNet AND Crexi appears in BOTH datasets. The actor:

1. Computes a **stable `dedup_key`** = hash(normalized\_street + city + state + sqft\_bucket + asset\_class)
2. Groups listings by key
3. Picks the most complete record as primary (highest non-null field count)
4. Marks `also_listed_on: ["crexi"]` on the LoopNet primary (or vice versa)
5. Outputs **one** deduplicated record per property

📌 No other Apify actor does this. It's the difference between raw scrape and intelligence.

***

### 💰 Pricing — fully transparent

Pay-per-event on Apify Store. **New Apify users get $5 platform credit on signup** (~1,000 listings test on this actor included).

| Event | Price | When |
|---|---|---|
| 🚀 **Actor Start** | $0.05 / run | Once per scrape job |
| 📍 **Result** ⭐ | $0.005 / listing | Each unique listing returned (primary cost) |
| 📋 **Listing detail enrichment** | $0.003 / listing | Only when `includeListingDetails: true` (NOI, photos, year built) |

#### 💵 Realistic cost estimates

| Volume | Without details | With details |
|---|---|---|
| 100 listings (test) | **$0.55** | $0.85 |
| 1,000 listings (city scan) | **$5.05** | $8.05 |
| 10,000 listings (multi-city) | **$50.05** | $80.05 |
| 25,000 listings/mo (active broker) | **~$125/mo** | ~$200/mo |

📈 At 25K listings/mo the actor costs **~$1,500/yr** vs CoStar's **$40,000/yr median**. ROI = **27×**.

***

### ✅ Key features

- 🔄 **Cross-platform aggregation** — LoopNet + Crexi
- 📊 **Cap-rate normalization** with NOI fallback + asset-class medians
- 🔁 **Cross-platform deduplication** with stable `dedup_key`
- ⏱️ **Days-on-market index** auto-computed
- 🆕 **Monitoring mode** — only new listings since last run
- 🤝 **Transaction tracking** — only closed deals (sold / leased / under contract)
- 📦 **Portfolio expansion** — multi-property listings split into individual items
- 🎛️ **20+ filters** — price, cap rate, sqft, asset class, sub-type, state, city
- 📁 **Apify dataset views** — Overview / Financial / Broker contacts pre-configured
- 🌍 **Multi-locale parsing** — handles English + French, ranges (1.5K-13K SF), K/M multipliers
- 🤖 **Fully autonomous** — runs on Apify residential proxy, no human ritual

***

### ❓ FAQ

**Q: How is this different from existing LoopNet / Crexi scrapers on Apify?**
A: Other scrapers cover a single source and return raw data. This covers BOTH, normalizes cap rates, deduplicates cross-platform, computes days-on-market. The intelligence layer is the value.

**Q: How fresh is the data?**
A: As fresh as LoopNet/Crexi public listings — typically updated every 24–48h on the source side. With monitoring mode, you're notified within 1 day of a new listing.

**Q: Can I get NOI and cap rate that aren't publicly listed?**
A: When the source platform exposes them, yes. When not (~40% of listings), the actor estimates with asset-class medians, clearly flagged with `cap_rate_estimated: true`. Medians refreshed quarterly.

**Q: Are broker contact emails / phones included?**
A: Phone and email when available on source (Crexi exposes more contact info than LoopNet). Use ethically — direct outreach is governed by your local CRE broker license rules.

**Q: Can I use this for off-market data?**
A: No, on-market only. For off-market, look at Reonomy or CompStak (different price tier).

**Q: What about Ten-X auctions?**
A: Out of scope — Ten-X auction listings are gated (bidder registration required). The actor flags listings transitioning to auction status via `status: "under_contract"` when source exposes it.

**Q: Can I run this on a schedule?**
A: Yes — Apify scheduler. Recommended: daily 6am ET in `monitoringMode: true` for new-listing alerts. Combine with Apify webhooks → Slack / Zapier / your CRM.

**Q: How does dedup handle two listings with same address but different asset class?**
A: Dedup key includes `asset_class`, so a "Mixed Use" record and a "Multifamily" record at the same address remain separate items.

**Q: What about international listings?**
A: USA + Canada (LoopNet covers both). International CRE markets not currently supported.

***

### 📋 Output schema

Each dataset row follows this schema:

```typescript
{
  source: "loopnet" | "crexi",
  source_listing_id: string,
  source_url: string,
  scraped_at: string,                 // ISO8601

  address: {
    raw: string,
    street: string | null,
    city: string | null,
    state: string | null,             // 2-letter US/CA code
    zip: string | null,
    country: "US" | "CA",
    lat: number | null,
    lng: number | null
  },

  asset_class:
    | "office" | "retail" | "industrial" | "multifamily"
    | "land" | "hotel" | "mixed-use" | "specialty" | "unknown",
  sub_type: string | null,
  sqft: number | null,
  units: number | null,
  year_built: number | null,
  lot_size_sqft: number | null,

  asking_price_usd: number | null,
  noi_usd: number | null,
  cap_rate_listed: number | null,
  cap_rate_normalized: number | null,
  cap_rate_estimated: boolean,
  price_per_sqft: number | null,

  listed_at: string | null,
  days_on_market: number | null,
  status:
    | "active" | "under_contract" | "sold" | "leased"
    | "removed" | "off_market" | "unknown",

  broker: {
    name: string | null,
    company: string | null,
    phone: string | null,
    email: string | null,
    profile_url: string | null
  },

  dedup_key: string,                  // stable hash for dedup
  also_listed_on: ("loopnet" | "crexi")[],

  photo_urls: string[],
  description: string | null,

  // Only when transactionTrackingMode=true
  transaction_tracking?: {
    close_status: "sold" | "leased" | "under_contract" | "removed" | "pending",
    close_detected_at: string,
    first_seen_at: string,
    last_seen_at: string,
    previous_price: number | null
  }
}
```

***

### 🛠️ Technical notes

- **Stack**: Apify SDK v3 + Crawlee v3.16, TypeScript ESM, Node 22
- **Source extraction**: `pds.loopnet.com` mobile API + `api.crexi.com` JSON
- **Anti-blocking**: Apify residential proxies, auto-rotated
- **Performance**: HTTP-only for normal usage, sub-10s runs for 50 listings
- **Cap-rate medians**: refreshed quarterly from Marcus & Millichap + CBRE Cap Rate Survey
- **Address normalization**: USPS-style street parsing + sqft buckets for dedup

***

### 📞 Contact

Issues, feature requests, custom feeds → open an issue on Apify Store or contact KazKN directly.

***

*🏢 Built to help SMB CRE brokers stop overpaying for data they couldn't differentiate themselves with anyway.*

# Actor input Schema

## `city` (type: `string`):

Target city. Combined with State below, used to build a search bounding box. Leave empty if you provide URLs in the field below.

## `state` (type: `string`):

US state code (CA, NY, TX, FL, NV, …). Required when City is set.

## `assetClasses` (type: `array`):

Restrict to specific commercial property types. Empty = all types.

## `sourcesEnabled` (type: `array`):

Which platforms to query. Both enabled by default (cross-platform dedup needs both).

## `transactionTypes` (type: `array`):

Whether to fetch <b>for-sale</b> properties (purchase prices, cap rates), <b>for-lease</b> spaces (rent quotes, suite breakdown), or <b>both</b>. When both are enabled, an asset listed for both sale AND lease emits 2 separate dataset rows.<br/><br/><b>⚠️ v0.2 scope:</b> Lease scraping is supported on <b>Crexi</b>. LoopNet for-lease is queued for v0.3 (pending iPhone API reverse-engineering). For lease-only runs prefer <code>sourcesEnabled: \["crexi"]</code>.

## `maxResultsPerSource` (type: `integer`):

Hard cap PER SOURCE per run. With both sources enabled and value=10, you get up to 10 LoopNet + 10 Crexi = 20 total.

## `startUrls` (type: `array`):

Optional. Paste search URLs from <a href="https://www.loopnet.com" target="_blank">LoopNet</a> or <a href="https://www.crexi.com" target="_blank">Crexi</a>. URL filters are auto-extracted when possible:<br/><br/><b>✅ Crexi URL params auto-extracted:</b> types\[] (asset class), priceMin / priceMax, capRateMin / capRateMax, squareFootageMin / squareFootageMax.<br/><b>⚠️ Crexi NOT auto-extracted:</b> placeIds (location — set City + State manually), subtypes, tenancy, occupancy, yearBuilt, lotSize, keywords, /lease/ paths.<br/><br/><b>✅ LoopNet URL parts auto-extracted:</b> spaceType (asset class) + city + state + for-sale/lease detection.<br/><b>⚠️ LoopNet NOT auto-extracted:</b> the encoded "sk=" filter blob (cap rate, tenancy, year built, etc.) — set those in the form below.<br/><br/>Form fields ALWAYS take priority over URL filters when both are set.

## `priceMin` (type: `integer`):

Lower bound on asking price.

## `priceMax` (type: `integer`):

Upper bound on asking price.

## `capRateMin` (type: `integer`):

Lower bound on the normalized cap rate (e.g. 5 = 5%).

## `capRateMax` (type: `integer`):

Upper bound on the normalized cap rate.

## `buildingSizeMin` (type: `integer`):

Lower bound on building square footage.

## `buildingSizeMax` (type: `integer`):

Upper bound on building square footage.

## `deduplicate` (type: `boolean`):

Merge listings appearing on both LoopNet and Crexi into a single record (with also\_listed\_on field).

## `normalizeCapRate` (type: `boolean`):

Recalculate cap\_rate\_normalized from NOI / asking\_price. Falls back to asset-class median when NOI absent.

## `monitoringMode` (type: `boolean`):

Emit only listings not seen in previous runs. Uses an internal snapshot keyed by your search criteria. Best run on a daily schedule.

## `transactionTrackingMode` (type: `boolean`):

Emit only listings that closed since the last run (sold / leased / under\_contract / removed). Useful for comp data and market velocity tracking.

## `includeListingDetails` (type: `boolean`):

Fetch each listing's detail page for richer data (NOI, year\_built, building class, full description, photos). Slower and adds $0.003 per listing.

## `includePortfolioProperties` (type: `boolean`):

When a listing groups multiple properties under one URL, emit each as a separate dataset item.

## `downloadImages` (type: `boolean`):

Save listing photos to the Apify Key-Value Store (collection: photos). Off by default to save bandwidth.

## `maxImages` (type: `integer`):

Cap on photos saved per listing (only used when downloadImages = true). NOT a result-count limit — for that, use "Maximum results PER SOURCE" above.

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

Maximum parallel page fetches.

## `minConcurrency` (type: `integer`):

Minimum parallel page fetches.

## `maxRequestRetries` (type: `integer`):

How many times to retry a failed request before giving up.

## `addresses` (type: `array`):

Format: "123 Main St, City, State". Each address queried on every enabled source. Use City + State above instead for new searches.

## `crexiCookies` (type: `array`):

Optional Crexi session cookies (export with EditThisCookie Chrome extension after login). Not required for the default scrape — only useful if you hit Crexi rate limits.

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

Apify residential proxies recommended (default).

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

Backward-compat alias. Use "Maximum results PER SOURCE" above instead. Leave empty — only kept for older saved tasks.

## Actor input object example

```json
{
  "city": "Austin",
  "state": "TX",
  "assetClasses": [],
  "sourcesEnabled": [
    "loopnet",
    "crexi"
  ],
  "transactionTypes": [
    "sale"
  ],
  "maxResultsPerSource": 100,
  "startUrls": [],
  "deduplicate": true,
  "normalizeCapRate": true,
  "monitoringMode": false,
  "transactionTrackingMode": false,
  "includeListingDetails": false,
  "includePortfolioProperties": false,
  "downloadImages": false,
  "maxImages": 10,
  "maxConcurrency": 20,
  "minConcurrency": 1,
  "maxRequestRetries": 5,
  "addresses": [],
  "crexiCookies": [],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Aggregated, normalized commercial real estate listings. Each row is one unique property after cross-platform dedup.

## `photos` (type: `string`):

Listing photos downloaded to the key-value store, only when downloadImages: true. One image per key, prefixed with photo-.

## `snapshots` (type: `string`):

Per-query snapshot files used by monitoringMode (detect new listings) and transactionTrackingMode (detect closed deals) across runs. JSON, prefixed with cre-snapshot-.

# 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 = {
    "city": "Austin",
    "state": "TX",
    "assetClasses": [],
    "startUrls": [],
    "addresses": [],
    "crexiCookies": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("kazkn/commercial-real-estate-brokerage-intel").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 = {
    "city": "Austin",
    "state": "TX",
    "assetClasses": [],
    "startUrls": [],
    "addresses": [],
    "crexiCookies": [],
}

# Run the Actor and wait for it to finish
run = client.actor("kazkn/commercial-real-estate-brokerage-intel").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "city": "Austin",
  "state": "TX",
  "assetClasses": [],
  "startUrls": [],
  "addresses": [],
  "crexiCookies": []
}' |
apify call kazkn/commercial-real-estate-brokerage-intel --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=kazkn/commercial-real-estate-brokerage-intel",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "LoopNet + Crexi Scraper · CRE Listings · CoStar Alternative",
        "description": "LoopNet + Crexi commercial real estate scraper. Aggregates listings, normalizes cap rates (NOI/price + asset-class median fallback), tracks days-on-market, dedups cross-platform, exposes broker contacts. The CoStar alternative for SMB CRE brokers — pay-per-result, $5/1K listings.",
        "version": "0.2",
        "x-build-id": "57FTxENeEUlaOgKCW"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/kazkn~commercial-real-estate-brokerage-intel/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-kazkn-commercial-real-estate-brokerage-intel",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/kazkn~commercial-real-estate-brokerage-intel/runs": {
            "post": {
                "operationId": "runs-sync-kazkn-commercial-real-estate-brokerage-intel",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/kazkn~commercial-real-estate-brokerage-intel/run-sync": {
            "post": {
                "operationId": "run-sync-kazkn-commercial-real-estate-brokerage-intel",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "city": {
                        "title": "🏙️ City",
                        "type": "string",
                        "description": "Target city. Combined with State below, used to build a search bounding box. Leave empty if you provide URLs in the field below."
                    },
                    "state": {
                        "title": "🇺🇸 State (US 2-letter)",
                        "type": "string",
                        "description": "US state code (CA, NY, TX, FL, NV, …). Required when City is set."
                    },
                    "assetClasses": {
                        "title": "🎯 Asset class",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Restrict to specific commercial property types. Empty = all types.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "office",
                                "retail",
                                "industrial",
                                "multifamily",
                                "land",
                                "hotel",
                                "mixed-use",
                                "specialty"
                            ],
                            "enumTitles": [
                                "🏢 Office",
                                "🛍️ Retail",
                                "🏭 Industrial",
                                "🏠 Multifamily",
                                "🌾 Land",
                                "🏨 Hotel",
                                "🏗️ Mixed-use",
                                "🎯 Specialty"
                            ]
                        }
                    },
                    "sourcesEnabled": {
                        "title": "📡 Sources",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Which platforms to query. Both enabled by default (cross-platform dedup needs both).",
                        "items": {
                            "type": "string",
                            "enum": [
                                "loopnet",
                                "crexi"
                            ],
                            "enumTitles": [
                                "LoopNet",
                                "Crexi"
                            ]
                        },
                        "default": [
                            "loopnet",
                            "crexi"
                        ]
                    },
                    "transactionTypes": {
                        "title": "💼 Transaction type",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Whether to fetch <b>for-sale</b> properties (purchase prices, cap rates), <b>for-lease</b> spaces (rent quotes, suite breakdown), or <b>both</b>. When both are enabled, an asset listed for both sale AND lease emits 2 separate dataset rows.<br/><br/><b>⚠️ v0.2 scope:</b> Lease scraping is supported on <b>Crexi</b>. LoopNet for-lease is queued for v0.3 (pending iPhone API reverse-engineering). For lease-only runs prefer <code>sourcesEnabled: [\"crexi\"]</code>.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "sale",
                                "lease"
                            ],
                            "enumTitles": [
                                "🛒 For sale (purchase)",
                                "🏷️ For lease (rent)"
                            ]
                        },
                        "default": [
                            "sale"
                        ]
                    },
                    "maxResultsPerSource": {
                        "title": "🔢 Maximum results PER SOURCE",
                        "minimum": 1,
                        "maximum": 10000,
                        "type": "integer",
                        "description": "Hard cap PER SOURCE per run. With both sources enabled and value=10, you get up to 10 LoopNet + 10 Crexi = 20 total.",
                        "default": 100
                    },
                    "startUrls": {
                        "title": "🔗 Search URLs (optional)",
                        "type": "array",
                        "description": "Optional. Paste search URLs from <a href=\"https://www.loopnet.com\" target=\"_blank\">LoopNet</a> or <a href=\"https://www.crexi.com\" target=\"_blank\">Crexi</a>. URL filters are auto-extracted when possible:<br/><br/><b>✅ Crexi URL params auto-extracted:</b> types[] (asset class), priceMin / priceMax, capRateMin / capRateMax, squareFootageMin / squareFootageMax.<br/><b>⚠️ Crexi NOT auto-extracted:</b> placeIds (location — set City + State manually), subtypes, tenancy, occupancy, yearBuilt, lotSize, keywords, /lease/ paths.<br/><br/><b>✅ LoopNet URL parts auto-extracted:</b> spaceType (asset class) + city + state + for-sale/lease detection.<br/><b>⚠️ LoopNet NOT auto-extracted:</b> the encoded \"sk=\" filter blob (cap rate, tenancy, year built, etc.) — set those in the form below.<br/><br/>Form fields ALWAYS take priority over URL filters when both are set.",
                        "items": {
                            "type": "object",
                            "required": [
                                "url"
                            ],
                            "properties": {
                                "url": {
                                    "type": "string",
                                    "title": "URL of a web page",
                                    "format": "uri"
                                }
                            }
                        }
                    },
                    "priceMin": {
                        "title": "Min asking price (USD)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Lower bound on asking price."
                    },
                    "priceMax": {
                        "title": "Max asking price (USD)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Upper bound on asking price."
                    },
                    "capRateMin": {
                        "title": "Min cap rate (%)",
                        "minimum": 0,
                        "maximum": 50,
                        "type": "integer",
                        "description": "Lower bound on the normalized cap rate (e.g. 5 = 5%)."
                    },
                    "capRateMax": {
                        "title": "Max cap rate (%)",
                        "minimum": 0,
                        "maximum": 50,
                        "type": "integer",
                        "description": "Upper bound on the normalized cap rate."
                    },
                    "buildingSizeMin": {
                        "title": "Min building size (sqft)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Lower bound on building square footage."
                    },
                    "buildingSizeMax": {
                        "title": "Max building size (sqft)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Upper bound on building square footage."
                    },
                    "deduplicate": {
                        "title": "Cross-platform deduplication",
                        "type": "boolean",
                        "description": "Merge listings appearing on both LoopNet and Crexi into a single record (with also_listed_on field).",
                        "default": true
                    },
                    "normalizeCapRate": {
                        "title": "Normalize cap rates",
                        "type": "boolean",
                        "description": "Recalculate cap_rate_normalized from NOI / asking_price. Falls back to asset-class median when NOI absent.",
                        "default": true
                    },
                    "monitoringMode": {
                        "title": "Monitoring mode (only NEW listings)",
                        "type": "boolean",
                        "description": "Emit only listings not seen in previous runs. Uses an internal snapshot keyed by your search criteria. Best run on a daily schedule.",
                        "default": false
                    },
                    "transactionTrackingMode": {
                        "title": "Transaction tracking (CLOSED deals only)",
                        "type": "boolean",
                        "description": "Emit only listings that closed since the last run (sold / leased / under_contract / removed). Useful for comp data and market velocity tracking.",
                        "default": false
                    },
                    "includeListingDetails": {
                        "title": "Include full listing details",
                        "type": "boolean",
                        "description": "Fetch each listing's detail page for richer data (NOI, year_built, building class, full description, photos). Slower and adds $0.003 per listing.",
                        "default": false
                    },
                    "includePortfolioProperties": {
                        "title": "Expand portfolio listings",
                        "type": "boolean",
                        "description": "When a listing groups multiple properties under one URL, emit each as a separate dataset item.",
                        "default": false
                    },
                    "downloadImages": {
                        "title": "Download images to key-value store",
                        "type": "boolean",
                        "description": "Save listing photos to the Apify Key-Value Store (collection: photos). Off by default to save bandwidth.",
                        "default": false
                    },
                    "maxImages": {
                        "title": "Max images per listing",
                        "minimum": 0,
                        "maximum": 50,
                        "type": "integer",
                        "description": "Cap on photos saved per listing (only used when downloadImages = true). NOT a result-count limit — for that, use \"Maximum results PER SOURCE\" above.",
                        "default": 10
                    },
                    "maxConcurrency": {
                        "title": "Max concurrency",
                        "minimum": 1,
                        "maximum": 50,
                        "type": "integer",
                        "description": "Maximum parallel page fetches.",
                        "default": 20
                    },
                    "minConcurrency": {
                        "title": "Min concurrency",
                        "minimum": 1,
                        "maximum": 50,
                        "type": "integer",
                        "description": "Minimum parallel page fetches.",
                        "default": 1
                    },
                    "maxRequestRetries": {
                        "title": "Max request retries",
                        "minimum": 0,
                        "maximum": 50,
                        "type": "integer",
                        "description": "How many times to retry a failed request before giving up.",
                        "default": 5
                    },
                    "addresses": {
                        "title": "Addresses (alternative to URLs)",
                        "type": "array",
                        "description": "Format: \"123 Main St, City, State\". Each address queried on every enabled source. Use City + State above instead for new searches.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "crexiCookies": {
                        "title": "Crexi session cookies (advanced)",
                        "type": "array",
                        "description": "Optional Crexi session cookies (export with EditThisCookie Chrome extension after login). Not required for the default scrape — only useful if you hit Crexi rate limits."
                    },
                    "proxyConfiguration": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "Apify residential proxies recommended (default).",
                        "default": {
                            "useApifyProxy": true,
                            "apifyProxyGroups": [
                                "RESIDENTIAL"
                            ]
                        }
                    },
                    "maxResults": {
                        "title": "Legacy: max results (alias for maxResultsPerSource)",
                        "minimum": 1,
                        "maximum": 10000,
                        "type": "integer",
                        "description": "Backward-compat alias. Use \"Maximum results PER SOURCE\" above instead. Leave empty — only kept for older saved tasks."
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
