# Coldwell Banker Scraper \[$2/1K💰] | Listings, Agents & Offices (`ahmed_jasarevic/coldwellbanker-scraper`) Actor

Pull US property listings, listing-agent contact details and office/agent directories from Coldwell Banker into three separate datasets - price, beds, sqft, lot, taxes, open houses, MLS data, plus agent and office rosters.

- **URL**: https://apify.com/ahmed_jasarevic/coldwellbanker-scraper.md
- **Developed by:** [Ahmed Jasarevic](https://apify.com/ahmed_jasarevic) (community)
- **Features:** Limited permissions
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.90 / 1,000 listings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Coldwell Banker Scraper

Pull **US property listings, listing-agent contact details and full office/agent directories** from [coldwellbanker.com](https://www.coldwellbanker.com) into **three separate datasets** — 75 listing fields, 26 agent fields, 27 office fields, including price, beds, sqft, lot size, open-house schedules, MLS attribution and every agent's email and phone.

Built for real-estate investors, proptech data teams, mortgage and insurance lead-gen, and agent recruiters.

> **Before you publish or resell anything:** Coldwell Banker listing data is **MLS-derived**. That carries redistribution restrictions independent of this Actor. Read [Legal & Compliance](#legal--compliance) first.

### What you get

#### A whole-city price snapshot for one request per 32 listings

This is the thing that makes large runs affordable. Alongside normal listing rows, the Actor emits `listingSummary` rows — **one per city-search page**, each holding a `listings[]` array of cheap search hits (price, beds, baths, sqft, type, MLS, status, lat/lng, first photo), plus `total`, the number of matching listings city-wide.

Those summary rows are **not billed**. A 1,000-listing market costs about 32 requests for the whole city, not 1,000.

#### Listing detail rich enough for valuation models

`price`, `originalPrice`, `bathsDecimal`, `sqft`, `lotSizeSqft`, `lotSizeAcres`, `lotDimensions`, `yearBuilt`, `garageSpaces`, `parkingSpaces`, `stories`, `architecturalStyle`, `flooring`, `heating`, `basement`, `hasFireplace`, `isNewConstruction`, `daysOnMarket` and `listingContractDate` — the fields a comps or pricing model actually needs, all in one row.

#### Open-house schedules as structured data

`openHouses[]` carries `date`, `startTime`, `endTime`, `byAppointment` and `showOnInternet` per event, plus a `hasOpenHouse` boolean. That is the field for agent-scheduling and open-house lead-gen, and you can filter for it server-side with the `hasOpenHouses` input instead of filtering 1,000 rows yourself.

#### Listing-agent contact details with honest coverage

`listingAgentName`, `listingAgentPhone`, `listingAgentEmail`, `listingAgentMlsId`, `coListingAgentName`, `coListingAgentPhone`, `officeName`, `officePhone`, `officeMlsId`. Agent phone is **MLS-dependent** — see [Why is listingAgentPhone sometimes null?](#why-is-listingagentphone-sometimes-null) before you rely on it.

#### Agent and office rosters nobody derives from listings

A listing page's embedded payload never links to an agent or office profile, so agents and offices are **not** obtainable from a listing search. This Actor takes them directly: 26 agent fields (`bio`, `specialties`, `languages`, `activeListingsCount`, `soldListingsCount`, `email`, `phone`) and 27 office fields (`totalAgents`, `areasServed[]`, `agentsServed`, `agents[]`, `totalStaff`), via seed URLs or bulk sitemap discovery.

### How it works

`coldwellbanker.com` is a Next.js application. Every route server-renders its entire dataset for that route into a single inline script tag:

```html
<script id="__NEXT_DATA__" type="application/json">{ … pageProps … }</script>
```

So the Actor downloads plain HTML with `CheerioCrawler` — plain HTTP, **no JavaScript execution** — and `JSON.parse`s that blob. Consequences:

- **No browser.** Roughly 10× cheaper than a Playwright run and about 10× faster, because there is no browser boot and no client-side render wait.
- **No anti-bot to bypass.** There is no Cloudflare, Turnstile, DataDome or fingerprint challenge to work around, because the data is already in the first HTTP response.

| Purpose | URL | Data used |
|---|---|---|
| City listing search | `/city/{state}/{city}` | `propertiesData.properties[]` (32/page), `.total`, `.paginationMetadata` |
| Property detail | `/{state}/{city}/{address}/lid-{id}` | `pageProps.propertyDetails` + `rawPropertyDetails` |
| Agent profile | `/{state}/{city}/agents/{name}/aid-{id}` | `pageProps.detail.agentDetails` |
| Office directory | `/{state}/{city}/offices/{slug}/oid-{id}` | `pageProps.detail` |
| Listing sitemaps | `/xml-sitemap/states/sitemapindex-listings.xml` | `<loc>` — 2,267 per-state sitemaps |
| Agent sitemaps | `/xml-sitemap/states/sitemapindex-agents.xml` | `<loc>` |
| Office sitemaps | `/xml-sitemap/states/sitemapindex-offices.xml` | `<loc>` |

Every one of those sitemaps is **declared in `robots.txt`**, which for `User-agent: *` contains no `Disallow` rules at all — only those six `Sitemap:` lines.

Search filters all apply server-side: `minPrice`, `maxPrice`, `minBedrooms`, `maxBedrooms`, `minBathrooms`, `minSqft`, `maxSqft`, `propertyType` (`SFR`, `CONDO`, `COOP`, `TOWNHOUSE`, `MFR`, `MFD`, `LAND`, `FARM`), `status` (`ACTIVE`, `PENDING`, `COMING_SOON`, …), `hasOpenHouses`, `isRental`, `isNewConstruction`, `sortBy`, `keyword`, `page`.

#### Why sitemap discovery is budgeted per kind

Bulk enumeration is where scrapers usually cannibalise themselves. Here each child sitemap queues at most `maxListings`/`maxAgents`/`maxOffices` URLs **minus what has already been collected**, so listing discovery can never drain the request budget and starve agent or office collection. Search results seeded from `searchLocations` always process first, and listing detail pages are dequeued with `forefront`, so bulk discovery never delays your primary output.

#### Stale search entries don't eat your quota

City search indexes occasionally list properties whose detail page 404s (the page renders `listingNotFoundContent` and carries no payload). The Actor detects this, logs `Listing not found (stale search entry)`, and **backfills the slot from the rest of the same search page** — so a dead entry never consumes `maxListings`. Expect roughly 1 stale hit per 15–30 listings on fast-moving markets.

### Input

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `searchLocations` | string\[] | `["ny/new-york"]` | City slugs in the form `"<state>/<city>"`, e.g. `ny/new-york`, `ca/san-francisco`. |
| `startUrls` | string\[] | `[]` | Direct listing, agent or office URLs. Route is auto-detected. |
| `includeDetails` | boolean | `true` | Expand each search hit to a detail request for the full 75-field row. |
| `minPrice` / `maxPrice` | integer | — | Price range, applied server-side. |
| `minBedrooms` / `maxBedrooms` | integer | — | Bedroom range. |
| `minBathrooms` | integer | — | Minimum bathrooms. |
| `minSqft` / `maxSqft` | integer | — | Living-area range. |
| `propertyType` | string\[] | — | Any of `SFR`, `CONDO`, `COOP`, `TOWNHOUSE`, `MFR`, `MFD`, `LAND`, `FARM`. |
| `status` | string\[] | — | Any of `ACTIVE`, `PENDING`, `COMING_SOON`, … |
| `hasOpenHouses` | boolean | — | Only listings with a published open-home schedule. |
| `isRental` / `isNewConstruction` | boolean | — | Boolean filters. |
| `sortBy` | string | — | `newest`, `price_asc`, `price_desc`, `living_area_desc`. |
| `keyword` | string | — | Free-text keyword pass-through. |
| `maxListings` | integer | `50` | Hard cap on listing records. |
| `maxPagesPerLocation` | integer | `2` | 32 listings per page. |
| `sitemapDiscovery` | object | `{ enabled: false, states: [], kinds: ["listings"], maxUrls: 100 }` | Bulk-enumerate from XML sitemaps. `kinds`: `listings`, `agents`, `offices`. |
| `maxAgents` | integer | `20` | Cap on agent records. |
| `maxOffices` | integer | `20` | Cap on office records. |
| `maxRequestsPerCrawl` | integer | `300` | Runaway guard. |
| `proxy` | object | `{ useApifyProxy: false }` | Standard Apify proxy picker. |

#### Agents and offices need their own source

`searchLocations` alone will always yield `agents: 0, offices: 0`, because a listing page's payload never links to an agent or office profile. Use one of:

- put agent/office URLs (or a whole directory URL) in `startUrls` — the router detects the route;
- or set `sitemapDiscovery: { enabled: true, kinds: ["agents","offices"], states: ["ny"] }`.

If you set `maxAgents`/`maxOffices` without a source, the Actor logs an explicit warning rather than silently returning nothing.

### Output

Three datasets, so each record type validates, exports and bills independently.

| Dataset | Alias | Contents |
| --- | --- | --- |
| Listings | `default` | `listing`, `listingSummary` |
| Agents | `agents` | `agent` |
| Offices | `offices` | `office` |

Run totals are **not** a dataset record — they go to the key-value store as `RUN_SUMMARY.json` (`listings`, `agents`, `offices`, `billed`, `sitemapUrlsQueued`, `pagesFetched`, `isPaying`, `finishedAt`, `aborted`), so a summary row is never billed as data.

#### Listings (`default`) — 75 fields

`type`, `listingId`, `mlsNumber`, `mlsSource`, `mlsName`, `url`, `price`, `originalPrice`, `currency`, `priceText`, `isPriceHidden`, `isSoldListing`, `address`, `city`, `state`, `stateName`, `zip`, `county`, `fullAddress`, `latitude`, `longitude`, `neighborhood`, `beds`, `baths`, `bathsDecimal`, `bathsFull`, `sqft`, `lotSizeSqft`, `lotSizeAcres`, `lotDimensions`, `yearBuilt`, `garageSpaces`, `parkingSpaces`, `stories`, `architecturalStyle`, `flooring`, `heating`, `basement`, `hasFireplace`, `propertyType`, `isNewConstruction`, `isRental`, `description`, `photos`, `photoCount`, `openHouses`, `hasOpenHouse`, `listingAgentName`, `listingAgentPhone`, `listingAgentEmail`, `listingAgentMlsId`, `coListingAgentName`, `coListingAgentPhone`, `officeName`, `officePhone`, `officeMlsId`, `status`, `displayStatus`, `isComingSoon`, `isVirtualTourAvailable`, `listingContractDate`, `firstAppearedDate`, `daysOnMarket`, `lastUpdated`, `mlsDisclaimer`, `idxFeedAttributionCompany`, `source`, `detailFetched`, `scrapedAt`, `location`, `searchUrl`, `placeMasterId`, `page`, `total`, `listings`

Views: **Overview**, **Details**, **Search pages**.

#### Agents (`agents`) — 26 fields

`type`, `agentId`, `name`, `firstName`, `lastName`, `email`, `phone`, `photo`, `bio`, `specialties`, `languages`, `officeName`, `officeId`, `officeUrl`, `officePhone`, `address`, `city`, `state`, `stateName`, `zip`, `latitude`, `longitude`, `activeListingsCount`, `soldListingsCount`, `url`, `source`

Views: **Overview**, **Profiles**.

#### Offices (`offices`) — 27 fields

`type`, `officeId`, `name`, `companyName`, `brandCode`, `phone`, `email`, `website`, `address`, `addressLine2`, `city`, `state`, `zip`, `country`, `latitude`, `longitude`, `photo`, `logo`, `areasServed`, `totalAgents`, `totalStaff`, `agentsServed`, `agents`, `activeListingsCount`, `soldListingsCount`, `url`, `source`

Views: **Overview**, **Directories**, **Agent roster**.

Field notes that change how you read the data:

- `type` is your record discriminator: `listing`, `listingSummary`, `agent` or `office`. Filter on it before loading to a warehouse.
- `sqft` falls back through `structure.livingArea` → `buildingAreaTotal` → the SSR display string. Land, farm and vacant listings legitimately have none, so `null` is normal.
- `openHouses` is `[]` unless the MLS published a schedule. That is an empty schedule, not a missing field.
- `officeName`/`officePhone` on a *listing* come from the MLS attribution block, so they name the **listing brokerage** — often an independent franchise, not Coldwell Banker. Use `type: "office"` rows for true CB office directories.
- Agent source matters: a directory page (`/city/{state}/{city}/agents`, 12 agents per request) gives name, email, phone, photo, office and URL; a profile page adds `bio`, `specialties`, `languages`, `activeListingsCount` and `soldListingsCount`. Directory rows leave the profile-only fields `null`. Seed profile URLs when you need the extras.

### Example input

City search with filters:

```json
{
  "searchLocations": ["ny/new-york"],
  "propertyType": ["SFR"],
  "minBedrooms": 3,
  "maxPrice": 2500000,
  "status": ["ACTIVE"],
  "maxPagesPerLocation": 3,
  "maxListings": 100
}
```

Cheap whole-city price snapshot — no detail requests, summary rows only:

```json
{
  "searchLocations": ["ca/san-francisco", "ny/brooklyn"],
  "includeDetails": false,
  "maxPagesPerLocation": 10,
  "maxListings": 2000
}
```

Agent roster for recruiting:

```json
{
  "searchLocations": [],
  "sitemapDiscovery": { "enabled": true, "states": ["ny"], "kinds": ["agents"], "maxUrls": 300 },
  "maxAgents": 300,
  "maxRequestsPerCrawl": 400
}
```

Specific listing, agent and office pages:

```json
{
  "searchLocations": [],
  "startUrls": [
    "https://www.coldwellbanker.com/ny/springfield-gardens/21626-131st-ave/lid-P00800000HI8DebheIrz6ty0mDIFY6RivDTTN4Mm",
    "https://www.coldwellbanker.com/ny/brooklyn/agents/anthony-marra/aid-P00200000FDdqmuwdqxLZyHkaMD67yyZH1zRejeU"
  ]
}
```

### Example output

```json
{
  "type": "listing",
  "listingId": "P00800000HI8DebheIrz6ty0mDIFY6RivDTTN4Mm",
  "mlsNumber": "1234567890",
  "mlsSource": "onekey",
  "mlsName": "OneKey MLS",
  "url": "https://www.coldwellbanker.com/ny/springfield-gardens/21626-131st-ave/lid-P00800000HI8DebheIrz6ty0mDIFY6RivDTTN4Mm",
  "price": 685000,
  "originalPrice": 725000,
  "currency": "USD",
  "priceText": "$685,000",
  "isPriceHidden": false,
  "isSoldListing": false,
  "address": "21626 131st Ave",
  "city": "Springfield Gardens",
  "state": "NY",
  "stateName": "New York",
  "zip": "11012",
  "county": "Queens",
  "fullAddress": "21626 131st Ave, Springfield Gardens, NY 11012",
  "latitude": 40.65412,
  "longitude": -73.74431,
  "neighborhood": "Springfield Gardens",
  "beds": 4,
  "baths": 3,
  "bathsDecimal": 2.5,
  "bathsFull": "2.5",
  "sqft": 2340,
  "lotSizeSqft": 4800,
  "lotSizeAcres": 0.1101,
  "lotDimensions": "60x80",
  "yearBuilt": 1954,
  "garageSpaces": 1,
  "parkingSpaces": 1,
  "stories": 2,
  "architecturalStyle": "Cape Cod",
  "flooring": "Hardwood",
  "heating": "Forced Air",
  "basement": "Full",
  "hasFireplace": true,
  "propertyType": "SFR",
  "isNewConstruction": false,
  "isRental": false,
  "description": "Well-maintained Cape Cod on a quiet block, finished basement, updated roof 2021.",
  "photos": ["https://imagecache.coldwellbanker.com/1234567890/photo-01.jpg"],
  "photoCount": 28,
  "openHouses": [
    { "date": "2026-10-11", "startTime": "12:00 PM", "endTime": "02:00 PM", "byAppointment": false, "showOnInternet": true }
  ],
  "hasOpenHouse": true,
  "listingAgentName": "Anthony Marra",
  "listingAgentPhone": null,
  "listingAgentEmail": "anthony.marra@cbrealty.com",
  "listingAgentMlsId": "P00200000FDdqmuwdqxLZyHkaMD67yyZH1zRejeU",
  "coListingAgentName": null,
  "coListingAgentPhone": null,
  "officeName": "Marra Realty Group",
  "officePhone": "(718) 555-0132",
  "officeMlsId": "OFF-7781",
  "status": "ACTIVE",
  "displayStatus": "Active",
  "isComingSoon": false,
  "isVirtualTourAvailable": true,
  "listingContractDate": "2026-09-18",
  "firstAppearedDate": "2026-09-19",
  "daysOnMarket": 21,
  "lastUpdated": "2026-10-04T06:12:00.000Z",
  "mlsDisclaimer": "The data is deemed reliable but is not guaranteed.",
  "idxFeedAttributionCompany": "OneKey MLS",
  "source": "search",
  "detailFetched": true,
  "scrapedAt": "2026-10-04T06:12:41.220Z"
}
```

### Integrations & automation

Call it over the Apify API (`POST /v2/acts/1cOxtsUkKezlOoke4/runs`), or from Python, JavaScript, the CLI or MCP. All three datasets export as JSON, CSV, Excel, XML or RSS.

For **market monitoring**, `includeDetails: false` plus a weekly schedule over a list of `searchLocations` gives you city-level price and inventory movement cheaply — the `listingSummary` rows are unbilled, so this is the cheap path. For **agent lead-gen**, run sitemap discovery per state monthly and upsert on `agentId`; agent rosters change slowly.

Typical pipeline: run on schedule → split datasets on `type` → upsert listings on `listingId` and agents on `agentId` → alert when a tracked `mlsNumber` goes `ACTIVE` → `PENDING`, when `daysOnMarket` passes a threshold, or when a tracked neighbourhood's median `price`/`sqft` moves more than ~5%. PostGIS works directly: `latitude`/`longitude` are on listings, agents **and** offices.

#### What a run costs

| Configuration | Events | Cost |
| --- | --- | --- |
| City snapshot, `includeDetails: false`, 3 pages | summary rows only — **unbilled** | **$0** |
| 50 listing detail rows | 50 × listing | **$0.10** |
| 300 agent rows | 300 × agent | **$1.80** |
| 200 office rows | 200 × office | **$1.00** |

Free accounts get a 10-listing preview; paying accounts get the full `maxListings`. `listingSummary` rows are never charged — one row is a whole page of results, not a single listing.

### Related Actors

| Actor | Why |
| --- | --- |
| [`century21-scraper`](https://apify.com/ahmed_jasarevic/century21-scraper) | The same problem on the other big US brokerage brand — the closest structural comparison in this portfolio. |
| [`cargurus-scraper`](https://apify.com/ahmed_jasarevic/cargurus-scraper) | US vehicle-marketplace listings with seller and vehicle detail data. |
| [`cars-com-scraper`](https://apify.com/ahmed_jasarevic/cars-com-scraper) | Same US-marketplace shape, different vertical — useful for schema and price-comparison patterns. |
| [`housinganywhere-scraper`](https://apify.com/ahmed_jasarevic/housinganywhere-scraper) | Long-term rental listings in the UK and Europe, for geographic expansion. |
| [`wallapop-scraper`](https://apify.com/ahmed_jasarevic/wallapop-scraper) | Marketplace listings in Spain, with the same listing-detail + seller structure. |

### FAQ

#### Why not use the official Coldwell Banker API?

Because there isn't a usable one for this data. Coldwell Banker is built on an internal real-estate platform (`api.anywhere.re`) whose public runtime config leaks an API key and a `/properties` route, but that key is scoped and rotated — calling it directly returns `401 {"statusCode":401,"message":"Invalid ApiKey for given resource"}`. What that internal endpoint would give you is the same data already present in the server-rendered page. So the Actor reads the server-rendered JSON instead: no key, no auth, more robust.

#### Agents and offices come back as zero. Why?

Because a listing page's payload never links to an agent or office profile, so a listing search cannot produce them. Supply agent or office URLs in `startUrls`, or enable `sitemapDiscovery` with `kinds: ["agents","offices"]`. Setting `maxAgents`/`maxOffices` without a source logs an explicit warning rather than silently returning nothing.

#### Why is `listingAgentPhone` sometimes null?

It is **MLS-dependent**, and the Actor will not invent one. Midwestern feeds such as Midwest Real Estate Data (IL, WI) populate it; New York's OneKey MLS feed does not ship the field at all. Agent **email** and **office phone** are populated in both. If you need guaranteed agent phone numbers, scrape agent profile pages — those always carry a business phone number.

#### Why do some listings have no `sqft`?

Because `sqft` is resolved through a fallback chain ending at the rendered display string, and land, farm and vacant listings genuinely have no living area. `null` is the correct value there, not a parse failure.

#### Why is `officeName` on a listing not "Coldwell Banker"?

Because it comes from the MLS attribution block, so it names the brokerage that actually holds the *listing* — frequently an independent franchise. For true CB office directories, read the `offices` dataset instead.

#### Why am I getting fewer listings than `maxListings`?

Three causes, all normal: New York's OneKey index limits how many listings a city search returns; `maxPagesPerLocation` may be too low; and stale search entries whose detail page 404s are detected and skipped (with the slot backfilled where possible). Expect roughly 1 stale hit per 15–30 listings on fast-moving markets — the log line is `Listing not found (stale search entry)`.

#### Do I need a browser or a proxy?

No. The data is in the first HTML response, so `CheerioCrawler` with plain HTTP is enough. There is no Cloudflare, DataDome, Akamai or fingerprint challenge on this target. Proxy defaults to off; enable it only if you plan high-volume runs.

### SEO Keywords

coldwell banker scraper, coldwellbanker.com, us real estate listing data, real estate leads, listing agent contact data, property listing api, open house schedule data, mls listing data, house for sale data, realtor directory data, real estate agent directory, real estate office directory, proptech data pipeline, comps data, us property market research, real estate price monitoring, listing photos and details, property valuation data, agent lead generation, mortgage lead generation, real estate market analysis, bulk property scraping

### For AI Agents & LLM Apps

Returns Coldwell Banker data as **three flat datasets** — `default` (listings + page summaries), `agents`, `offices` — discriminated by a `type` field.

**Minimal input** — smallest input that produces a valid run:

```json
{ "maxListings": 25 }
```

**Cheapest useful run** — whole-city snapshot, no detail requests, summary rows are unbilled:

```json
{ "searchLocations": ["ny/new-york"], "includeDetails": false, "maxPagesPerLocation": 5 }
```

**Filtered listing search**:

```json
{ "searchLocations": ["ny/new-york"], "propertyType": ["SFR"], "minBedrooms": 3, "status": ["ACTIVE"], "maxListings": 100 }
```

**Agent roster** (requires sitemap discovery or agent URLs — listings alone yield zero agents):

```json
{ "searchLocations": [], "sitemapDiscovery": { "enabled": true, "states": ["ny"], "kinds": ["agents"] }, "maxAgents": 200 }
```

**Output fields — listings (75):** type, listingId, mlsNumber, mlsSource, mlsName, url, price, originalPrice, currency, priceText, isPriceHidden, isSoldListing, address, city, state, stateName, zip, county, fullAddress, latitude, longitude, neighborhood, beds, baths, bathsDecimal, bathsFull, sqft, lotSizeSqft, lotSizeAcres, lotDimensions, yearBuilt, garageSpaces, parkingSpaces, stories, architecturalStyle, flooring, heating, basement, hasFireplace, propertyType, isNewConstruction, isRental, description, photos, photoCount, openHouses, hasOpenHouse, listingAgentName, listingAgentPhone, listingAgentEmail, listingAgentMlsId, coListingAgentName, coListingAgentPhone, officeName, officePhone, officeMlsId, status, displayStatus, isComingSoon, isVirtualTourAvailable, listingContractDate, firstAppearedDate, daysOnMarket, lastUpdated, mlsDisclaimer, idxFeedAttributionCompany, source, detailFetched, scrapedAt, location, searchUrl, placeMasterId, page, total, listings

**Output fields — agents (26):** type, agentId, name, firstName, lastName, email, phone, photo, bio, specialties, languages, officeName, officeId, officeUrl, officePhone, address, city, state, stateName, zip, latitude, longitude, activeListingsCount, soldListingsCount, url, source

**Output fields — offices (27):** type, officeId, name, companyName, brandCode, phone, email, website, address, addressLine2, city, state, zip, country, latitude, longitude, photo, logo, areasServed, totalAgents, totalStaff, agentsServed, agents, activeListingsCount, soldListingsCount, url, source

**Behaviours an agent should know**

- **Three datasets, one `type` field.** Always filter on `type` (`listing`, `listingSummary`, `agent`, `office`) before processing — the `default` dataset mixes full listings with per-page summary rows.
- **`listingSummary` rows are not billed.** Each holds a `listings[]` array plus `total` (city-wide match count). This is the cheapest way to get market-level price data: one request per 32 listings.
- **Agents and offices require their own source.** `searchLocations` alone always yields `agents: 0, offices: 0`. Use `startUrls` with agent/office URLs, or `sitemapDiscovery.kinds: ["agents","offices"]`. Otherwise the run logs an explicit warning.
- `includeDetails` defaults to `true`. Setting it `false` skips detail pages entirely — much cheaper, but you get only search-level fields.
- `listingAgentPhone` is MLS-dependent and may be `null` (OneKey MLS does not ship it). `listingAgentEmail` and `officePhone` are reliable. Agent **profile** rows always have a phone.
- Stale search entries (detail page 404s) are detected and skipped, and the slot is backfilled from the same page where possible — so counts stay close to `maxListings` without exceeding it.
- Billing is per-event: **$0.002 per listing**, **$0.006 per agent**, **$0.005 per office**. `listingSummary` rows cost nothing. Agents are the most expensive record type by far — check the tiered pricing table for paid-plan rates before scraping a full state roster.
- Free accounts are capped at **10 listings**; `maxAgents`/`maxOffices` are not plan-capped.

### Legal & Compliance

This Actor is an independent third-party tool. It is not affiliated with, endorsed by, or sponsored by Coldwell Banker, by any Coldwell Banker franchisee, or by any MLS.

**What it actually accesses:** publicly visible Coldwell Banker listing, agent and office pages over plain HTTP, plus the XML sitemaps that `robots.txt` explicitly declares. No login, no authentication bypass, no captcha solving, no headless browser. `robots.txt` for `User-agent: *` contains no `Disallow` rules — only `Sitemap:` declarations.

**The real constraint is MLS data licensing, not robots.txt.** Coldwell Banker listing data is sourced from regional Multiple Listing Services (this Actor surfaces `mlsSource`, `mlsName`, `mlsDisclaimer` and `idxFeedAttributionCompany` on every row precisely so you know whose data you are holding). MLS data is licensed to brokerages for member use. **Redistribution, resale, republication or use in a competing data product is generally restricted by those agreements and by NAR rules, even where the web page is publicly viewable.** If your use case is anything other than internal analysis of markets where you operate, get written clearance first — this Actor's README is not that clearance.

**Personal data.** The agents dataset contains `email` and `phone` for real estate professionals, which are business contact details published for buyer enquiries. B2B outreach to agents is regulated in the US by **CAN-SPAM** (email) and **TCPA** (automated or prerecorded calls to mobile numbers). Note also that `named` agent consent is not implied by publication: `claimedStatus` is not exposed on agent rows, so verify before you treat a record as an opt-in contact. GDPR applies to agents with EU ties, and Canadian PIPEDA to agents in Canada.

**Platform terms.** Coldwell Banker's own Terms of Service and franchise agreements also bind, and individual franchises may set additional restrictions.

You are solely responsible for compliance with those agreements, Coldwell Banker's Terms of Service, `robots.txt`, and applicable law. Keep volumes modest: `maxRequestsPerCrawl` (default 300), `maxConcurrency` 3 and `maxRequestRetries` 3 bound a run without effort. Do not use this Actor to build or resell a competing property-listing data product without a licence.

# Actor input Schema

## `searchLocations` (type: `array`):

City slugs to search, formatted as "<state>/<city-slug>", e.g. "ny/new-york" or "ca/claremont". Maps to https://www.coldwellbanker.com/city/<state>/<city>. Each page yields 32 listings.

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

Absolute coldwellbanker.com URLs. Property detail, agent profile and office pages are detected automatically and routed to the right parser.

## `includeDetails` (type: `boolean`):

When true (recommended), every search hit is expanded into a detail request so you get agent name/phone/email, office name/phone, open-house dates, lot size, description and the full photo set. When false, only the cheap search-page row is emitted (no extra requests).

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

Only listings priced at or above this amount.

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

Only listings priced at or below this amount.

## `minBedrooms` (type: `integer`):

Only listings with at least this many bedrooms.

## `maxBedrooms` (type: `integer`):

Only listings with at most this many bedrooms.

## `minBathrooms` (type: `integer`):

Only listings with at least this many bathrooms.

## `minSqft` (type: `integer`):

Only listings with at least this many square feet of living area.

## `maxSqft` (type: `integer`):

Only listings with at most this many square feet of living area.

## `propertyType` (type: `array`):

e.g. SFR (single family), COND, TOWN, MULTI, COOP, LAND, FARM

## `status` (type: `array`):

status

## `hasOpenHouses` (type: `boolean`):

When true, only listings that have an open-house schedule.

## `isRental` (type: `boolean`):

When true, only rental listings; when false, only sale listings.

## `isNewConstruction` (type: `boolean`):

When true, only new-construction listings.

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

sortBy

## `keyword` (type: `string`):

Free-text keyword filter passed through to the site.

## `maxListings` (type: `integer`):

Hard cap on property listings emitted per run.

## `maxPagesPerLocation` (type: `integer`):

Each page = 32 listings. Keep this low for big cities.

## `sitemapDiscovery` (type: `object`):

Bulk URL discovery from the site's own XML sitemaps instead of city search. Best for enumerating agent rosters and office directories.

## `maxAgents` (type: `integer`):

Cap on agent profile records.

## `maxOffices` (type: `integer`):

Cap on office records.

## `maxRequestsPerCrawl` (type: `integer`):

Absolute ceiling on HTTP requests for the run - a runaway guard.

## `proxy` (type: `object`):

Select proxies to be used by your crawler.

## Actor input object example

```json
{
  "searchLocations": [
    "ny/new-york"
  ],
  "startUrls": [],
  "includeDetails": true,
  "propertyType": [],
  "status": [
    "ACTIVE"
  ],
  "maxListings": 50,
  "maxPagesPerLocation": 2,
  "sitemapDiscovery": {
    "enabled": false,
    "states": [],
    "kinds": [
      "listings"
    ],
    "maxUrls": 100
  },
  "maxAgents": 20,
  "maxOffices": 20,
  "maxRequestsPerCrawl": 300,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Property listings: one item per detail page, plus one search-page snapshot item per crawled city search page.

## `agents` (type: `string`):

Agent profiles with contact details, office affiliation and production counts.

## `offices` (type: `string`):

Brokerage office directories with address, coverage areas and agent roster.

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

Run totals: record counts per dataset, pages fetched and billing state.

# 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 = {
    "searchLocations": [
        "ny/new-york"
    ],
    "startUrls": [],
    "propertyType": [],
    "status": [
        "ACTIVE"
    ],
    "proxy": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ahmed_jasarevic/coldwellbanker-scraper").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 = {
    "searchLocations": ["ny/new-york"],
    "startUrls": [],
    "propertyType": [],
    "status": ["ACTIVE"],
    "proxy": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("ahmed_jasarevic/coldwellbanker-scraper").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 '{
  "searchLocations": [
    "ny/new-york"
  ],
  "startUrls": [],
  "propertyType": [],
  "status": [
    "ACTIVE"
  ],
  "proxy": {
    "useApifyProxy": false
  }
}' |
apify call ahmed_jasarevic/coldwellbanker-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ahmed_jasarevic/coldwellbanker-scraper"
        }
    }
}
```

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/1cOxtsUkKezlOoke4/builds/UlBqlnYbX8lBoY59A/openapi.json
