# Realtor.ca Scraper (`haketa/realtor-ca-scraper`) Actor

Realtor.ca scraper & API for Canadian real estate: search homes for sale & rent and export address, price, beds, baths, property type, MLS number, description, agent & brokerage, photos and GPS coordinates. Canada property market data and real-estate lead generation — fast, no login.

- **URL**: https://apify.com/haketa/realtor-ca-scraper.md
- **Developed by:** [Haketa](https://apify.com/haketa) (community)
- **Categories:** Real estate, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 83 total users, 7 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 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.
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

## Realtor.ca Scraper — Canada MLS Listings, Agents & Brokerages Extractor (CREA / Realtor.ca API Alternative)

> **The most complete Realtor.ca data extraction tool on Apify.** Pull live MLS listings, REALTOR agent profiles, and brokerage office records from Realtor.ca — Canada's national MLS portal operated by CREA — with a deep, normalized schema covering price (CAD), beds/baths in Canadian X+Y format, ownership type, taxes, condo fees, heating fuel, agent contact, and brokerage franchise data. Built for prop-tech, investor research, immigration intel, brokerage analytics, mortgage broker outreach, and Canadian real-estate data teams who need **MLS Canada data** without building their own anti-bot-bypassing crawler.

[![Apify Actor](https://img.shields.io/badge/Apify-Actor-blue)](https://apify.com/haketa/realtor-ca-scraper)
[![Live Data](https://img.shields.io/badge/Data-Live%20at%20Run%20Time-orange)]()
[![CA Residential Proxy](https://img.shields.io/badge/Proxy-CA%20Residential%20Recommended-critical)]()
[![Pay Per Event](https://img.shields.io/badge/Pricing-Pay%20Per%20Event-yellow)]()
[![Coverage](https://img.shields.io/badge/Coverage-All%2010%20Provinces%20%2B%203%20Territories-purple)]()
[![Bilingual](https://img.shields.io/badge/Languages-EN%20%2F%20FR-informational)]()
[![Scale](https://img.shields.io/badge/Scale-1%2C000%2B%20MLS%20Listings%20Pagination-2EA44F)]()

***

### ⚡ At a glance

| Feature | Details |
|---|---|
| **Target Platform** | [Realtor.ca](https://www.realtor.ca) — Canada's National MLS® Portal (All 10 Provinces & 3 Territories) |
| **Scrape Modes** | 27-City Dropdown (`cities`), Area URLs (`search-area`), Custom Bounding Box (`map-bbox`), Single MLS Lookup (`single-listing`), Agent Profiles (`agent-profile`), Brokerage Offices (`office-profile`) |
| **Core Extracted Fields** | `mlsNumber`, `listingId`, `priceAmount` (CAD), `pricePerSqft`, `bedroomsTotal` (`above` + `below` X+Y decoded), `bathroomsTotal`, `sizeInterior` (sqft), `ownershipType` (`Freehold`/`Condominium`/`Strata`), `taxAnnualAmount`, `condoFeeMonthly`, `addressLine`, `city`, `province`, `postalCode`, `latitude`, `longitude`, `agentName`, `agentPhone`, `officeName`, `brokerageFranchise`, `photos` |
| **Pagination & 1K+ Scale** | Automatic bounding-box geo-grid splitting + multi-page traversal (`maxPages: 0` = auto/unlimited governed by `maxItems`), scaling to **1,000+ deduplicated MLS listings** across Canadian cities |
| **Empty-Input Behavior** | Running with `{}` automatically collects a fast 100-listing sample across Toronto, Montreal, Vancouver, and Calgary |
| **Export Formats** | JSON, JSONL, CSV, Excel (XLSX), HTML, XML, RSS, and Apify REST API |

***

### What This Actor Does

The **Realtor.ca Scraper** is a production-grade Apify Actor that extracts the **full public MLS listing universe** from **[Realtor.ca](https://www.realtor.ca/)** — the national real-estate portal operated by the **Canadian Real Estate Association (CREA)** and powered by data from every member real-estate board in Canada (TREB, REBGV, OREB, CREB, QPAREB, and 70+ others). Realtor.ca is the single most-trafficked Canadian real-estate website and the only consumer-facing surface that aggregates listings from coast to coast — Toronto's Yorkville condos, Vancouver's Kitsilano detached homes, Montreal's Plateau triplexes, Calgary acreages, Halifax waterfronts, and Yellowknife cabins all live in one searchable index.

In a single run, the actor returns structured records covering:

- **Active for-sale residential listings** — single-family detached, semi-detached, townhomes, condo apartments, lofts, co-ops, mobile homes, vacant land
- **Active for-rent residential listings** — apartments, condos, rental houses, basement suites
- **Active commercial listings** — office, retail, industrial, multi-family investment, hospitality, business-for-sale
- **REALTOR agent profiles** — licensed CREA REALTOR members with bio, designations, languages, service areas, and current listing counts
- **Brokerage / office profiles** — franchise affiliations (RE/MAX, Royal LePage, Century 21, Sutton, Coldwell Banker, EXP, Engel & Völkers, independent boutiques), agent counts, and total active listings

Every record arrives flattened, normalized, and ready for ingestion into Postgres, BigQuery, Snowflake, Google Sheets, Salesforce, HubSpot, or any BI / CRM stack — no parsing scripts, no anti-bot cookie wrangling, no per-province board logic, no CSV stitching.

#### Why scrape Realtor.ca yourself when this exists?

Realtor.ca is one of the **most aggressively anti-bot real-estate portals on the public internet**. Teams that try to roll their own crawler — even experienced ones — discover the same problems in the same order:

- Realtor.ca sits behind **Cloudflare bot-management** (plus IP-reputation gating) with a challenge/"you have been blocked" wall — datacenter IPs are blocked within seconds, a plain Chrome/`got` TLS fingerprint is rejected with a 403 even from a clean residential IP, and a flagged IP never clears until you rotate to a fresh one
- The undocumented `api2.realtor.ca/Listing.svc/PropertySearch_Post` endpoint is a map-centric search that expects an `ApplicationId`, a lat/lng bounding box, and a consistent browser-like client fingerprint — reverse-engineering it takes days
- Pagination tops out at a **hard 200-results-per-query cap**; provincewide queries silently truncate unless you split the geography into a bounding-box grid
- Listing detail pages are **bilingual (English + French)** — Quebec inventory is FR-only and naive parsers drop or mangle accented characters
- Beds/baths come as `"3 + 1"` strings (above-ground + basement) — a uniquely Canadian convention foreign scrapers stringify as `"3"` and lose half the bedrooms
- Ownership types include **Freehold, Condominium, Strata (BC), Leasehold, Co-operative, Life Lease, and Undivided Co-ownership** — each implies a different cost structure
- Taxes are annual **CAD** values with a separate `taxYear`; condo fees include a `condoFeeIncludes` array (heat, water, parking, common elements) that nobody parses correctly the first time
- Agent and office data live on **separate `realtor.ca/agent/{id}` and `realtor.ca/office/firm/{id}` profile pages** (each server-renders a `PropertySearch_Post` payload into the HTML) — joining them to listings requires coordinated request streams
- Realtor.ca throttles aggressively: more than **2-4 parallel requests from the same IP** triggers a cooldown
- When the site's response schema shifts (it has several times), every rigid selector-based scraper breaks silently — this actor parses with a tolerant, key-fuzzy schema instead
- CREA's robots.txt and ToS expressly forbid datacenter crawling — only residential-IP, low-concurrency access keeps you under the radar

This actor solves all of that: sticky-session **Canadian residential proxy** with fresh-IP rotation on every block, conservative concurrency, the correct `ApplicationId`, automatic bounding-box grid splitting, X+Y bed/bath decoding, EN/FR language detection, joined agent + office enrichment, and a flat output schema you can drop straight into a warehouse.

***

### Quick Start

#### One-Click Run (Apify UI)

1. Open the actor page on Apify Store: [haketa/realtor-ca-scraper](https://apify.com/haketa/realtor-ca-scraper)
2. Click **"Try for free"**
3. In **Cities / Markets**, pick one or more Canadian cities from the dropdown (Toronto, Vancouver, Calgary are prefilled) — **no URLs, IDs or codes needed**. Leave it empty to pull a fresh mixed sample from popular markets across Canada.
4. Optionally set filters — **For Sale / For Rent**, **Property Category**, price, beds, baths — and a **Max Results** cap for a fast, cheap test run
5. Hit **Start** — your dataset is available in the **Dataset** tab within minutes, downloadable as JSON, CSV, Excel, HTML, XML, or JSON-Lines

> **Power users:** switch **Advanced Input Mode** to paste your own Realtor.ca area-page links, a map bounding box, MLS numbers, or agent / brokerage profile links in the **Advanced Inputs** field. See the [Scrape Mode Reference](#scrape-mode-reference) below.

#### API Run (Python)

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run = client.actor("haketa/realtor-ca-scraper").call(run_input={
    "cities": ["toronto", "vancouver", "calgary"],
    "transactionType": "for-sale",
    "propertyTypeGroup": "residential",
    "priceMin": 500000,
    "priceMax": 2000000,
    "bedroomsMin": 2,
    "fetchDetails": True,
    "fetchAgentProfiles": False,
    "fetchOfficeProfiles": False,
    "maxItems": 500,
    "maxPages": 10,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "CA"
    }
})

for record in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(record["mlsNumber"], record["addressLine"], record["city"], record["priceAmount"])
```

#### API Run (Node.js / TypeScript)

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

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });

const run = await client.actor('haketa/realtor-ca-scraper').call({
    advancedMode: 'map-bbox',
    mapBoundingBox: {
        latMin: 43.62,   // Toronto downtown core
        latMax: 43.72,
        lngMin: -79.45,
        lngMax: -79.32
    },
    transactionType: 'for-sale',
    propertyTypeGroup: 'residential',
    priceMin: 800000,
    bedroomsMin: 2,
    fetchDetails: true,
    maxItems: 300,
    proxyConfiguration: {
        useApifyProxy: true,
        apifyProxyGroups: ['RESIDENTIAL'],
        apifyProxyCountry: 'CA'
    }
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(`Pulled ${items.length} downtown Toronto for-sale listings`);
```

#### API Run (cURL)

```bash
curl -X POST "https://api.apify.com/v2/acts/haketa~realtor-ca-scraper/runs?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "advancedMode": "single-listing",
    "searchUrls": ["X8234567", "W8123450", "C8765432"],
    "fetchDetails": true,
    "fetchAgentProfiles": true,
    "proxyConfiguration": {
      "useApifyProxy": true,
      "apifyProxyGroups": ["RESIDENTIAL"],
      "apifyProxyCountry": "CA"
    }
  }'
```

***

### How It Works

Realtor.ca is **not** a static HTML site. The consumer-facing pages at `realtor.ca/{province}/{city}/real-estate` and `realtor.ca/real-estate/{mls-number}/...` are React shells that hydrate from CREA's internal JSON API at `api2.realtor.ca`. This actor talks to that API directly using an **`impit` HTTP client with a genuine Firefox TLS fingerprint** (no headless browser overhead), parses the JSON, and — for agent/office profiles and the fields that only appear in the HTML hydration payload — parses the server-rendered page with Cheerio. The Firefox fingerprint is what clears **Cloudflare** on the `www.realtor.ca` host where a Chrome/`got` fingerprint is blocked with a 403.

| Source Endpoint | Purpose | Method |
|---|---|---|
| `api2.realtor.ca/Listing.svc/PropertySearch_Post` | Paginated listing search by map bounding box (built from the city you pick), price, beds/baths, transaction type | POST (form-encoded) |
| `api2.realtor.ca/Listing.svc/PropertyDetails` | Full listing detail by PropertyID + ReferenceNumber (MLS) — remarks, all photos, features, appliances, financials | GET |
| `www.realtor.ca/agent/{id}/{slug}` | Agent profile HTML — `agent-profile` mode & `fetchAgentProfiles` enrichment. The page server-renders a JSON-LD Person block + a `PropertySearch_Post` payload (bio, designations, languages, current listing count) | GET (Cheerio) |
| `www.realtor.ca/office/firm/{id}/{slug}` | Brokerage / office profile HTML — `office-profile` mode & `fetchOfficeProfiles` enrichment. Carries the office JSON-LD + an embedded `PropertySearch_Post` payload (franchise, total listing count) | GET (Cheerio) |

> There is **no separate `Profile.svc` API** — Realtor.ca exposes agent and office data only through their HTML profile pages, which the actor parses (JSON-LD + the embedded search payload rendered into the page). The listing search and detail both come from `api2.realtor.ca/Listing.svc`.

#### Engineering details

- **HTTP-first with `impit` (Firefox fingerprint)** — no Puppeteer / Playwright overhead; a genuine Firefox TLS/HTTP fingerprint clears **Cloudflare** on `www.realtor.ca` where a Chrome / `got-scraping` client is 403-blocked. No Chrome client-hints are sent (they would contradict the Firefox fingerprint and tip off Cloudflare)
- **Canadian residential proxy mandatory** — `apifyProxyGroups: ["RESIDENTIAL"]` + `apifyProxyCountry: "CA"`; datacenter IPs are blocked immediately and the API host (`api2.realtor.ca`) is Cloudflare-gated
- **Sticky-session + fresh-IP rotation** — a clean Canadian residential IP is pinned for all requests while it works (pagination reuses the cleared IP); the instant a request is challenged, the actor rotates to a brand-new IP and retries — up to 10 fresh-IP attempts per request
- **Bounding-box grid auto-split** — every city you pick (and any custom `map-bbox`) is split into a grid of cells so each query stays under Realtor.ca's 200-result-per-query cap, then results are deduplicated by MLS number / listing ID
- **`ApplicationId` injection** — the actor uses a known-good ApplicationId by default and exposes it as an override input for the rare day CREA rotates it
- **Conservative concurrency (2-4) + request delay** — well below Realtor.ca's throttling threshold; tuned for sustained multi-thousand-listing runs
- **Soft-deadline shutdown** — the actor stops starting new work a safety buffer before the platform timeout, so a run always finishes cleanly (`SUCCEEDED`) with whatever it gathered instead of being killed mid-flight
- **X+Y bed/bath decoder** — `"3 + 1"` → `bedroomsTotal: 4`, `bedroomsAbove: 3`, `bedroomsBelow: 1`; bathrooms decoded as `bathroomsTotal = full + half × 0.5`
- **EN/FR language detection** — `descriptionLang` is set per-listing so downstream pipelines can route Quebec inventory to French NLP, and Ontario/Alberta/BC inventory to English
- **CAD currency normalization** — every `priceAmount`, `taxAnnualAmount`, `condoFeeMonthly` is a numeric value; `priceCurrency` is always `"CAD"`
- **Tax-to-price ratio precomputed** — `taxToPrice = taxAnnualAmount / priceAmount` for instant comparables filtering
- **Agent + office enrichment** — `fetchAgentProfiles` and `fetchOfficeProfiles` add one request per **unique** agent/office (not per listing) — extremely cheap even on thousand-listing runs
- **Deterministic, flat output** — one record per listing (or per agent/office in profile modes); same shape regardless of mode so a single ingestion script handles everything

***

### Input Parameters

```json
{
  "cities": ["toronto", "vancouver", "calgary"],
  "transactionType": "for-sale",
  "propertyTypeGroup": "residential",
  "priceMin": 500000,
  "priceMax": 2000000,
  "bedroomsMin": 2,
  "bathroomsMin": 1,
  "newListingsOnlyDays": 0,
  "language": "both",
  "fetchDetails": true,
  "fetchAgentProfiles": false,
  "fetchOfficeProfiles": false,
  "maxItems": 500,
  "maxPages": 10,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "CA"
  },
  "requestDelay": 1500,
  "maxConcurrency": 2,
  "maxRetries": 4,
  "debugMode": false
}
```

#### Parameter reference

| Parameter | Type | Default | Description |
|---|---|---|---|
| `cities` | `array<string>` | `["toronto","vancouver","calgary"]` | **Primary input.** Pick one or more Canadian cities from the dropdown — 27 markets across all provinces (Toronto, Mississauga, Ottawa, Montreal, Quebec City, Vancouver, Surrey, Victoria, Calgary, Edmonton, Winnipeg, Halifax, St. John's, and more). No URLs or codes required. Leave empty for a fresh mixed sample of popular markets. |
| `transactionType` | `string` | `for-sale` | `for-sale`, `for-rent`, or `both`. |
| `propertyTypeGroup` | `string` | `residential` | `residential` (houses, condos, townhomes), `commercial` (office, retail, industrial), or `all`. |
| `priceMin` | `integer` | `0` | Minimum price in CAD. `0` = no minimum. |
| `priceMax` | `integer` | `0` | Maximum price in CAD. `0` = no maximum. |
| `bedroomsMin` | `integer` | `0` | Minimum bedroom count. `0` = no filter. |
| `bathroomsMin` | `integer` | `0` | Minimum bathroom count. `0` = no filter. |
| `newListingsOnlyDays` | `integer` | `0` | Keep only listings first published within the last N days. `0` = keep all. Great for fresh-lead monitoring. |
| `language` | `string` | `both` | `both`, `en`, or `fr` — filter by detected description language. Useful for Quebec-only or rest-of-Canada feeds. |
| `fetchDetails` | `boolean` | `false` | Visit each listing's detail endpoint for full remarks, all photos, features, appliances, financials. Off (`false`) = fast search-summary extraction (ideal for quick runs and high-volume sweeps). |
| `fetchAgentProfiles` | `boolean` | `false` | Additionally fetch the REALTOR agent profile (bio, designations, languages, service areas, listing count) for each listing's agent. One request per unique agent. |
| `fetchOfficeProfiles` | `boolean` | `false` | Additionally fetch the brokerage office profile (franchise, total listings). One request per unique office. |
| `maxItems` | `integer` | `0` (prefill 100) | Hard cap on total records across all cities/inputs. `0` = unlimited (supports **1,000+ listings** across cities and pages). Keep a limit for fast, low-cost test runs. |
| `maxPages` | `integer` | `0` | Maximum result pages per city / bbox cell (`0` = auto/unlimited governed by `maxItems`). Each page returns up to ~200 listings. |
| `proxyConfiguration` | `object` | CA Residential | **REQUIRED.** Use Apify Proxy with `RESIDENTIAL` group and `CA` country. Datacenter IPs are blocked. |
| `requestDelay` | `integer` | `1500` | Milliseconds between requests. Keep at 1000+ for sustained runs. |
| `maxConcurrency` | `integer` | `2` | Parallel area-search tasks. Realtor.ca tolerates 2-4 — higher risks a cooldown. |
| `maxRetries` | `integer` | `4` | Retry attempts on blocked / challenged responses. |
| `debugMode` | `boolean` | `false` | Verbose logging — dumps API request/response shapes and field maps for the first page. Use only when troubleshooting. |

##### Advanced input (power users)

Most users only need the city dropdown. To scrape your own targets, set **Advanced Input Mode** (`advancedMode`) to one of the options below and put your links / numbers in the **Advanced Inputs** field (`searchUrls`), or supply a `mapBoundingBox`:

| Parameter | Type | Description |
|---|---|---|
| `advancedMode` | `string` | `cities` (default, uses the dropdown), `search-area`, `map-bbox`, `single-listing`, `agent-profile`, or `office-profile`. |
| `searchUrls` | `array<string>` | Realtor.ca area-page links (`https://www.realtor.ca/on/ottawa/real-estate`), MLS numbers (`X13126780`), agent links (`https://www.realtor.ca/agent/2182401/jane-doe`), or brokerage links (`https://www.realtor.ca/office/firm/259003/royal-lepage-team`) — matched to the chosen `advancedMode`. |
| `mapBoundingBox` | `object` | A `{ latMin, latMax, lngMin, lngMax }` box in decimal degrees for `map-bbox` mode. Large boxes auto-split into a grid. |
| `applicationId` | `string` | Override the `ApplicationId` sent to `api2.realtor.ca`. Leave blank to use the built-in default. |

***

### Output Schema

Every record arrives as a flat JSON object with the same top-level shape regardless of whether it was produced by a listing mode, an `agent-profile` mode, or an `office-profile` mode. The `recordType` field tells you which one to expect, and irrelevant fields are simply `null`.

#### Identity & source fields

| Field | Type | Description |
|---|---|---|
| `recordType` | `string` | `listing` (default) / `agent` / `office` |
| `mlsNumber` | `string` | MLS listing ID, board-prefixed (e.g. `X8234567`, `W8123450`, `C8765432`) |
| `listingId` | `string` | Realtor.ca internal listing ID |
| `source` | `string` | Always `realtor.ca` |
| `mode` | `string` | Scrape mode that produced this record |
| `listingUrl` | `string` | Full canonical Realtor.ca listing URL |
| `searchUrl` | `string` | Source input URL / search query |
| `scrapedAt` | `string` | ISO-8601 timestamp of extraction |

#### Transaction & price fields (CAD)

| Field | Type | Description |
|---|---|---|
| `transactionType` | `string` | `For Sale` / `For Rent` |
| `status` | `string` | `active` (v1 — sold inference reserved) |
| `priceRaw` | `string` | Original price string from Realtor.ca |
| `priceAmount` | `number` | Parsed numeric price in CAD |
| `priceCurrency` | `string` | Always `CAD` |
| `priceFrequency` | `string` | `Monthly` / `Yearly` / `Weekly` (rentals) |
| `pricePerSqft` | `number` | Price ÷ interior size |

#### Property characteristics

| Field | Type | Description |
|---|---|---|
| `propertyType` | `string` | `Single Family` / `Condo` / `Vacant Land` / `Multi-Family` / `Commercial` etc. |
| `buildingType` | `string` | `House` / `Apartment` / `Row-Townhouse` / `Duplex` / `Mobile Home` etc. |
| `ownershipType` | `string` | `Freehold` / `Condominium` / `Strata` / `Leasehold` / `Co-operative` / `Life Lease` |
| `bedroomsRaw` | `string` | Original bed string (e.g. `"3 + 1"`) |
| `bedroomsTotal` | `number` | Total bedrooms (above + below ground) |
| `bedroomsAbove` | `number` | Above-ground bedrooms |
| `bedroomsBelow` | `number` | Basement bedrooms (may be non-conforming) |
| `bathroomsRaw` | `string` | Original bath string (e.g. `"2 + 1"`) |
| `bathroomsTotal` | `number` | Full + half × 0.5 |
| `bathroomsFull` | `number` | Full bathrooms |
| `bathroomsHalf` | `number` | Half / powder bathrooms |
| `sizeInteriorRaw` | `string` | Original interior size string |
| `sizeInterior` | `number` | Parsed square footage |
| `lotSizeRaw` | `string` | Lot / land size string |
| `sizeFrontage` | `string` | Lot frontage |
| `yearBuilt` | `integer` | Construction year |
| `parkingType` | `string` | `Garage` / `Carport` / `Underground` / `Street` |
| `parkingSpaces` | `integer` | Number of parking spots |
| `basementType` | `string` | `Full` / `Partial` / `Crawl Space` / `None` |
| `basementFeatures` | `array` | `Finished` / `Walk-out` / `Separate Entrance` etc. |
| `heatingType` | `string` | `Forced Air` / `Baseboard` / `Heat Pump` / `Radiant` |
| `heatingFuel` | `string` | `Natural Gas` / `Electric` / `Oil` / `Propane` / `Wood` |
| `coolingType` | `string` | `Central Air` / `Wall Unit` / `None` |
| `exteriorFinish` | `string` | `Brick` / `Vinyl` / `Stucco` / `Wood` / `Stone` |
| `roofType` | `string` | `Asphalt Shingle` / `Metal` / `Tile` |
| `flooringType` | `string` | `Hardwood` / `Carpet` / `Tile` / `Laminate` / `Vinyl` |
| `poolType` | `string` | `Inground` / `Above Ground` / `None` |
| `waterSource` | `string` | `Municipal` / `Well` / `Lake` |
| `sewer` | `string` | `Municipal` / `Septic` |
| `zoningDescription` | `string` | Zoning text |
| `fireplacePresent` | `boolean` | Whether the property has a fireplace |
| `viewType` | `string` | Water / Mountain / City etc. |
| `waterfrontName` | `string` | Adjacent water body name |
| `features` | `array` | Property features / amenities |
| `appliances` | `array` | Included appliances |

#### Location

| Field | Type | Description |
|---|---|---|
| `addressLine` | `string` | Street address |
| `city` | `string` | City |
| `province` | `string` | Province code (`ON`, `QC`, `BC`, `AB`, `MB`, `SK`, `NS`, `NB`, `NL`, `PE`, `YT`, `NT`, `NU`) |
| `postalCode` | `string` | Canadian postal code (`A1A 1A1`) |
| `latitude` | `number` | Latitude |
| `longitude` | `number` | Longitude |
| `communityName` | `string` | Community / subdivision name |
| `neighbourhood` | `string` | Neighbourhood name |
| `locationDescription` | `string` | Cross-streets / directions |

#### Financials

| Field | Type | Description |
|---|---|---|
| `taxAnnualAmount` | `number` | Annual property tax in CAD |
| `taxYear` | `integer` | Tax assessment year |
| `taxToPrice` | `number` | Annual tax ÷ price (precomputed) |
| `condoFeeMonthly` | `number` | Monthly condo / strata / maintenance fee in CAD |
| `condoFeeIncludes` | `array` | What the condo fee covers (heat, water, parking, common elements) |
| `associationFee` | `number` | HOA-style association fee if present |

#### Listing lifecycle & media

| Field | Type | Description |
|---|---|---|
| `listingDate` | `string` | Date the listing was published |
| `lastUpdated` | `string` | Date last modified |
| `daysOnMarket` | `integer` | DOM (reserved for future versions — requires cross-run state) |
| `hasOpenHouse` | `boolean` | Whether an open house is scheduled |
| `openHouses` | `array` | Scheduled open house dates / times |
| `photoCount` | `integer` | Number of photos |
| `thumbnailUrl` | `string` | Primary photo URL |
| `photos` | `array` | All photo URLs |
| `virtualTourUrl` | `string` | 3D / virtual tour link |
| `videoUrl` | `string` | Video link |
| `publicRemarks` | `string` | Public listing description |
| `descriptionLang` | `string` | `en` or `fr` |

#### Agent fields

| Field | Type | Description |
|---|---|---|
| `agentName` | `string` | REALTOR name |
| `agentId` | `string` | Realtor.ca agent ID |
| `agentUrl` | `string` | Agent profile URL |
| `agentPhone` | `string` | Contact phone |
| `agentEmail` | `string` | Always `null` — Realtor.ca hides agent emails behind a contact form (use `agentPhone` / `agentWebsite`) |
| `agentWebsite` | `string` | Personal website |
| `agentPhoto` | `string` | Photo URL |
| `agentBio` | `string` | Agent biography (profile modes) |
| `agentDesignations` | `array` | Professional designations / certifications |
| `agentLanguages` | `array` | Languages spoken |
| `agentServiceAreas` | `array` | Geographic service areas (agent-profile mode) |
| `agentSpecializations` | `array` | Residential / Commercial / Luxury / Rural |
| `currentListingCount` | `integer` | Agent's active listing count (agent-profile mode) |

#### Office / brokerage fields

| Field | Type | Description |
|---|---|---|
| `officeName` | `string` | Brokerage / office name |
| `officeId` | `string` | Realtor.ca office firm ID |
| `officeUrl` | `string` | Office profile URL |
| `officeAddress` | `string` | Brokerage address |
| `officePhone` | `string` | Brokerage phone |
| `officeWebsite` | `string` | Brokerage website |
| `officeEmail` | `string` | Always `null` — not published by Realtor.ca (use `officePhone` / `officeWebsite`) |
| `brokerageFranchise` | `string` | Parent franchise (RE/MAX, Royal LePage, Century 21, Sutton, Coldwell Banker, eXp Realty, Engel & Völkers, Sotheby's, HomeLife, and more; `null` if independent/unknown) |
| `officeAgentCount` | `integer` | Number of agents at the office (office-profile mode) |
| `officeListingCount` | `integer` | Active listings at the office (office-profile mode) |

#### Intelligence fields (reserved)

| Field | Type | Notes |
|---|---|---|
| `firstSeenAt`, `priceHistory`, `priceChanged`, `priceReduced`, `changePercent`, `hpiBenchmark`, `deviationVsHPI`, `marketCondition`, `estimatedStatus` | various | Reserved for a future version that tracks state across runs and joins to CREA MLS HPI benchmarks. Present in the schema for forward-compatibility; emitted as `null` in v1. |

#### Example: Toronto condo for-sale listing

```json
{
  "recordType": "listing",
  "mlsNumber": "C8123450",
  "listingId": "27345610",
  "source": "realtor.ca",
  "mode": "search-area",
  "listingUrl": "https://www.realtor.ca/real-estate/27345610/210-1080-bay-st-toronto",
  "transactionType": "For Sale",
  "status": "active",
  "priceRaw": "$899,000",
  "priceAmount": 899000,
  "priceCurrency": "CAD",
  "priceFrequency": null,
  "pricePerSqft": 1199,
  "propertyType": "Single Family",
  "buildingType": "Apartment",
  "ownershipType": "Condominium",
  "bedroomsRaw": "2 + 0",
  "bedroomsTotal": 2,
  "bedroomsAbove": 2,
  "bedroomsBelow": 0,
  "bathroomsRaw": "2 + 0",
  "bathroomsTotal": 2,
  "bathroomsFull": 2,
  "bathroomsHalf": 0,
  "sizeInteriorRaw": "700 - 799 sqft",
  "sizeInterior": 750,
  "yearBuilt": 2015,
  "parkingType": "Underground",
  "parkingSpaces": 1,
  "heatingType": "Forced Air",
  "heatingFuel": "Natural Gas",
  "coolingType": "Central Air",
  "addressLine": "210 - 1080 Bay St",
  "city": "Toronto",
  "province": "ON",
  "postalCode": "M5S 0A5",
  "latitude": 43.6685,
  "longitude": -79.3870,
  "communityName": "Bay Street Corridor",
  "neighbourhood": "Yorkville",
  "taxAnnualAmount": 3420,
  "taxYear": 2024,
  "taxToPrice": 0.0038,
  "condoFeeMonthly": 615,
  "condoFeeIncludes": ["Heat", "Water", "Common Area Maintenance", "Building Insurance"],
  "features": ["Concierge", "Gym", "Pool", "Visitor Parking"],
  "appliances": ["Dishwasher", "Fridge", "Stove", "Washer", "Dryer"],
  "listingDate": "2026-05-10",
  "lastUpdated": "2026-05-15",
  "photoCount": 28,
  "thumbnailUrl": "https://cdn.realtor.ca/listing/TS123/cdn/...",
  "publicRemarks": "Bright south-facing 2-bed in the heart of Yorkville. Steps to the subway, U of T, and Bloor Street shopping. Building amenities include 24-hr concierge, gym, and indoor pool.",
  "descriptionLang": "en",
  "agentName": "Jane Doe",
  "agentId": "2182401",
  "agentPhone": "(416) 555-0199",
  "agentEmail": null,
  "officeName": "Royal LePage Signature Realty",
  "officeId": "259003",
  "brokerageFranchise": "Royal LePage",
  "scrapedAt": "2026-05-16T14:30:00.000Z"
}
```

#### Example: Vancouver West End for-rent listing

```json
{
  "recordType": "listing",
  "mlsNumber": "R2891234",
  "source": "realtor.ca",
  "mode": "map-bbox",
  "transactionType": "For Rent",
  "priceRaw": "$3,200 / Monthly",
  "priceAmount": 3200,
  "priceCurrency": "CAD",
  "priceFrequency": "Monthly",
  "propertyType": "Single Family",
  "buildingType": "Apartment",
  "ownershipType": "Strata",
  "bedroomsTotal": 1,
  "bathroomsTotal": 1,
  "sizeInterior": 620,
  "addressLine": "1205 - 1234 Davie St",
  "city": "Vancouver",
  "province": "BC",
  "postalCode": "V6E 1N5",
  "neighbourhood": "West End",
  "descriptionLang": "en",
  "officeName": "RE/MAX Crest Realty",
  "brokerageFranchise": "RE/MAX",
  "scrapedAt": "2026-05-16T14:30:00.000Z"
}
```

#### Example: Montreal Plateau French-language listing

```json
{
  "recordType": "listing",
  "mlsNumber": "Q19876543",
  "source": "realtor.ca",
  "transactionType": "For Sale",
  "priceAmount": 749000,
  "priceCurrency": "CAD",
  "propertyType": "Multi-Family",
  "buildingType": "Triplex",
  "ownershipType": "Freehold",
  "bedroomsTotal": 6,
  "bathroomsTotal": 3,
  "addressLine": "4520 Rue Saint-Denis",
  "city": "Montréal",
  "province": "QC",
  "postalCode": "H2J 2L4",
  "neighbourhood": "Le Plateau-Mont-Royal",
  "publicRemarks": "Magnifique triplex au cœur du Plateau. Trois logements rénovés avec revenus stables. Idéal pour propriétaire-occupant ou investisseur.",
  "descriptionLang": "fr",
  "scrapedAt": "2026-05-16T14:30:00.000Z"
}
```

***

### Scrape Mode Reference

`advancedMode` selects how the actor finds listings. The default — **`cities`** — needs nothing but the dropdown; the rest are for power users who paste their own links, numbers, or a map box in the `searchUrls` / `mapBoundingBox` fields.

| `advancedMode` | Input shape | Output | When to use |
|---|---|---|---|
| `cities` *(default)* | City slugs in the **Cities / Markets** dropdown | `listing` records | The easy path — pick Toronto, Vancouver, Montreal, Calgary… and go. Every city maps to a map-search box automatically. |
| `search-area` | Realtor.ca province/city result URLs in `searchUrls` | `listing` records | Point at a specific Realtor.ca area page URL you already have |
| `map-bbox` | `mapBoundingBox` (lat/lng box) | `listing` records | Precision geo-search — a single neighbourhood, school catchment, transit corridor, or investment zone |
| `single-listing` | MLS numbers in `searchUrls` (e.g. `X13126780`) | `listing` records | Refresh a known watchlist of MLS numbers (CRM enrichment, alerting, comp pulls) |
| `agent-profile` | Agent profile URLs (e.g. `realtor.ca/agent/2182401/jane-doe`) | `agent` records | Recruit top producers, build broker contact databases, audit competitor agents |
| `office-profile` | Office URLs (e.g. `realtor.ca/office/firm/259003/royal-lepage-team`) | `office` records | Map brokerage market share, audit franchise expansion, build M\&A target lists |

***

### Property Type & Ownership Taxonomy

#### Property types

| Type | Typical building types |
|---|---|
| Single Family | House, Apartment, Row-Townhouse, Mobile Home |
| Multi-Family | Duplex, Triplex, Fourplex, Apartment Building |
| Vacant Land | Lot, Acreage, Farm, Recreational |
| Commercial | Office, Retail, Industrial, Mixed-Use, Hospitality |
| Business | Business-without-property listings |

#### Ownership types (uniquely Canadian)

| Ownership | Where common | Notes |
|---|---|---|
| Freehold | Detached/semi-detached across Canada | Owner holds title to land and building |
| Condominium | ON, AB, NS, NB, PE, MB, SK, NL, QC | Owner holds title to unit + share of common elements |
| Strata | BC, NT, YT, NU | BC's equivalent of condominium (Strata Property Act) |
| Co-operative | ON & QC (older buildings) | Owner holds shares in a corporation that owns the building |
| Leasehold | First Nations land, military bases, some Toronto sites | Time-limited land lease (typically 99 years) |
| Life Lease | Senior housing across ON, MB | Right to occupy for life; capital refunded on exit |

***

### 🔁 Pagination & scale

The **Realtor.ca scraper** paginates automatically — you never manage pages or cursors yourself.

- **Every city you pick becomes a map search.** The city's bounding box is split into a grid of cells (up to 16), and each cell is queried against `PropertySearch_Post` at **200 listings per page**, advancing `CurrentPage` until the box is exhausted (it stops at the API's reported `TotalPages`, on an empty page, or when a page yields no new records).
- **Large areas are handled by the grid**, so Realtor.ca's hard 200-results-per-query cap never silently truncates your results — the geography is subdivided until every cell fits under the cap.
- **De-duplication is built in.** Listings are keyed on `mlsNumber` (falling back to the internal listing ID), so the same property is never emitted twice even when grid cells overlap or multiple cities share inventory.
- **You control scale** with two inputs: `maxItems` (hard cap on total records across all cities/inputs; `0` = unlimited) and `maxPages` (pages per city/cell). Independent city searches run in a small worker pool (`maxConcurrency`, 2–4), so one slow IP never stalls the whole run.
- **Runs always finish cleanly.** A soft deadline winds the run down a safety buffer before the platform timeout, flushing everything gathered so the run ends `SUCCEEDED` rather than being killed mid-page.

In practice a single metro returns **hundreds to thousands** of de-duplicated listings when the `api2.realtor.ca` API host is reachable; multi-city runs scale linearly. Keep `maxItems` small (e.g. 50–100) for a near-free test, then raise it for a full sweep. Realtor.ca's API host is behind **Cloudflare bot-management** whose posture varies run-to-run — if a run ends with 0 records, every proxy IP was challenged on that attempt; re-run and a fresh IP set usually clears. The HTML-based `agent-profile` / `office-profile` / `search-area` modes read the `www.realtor.ca` host directly and are the most reliable surfaces.

***

### Use Cases

#### Canadian Prop-Tech & Real Estate SaaS

Prop-tech founders building Canadian-market apps (CRM, alerting, lead gen, AVM, mortgage prequal) use this dataset to:

- **Bootstrap an MVP listings index** without negotiating CREA DDF / VOW access agreements (which require board membership and months of paperwork)
- **Power consumer-facing search UIs** for niche audiences (luxury, off-grid, multigenerational, accessible housing) that the big portals underserve
- **Train AVM and price-prediction models** on real Canadian inventory with full feature vectors (beds, baths, sqft, condo fees, taxes, neighbourhood)
- **Generate market reports** segmented by city, neighbourhood, building age, ownership type
- **Build school-catchment, commute-time, or transit-overlay tools** by joining listing coordinates to municipal open data
- **A/B test pricing strategies** against live brokerage inventory

#### Investor & Portfolio Research

Canadian REITs, family offices, and individual buy-and-hold investors use Realtor.ca data to:

- **Screen multi-family inventory** (duplex/triplex/fourplex) by price-per-door and cap rate proxies (using `taxAnnualAmount` and rental comps)
- **Track condo pipeline absorption** in Toronto's CityPlace, Liberty Village, and Vancouver's Yaletown and Coal Harbour
- **Identify under-priced freehold inventory** by computing $/sqft vs. neighbourhood median
- **Spot price reductions** by rerunning the same search weekly and diffing
- **Build short-listing pipelines** for property managers and acquisition teams
- **Score new-build vs resale spread** to time entry into a market

#### Immigration & Relocation Buyer Intelligence

Newcomer-focused real-estate teams, relocation consultants, and immigration law firms use this dataset to:

- **Build neighbourhood guides** for clients arriving on PR / work permit visas — cost-of-housing snapshots per family-size cohort
- **Generate "what your budget buys" reports** in Toronto, Vancouver, Calgary, Montreal, Ottawa, Halifax, and Edmonton
- **Connect newcomers with multilingual agents** by joining the dataset on `agentLanguages` (Mandarin, Punjabi, Tagalog, Spanish, Arabic, French, Portuguese, Russian)
- **Track rental availability by neighbourhood** for short-term staging accommodations
- **Surface bilingual French/English inventory** for clients relocating to Quebec or Acadian regions of NB

#### Brokerage Operations & Recruiting

Real-estate brokerages and team leaders use the agent + office endpoints to:

- **Recruit producing agents** by filtering competitor offices by `officeListingCount` and `agentSpecializations`
- **Benchmark franchise market share** (RE/MAX vs. Royal LePage vs. Century 21 vs. Sutton vs. EXP) per city
- **Audit team manager candidates** by triangulating `currentListingCount` across years
- **Build poaching shortlists** of top producers at competing offices
- **Monitor brokerage expansion** as new offices open or franchise affiliations change
- **Generate competitive intelligence dashboards** for franchise leadership

#### Mortgage Brokers & Lender Marketing

Mortgage brokers, lender BDM teams, and fintech underwriters use this dataset to:

- **Time outreach to new buyers** by scraping fresh listings daily and contacting the listing agent within hours
- **Pre-screen properties** for unconventional construction, leasehold land, or non-conforming basement units that affect lendability
- **Generate purchase-price-vs-tax-burden affordability reports** for pre-approval marketing campaigns
- **Identify high-rise condo inventory** in projects with known mortgage-insurance restrictions
- **Cross-sell HELOC and refinance offers** to neighbourhoods showing high turnover and equity appreciation

#### REIT & Institutional Investor Research

Public Canadian REITs (CAPREIT, Boardwalk, InterRent, Killam, RioCan) and institutional investors use the actor for:

- **Acquisition pipeline scouting** — every multi-family >5 unit listing in target metros
- **Tenant-base research** — average asking rents per bedroom count per neighbourhood
- **Submarket cap-rate calibration** — combining tax data with rent comps
- **Disposition timing** — tracking when comparable buildings list and at what price
- **Board reporting** with hard data on competing inventory

#### Valuation, Appraisal & Comp Research

Residential and commercial appraisers, AACI candidates, and mortgage default underwriters use this dataset to:

- **Pull active comps** by lat/lng box, property type, beds/baths, and price range — bypassing slow MLS comp tools
- **Cross-check disclosed sale prices** against initial list prices (when combined with historical archives)
- **Build neighbourhood adjustments grids** with thousands of data points per quarter
- **Reverse-engineer condo building $/sqft trajectories** for refinance and divorce appraisals
- **Support expert witness reports** in expropriation and matrimonial property cases

#### Real Estate Journalism & Data Reporting

Newsrooms covering Canada's housing market — The Globe and Mail, Toronto Star, Vancouver Sun, La Presse, Le Devoir, CBC News, CTV, Global News, Daily Hive, blogTO — use the dataset to:

- **Cover housing affordability stories** with current asking-price data per neighbourhood
- **Investigate foreign-buyer trends** by tracking inventory and language fingerprints
- **Map "ghost listings"** that vanish and reappear at higher prices
- **Produce annual market reports** with metro-level $/sqft tables and YoY change
- **Visualize the rental crisis** with current asking rents in every major city
- **Investigate brokerage misconduct** by joining disciplined-agent registries to active listings

#### Government & Urban Planning Research

Municipal planning departments, CMHC researchers, provincial housing ministries, and academic urban-policy programs use Realtor.ca data for:

- **Measure supply elasticity** — listing volume vs. price by neighbourhood quarter-over-quarter
- **Study missing-middle housing** — duplex/triplex/fourplex availability in restrictive-zoning jurisdictions
- **Benchmark short-term-rental impact** — sudden inventory drops in tourist neighbourhoods (Old Quebec, Banff, Tofino, Mont-Tremblant)
- **Quantify the rental-vs-ownership gap** in priced-out metros (Vancouver, Toronto)
- **Inform zoning reform debates** with hard data on what's actually for sale

#### M\&A and Brokerage Investment Banking

Boutique investment banks advising on brokerage roll-ups and franchise acquisitions use the office-profile endpoint to:

- **Build target lists** of independent brokerages with N+ agents in target metros
- **Quantify pipeline** — total active listings × estimated commission × split ratios
- **Score franchise vs. independent** market share trajectories
- **Identify succession candidates** at brokerages whose principals appear in retirement age cohorts

***

### Sample Queries & Recipes

#### Recipe 1: All for-sale Toronto condos under $700K — first-time buyer feed

```json
{
  "cities": ["toronto"],
  "transactionType": "for-sale",
  "propertyTypeGroup": "residential",
  "priceMax": 700000,
  "bedroomsMin": 1,
  "fetchDetails": true,
  "maxItems": 1000
}
```

Then filter downstream for `buildingType === "Apartment"` to isolate condos.

#### Recipe 2: Vancouver West End rental inventory — landlord-rep CMA pull

```json
{
  "advancedMode": "map-bbox",
  "mapBoundingBox": {
    "latMin": 49.275,
    "latMax": 49.295,
    "lngMin": -123.150,
    "lngMax": -123.120
  },
  "transactionType": "for-rent",
  "language": "en",
  "fetchDetails": true
}
```

#### Recipe 3: Montreal French-language triplex investor target list

```json
{
  "cities": ["montreal"],
  "transactionType": "for-sale",
  "propertyTypeGroup": "residential",
  "bedroomsMin": 5,
  "language": "fr",
  "fetchDetails": true,
  "maxItems": 300
}
```

Then filter downstream for `buildingType === "Triplex"`.

#### Recipe 4: Calgary new listings in the last 7 days — buyer alert feed

```json
{
  "cities": ["calgary"],
  "transactionType": "for-sale",
  "newListingsOnlyDays": 7,
  "fetchDetails": true
}
```

#### Recipe 5: Refresh a watchlist of specific MLS numbers — CRM enrichment

```json
{
  "advancedMode": "single-listing",
  "searchUrls": [
    "X8234567", "W8123450", "C8765432", "E8111111", "N8222222"
  ],
  "fetchDetails": true,
  "fetchAgentProfiles": true,
  "fetchOfficeProfiles": true
}
```

#### Recipe 6: A specific brokerage office — agent recruiting research

```json
{
  "advancedMode": "office-profile",
  "searchUrls": [
    "https://www.realtor.ca/office/firm/259003/re-max-crest-realty-vancouver"
  ]
}
```

#### Recipe 7: Ottawa Westboro single-family $800K-$1.5M with parking and basement — relocation buyer brief

```json
{
  "advancedMode": "map-bbox",
  "mapBoundingBox": {
    "latMin": 45.385,
    "latMax": 45.405,
    "lngMin": -75.760,
    "lngMax": -75.730
  },
  "transactionType": "for-sale",
  "priceMin": 800000,
  "priceMax": 1500000,
  "bedroomsMin": 3,
  "bathroomsMin": 2,
  "fetchDetails": true,
  "fetchAgentProfiles": true
}
```

***

### Integration Examples

#### Google Sheets

Schedule the actor daily in Apify (the built-in Scheduler accepts any cron expression), add the **Apify → Google Sheets** integration on the schedule, and a fresh Realtor.ca inventory sheet lands in your Drive every morning. Add conditional formatting to flag `priceReduced` rows (once you start diffing against your own historical snapshots).

#### Make.com / Zapier / n8n

The official **Apify** connectors on Make, Zapier, and n8n let you trigger downstream workflows on every actor run. Common automations:

- New listings in a saved geography → Slack alert in `#new-toronto-inventory`
- Listings with `priceFrequency === "Monthly"` and a target neighbourhood → push to a rental CRM as fresh leads
- Agent-profile records with email + `currentListingCount > 20` → enqueue into an Outreach.io recruiting sequence
- Listings with `condoFeeMonthly > 1000` → tag as "high-fee" in HubSpot for client warning

#### Power BI, Tableau, Looker, Metabase

Use Apify's REST API as a connected data source and refresh on schedule. Out-of-the-box dashboards:

- Median ask $/sqft by neighbourhood quarter-over-quarter
- Inventory by ownership type per metro
- Brokerage franchise market share by city
- Bilingual inventory share in Quebec / Ottawa / Moncton
- Year-built distribution per neighbourhood (heritage / mid-century / new-build)

#### Postgres / Snowflake / BigQuery

Configure an [Apify webhook](https://docs.apify.com/platform/integrations/webhooks) on the `ACTOR.RUN.SUCCEEDED` event pointing at your warehouse ingestion endpoint. The flat schema deserializes cleanly into a `listings` table keyed on `mlsNumber`, with `agents` and `offices` as side tables. Run the actor multiple times per day to build a slowly-changing-dimension history of asking-price moves.

#### Salesforce / HubSpot / Pipedrive CRM enrichment

Trigger an Apify run hourly during business hours, upsert listing records on `mlsNumber` and agent records on `agentId`. Status changes (active → withdrawn → relisted) and price drops can auto-create Tasks for agents or Cases for sales managers. Brokerage records become Accounts; agents become Contacts hierarchically linked to their Office.

#### Slack & Microsoft Teams alerts

Use the [Apify Slack integration](https://apify.com/integrations/slack) to post new-listing alerts directly to channels — `#toronto-luxury`, `#vancouver-investor`, `#calgary-acreage`, `#halifax-relocations` — formatted with thumbnail, price, address, and a clickable Realtor.ca link.

***

### Major Canadian Markets at a Glance

| Metro | Province | Population (CMA) | Realtor.ca Coverage Notes |
|---|---|---|---|
| Toronto (GTA) | ON | ~6.7M | Highest listing volume nationwide; Yorkville, Liberty Village, Etobicoke, Scarborough, North York, Vaughan, Mississauga, Markham, Oakville, Burlington |
| Montreal | QC | ~4.4M | Bilingual (FR-dominant) inventory; Plateau-Mont-Royal, Westmount, Outremont, NDG, Mile End, Old Montreal, Laval, Longueuil |
| Vancouver | BC | ~2.7M | Strata ownership dominates; West End, Yaletown, Kitsilano, Coal Harbour, Mount Pleasant, Burnaby, Richmond, North Vancouver, Surrey |
| Calgary | AB | ~1.6M | Strong detached + acreage market; Beltline, Mission, Inglewood, Kensington, Bridgeland, plus rural foothills inventory |
| Edmonton | AB | ~1.5M | Older detached stock + new-build sprawl; Oliver, Whyte Avenue, Glenora, Westmount, Sherwood Park |
| Ottawa | ON | ~1.5M | Bilingual; Westboro, Glebe, Hintonburg, Centretown, Manor Park, plus Gatineau (QC) cross-river inventory |
| Quebec City | QC | ~0.85M | Predominantly FR; Old Quebec, Saint-Roch, Limoilou, Sainte-Foy |
| Winnipeg | MB | ~0.85M | Affordable detached + character home stock; Wolseley, Crescentwood, St. Boniface, Osborne Village |
| Hamilton | ON | ~0.8M | GTA-adjacent affordability story; Westdale, Durand, Ainslie Wood |
| Halifax | NS | ~0.5M | East coast capital; South End, North End, Bedford, Dartmouth, Spryfield |
| Victoria | BC | ~0.4M | Premium island market; Oak Bay, Fairfield, James Bay, Saanich |
| St. John's | NL | ~0.21M | Coastal heritage stock |
| Whitehorse / Yellowknife | YT / NT | small | Sparse but covered — useful for resource-sector relocations |

The actor handles **all 10 provinces and 3 territories**. There is no built-in cap on geography — you control scope through `searchUrls` and `mapBoundingBox`.

***

### Cost & Performance

| Metric | Value |
|---|---|
| Engine | HTTP via `impit` (Firefox fingerprint, Cloudflare-clearing) + Cheerio — no headless browser |
| Runtime (100 listings, search-area) | 3-6 minutes (governed by 1500ms delay + concurrency 2) |
| Runtime (1000 listings, search-area, fetchDetails on) | 30-60 minutes |
| Runtime (single MLS lookup) | 5-15 seconds |
| Cost per run | Varies with item count — pay-per-event pricing |
| Pricing model | Pay-per-event (transparent per-record billing) |
| Data freshness | Live at run time — same data Realtor.ca serves to a browser |
| Auth required | None |
| Proxy required | **Yes** — Apify Proxy, `RESIDENTIAL` group, country `CA` |
| Concurrency | 2 default, 4 max — Realtor.ca throttles above |
| Memory footprint | 256 MB baseline; 1024 MB safe for thousand-listing runs with `fetchDetails: true` |

***

### Frequently Asked Questions

#### How fresh is the data?

**Live at run time.** Realtor.ca does not publish a downloadable dataset — the actor pulls the same JSON that powers realtor.ca in your browser, at the moment your run executes. Schedule it hourly, daily, or weekly to maintain a rolling snapshot.

#### How many listings will I get per run?

Depends on geography, filters, and Cloudflare's current posture. When the `api2.realtor.ca` API host is reachable, a single city from the dropdown with no filters can return thousands of listings (each city's bounding box is grid-split and paginated 200 at a time); picking several cities multiplies that, and a tight `map-bbox` over one neighbourhood returns hundreds. The actor honours `maxItems` and `maxPages` to cap your run — set a small `maxItems` for a fast, cheap test and raise it for a full sweep. If a run returns 0 records, Cloudflare challenged every proxy IP for the API host on that attempt — **re-run and a fresh set of residential IPs usually clears** (the actor exits cleanly with a status message rather than erroring). The `agent-profile`, `office-profile`, and `search-area` HTML modes run on the `www.realtor.ca` host, which the Firefox fingerprint clears reliably.

#### Does this actor require a Realtor.ca login?

No. Realtor.ca is publicly browseable and this actor uses the same public endpoints a logged-out user would hit. You only need an Apify account.

#### Why is residential proxy required?

Realtor.ca sits behind **Cloudflare bot-management**, which combines IP-reputation gating with TLS/HTTP fingerprinting. Datacenter IP ranges (AWS, GCP, Azure, DigitalOcean, OVH) are challenged/blocked within a few requests, and a non-browser fingerprint is 403-blocked even from a clean residential IP. The actor uses an `impit` client with a genuine **Firefox** fingerprint from a Canadian residential IP so it reads like a real consumer browser; when an IP is flagged, it rotates to a fresh residential IP and retries. Note: Cloudflare's posture on the `api2.realtor.ca` API host varies — if a run returns 0 records because every IP was challenged, simply re-run; a fresh set of IPs usually clears.

#### Can I run this on the Apify Free Plan?

Yes — small runs work on the Free tier. Realistic production usage (thousands of listings/day with residential proxy) requires a paid Apify plan to cover Proxy and Compute Unit costs.

#### Can I scrape sold or expired listings?

Realtor.ca only exposes **active** listings publicly. Sold inference (`status: sold`, `daysOnMarket`, `priceHistory`) requires cross-run state and is reserved for a future version of this actor — the schema fields are present for forward-compatibility.

#### Does the actor handle French-language listings?

Yes. Quebec inventory is predominantly French; the actor sets `descriptionLang: "fr"` so you can route Quebec records to French NLP pipelines. Filter at extraction time with `language: "fr"`, `language: "en"`, or keep both with `language: "both"` (default).

#### What's the difference between Freehold, Condominium, and Strata?

`Freehold` means you own the land and building outright (typical of detached houses). `Condominium` is the term used outside BC for shared-ownership multi-unit buildings (you own the unit + a share of common elements). `Strata` is BC's equivalent term (BC's Strata Property Act). The actor preserves the exact term Realtor.ca uses.

#### Why is bedroom count in "X + Y" format?

This is a **uniquely Canadian convention**: above-ground bedrooms + basement bedrooms. A `"3 + 1"` listing has 3 main-floor bedrooms and 1 basement bedroom (often non-conforming). The actor decodes this into `bedroomsAbove`, `bedroomsBelow`, and `bedroomsTotal` so downstream code can treat them separately if needed.

#### Are agent emails included?

**No — Realtor.ca does not publish agent emails.** The site hides them behind an "Email a REALTOR®" contact form, and the underlying API only carries internal contact IDs, not real addresses. So `agentEmail` is always `null`. What you **do** get for the listing agent is `agentName`, `agentPhone`, `agentWebsite`, `agentPhoto`, `agentDesignations`, and `agentLanguages`; the brokerage carries `officeName`, `officePhone`, `officeWebsite`, and `brokerageFranchise`. Phone and website are the reliable outreach channels.

#### Can I scrape commercial listings?

Yes. Set `propertyTypeGroup: "commercial"` and Realtor.ca will return office, retail, industrial, mixed-use, hospitality, and business-for-sale inventory.

#### How do I scrape only luxury listings?

Use the `priceMin` filter (e.g. `priceMin: 3000000` for $3M+ in Toronto / Vancouver, `priceMin: 1500000` for most other metros) combined with the city dropdown. For a specific luxury pocket (Yorkville, Forest Hill, Westmount, West Vancouver, Rosedale), switch `advancedMode` to `map-bbox` and draw a tight bounding box around the neighbourhood.

#### How do I avoid blocks?

Stick to the defaults: `maxConcurrency: 2`, `requestDelay: 1500`, `proxyConfiguration` set to CA residential. If you start seeing 403/429s, increase the delay to 2500-3000 and lower concurrency to 1.

#### Can I run multiple parallel jobs?

Yes, but each parallel run draws from the same residential proxy pool. Spreading jobs across geographies (one Toronto, one Vancouver, one Montreal) is safer than running 3 parallel jobs against the same city.

#### Does this scrape `realtor.com` (US)?

No — `realtor.com` is a different site for US inventory. This **Realtor.ca scraper** targets `realtor.ca` (Canada) exclusively. For other markets, see the **Related scrapers** section below.

#### Can I get historical price data for a listing?

V1 returns the current asking price. Building a historical price archive requires running the actor on a schedule and storing each snapshot — Apify retains dataset history indefinitely on most plans, and a planned v2 will surface `priceHistory` directly.

#### What if Realtor.ca changes their API structure?

The actor includes a `debugMode` flag that dumps API request/response shapes for troubleshooting. Schema breaks are typically patched within hours — open an issue on the Apify Store actor page and the developer will push a fix.

#### What export formats are supported?

Apify supports JSON, CSV, Excel (XLSX), HTML, XML, RSS, and JSON-Lines streaming — all from the Dataset tab or REST API.

#### Can I schedule it?

Yes — Apify's built-in Scheduler accepts any cron expression. Common patterns: hourly for active watchlists, every 6 hours for metro snapshots, daily for full provincial sweeps.

#### How is pay-per-event pricing better than a monthly subscription?

You pay only for the records you actually pull. Stop running and your bill stops. Sample with `maxItems: 50` for pennies before committing to a full sweep — see the dedicated pricing section below.

***

### 🔗 Related scrapers

If you need real-estate or classified data from other markets, these sibling actors are built on the same engineering foundation — deep schemas, auto-pagination, and a one-row-per-listing output you can drop straight into a warehouse:

- [Yad2 Scraper (Israel)](https://apify.com/haketa/yad2-scraper) — Israel's #1 classifieds & real-estate portal
- [ZAP Imóveis Scraper (Brazil)](https://apify.com/haketa/zapimoveis-scraper) — Brazil for-sale & for-rent listings
- [Boliga Scraper (Denmark)](https://apify.com/haketa/boliga-scraper) — Danish property listings & sold-price history
- [Domain.com.au Scraper (Australia)](https://apify.com/haketa/domain-com-au-scraper) — Australian real-estate portal
- [ImmobilienScout24 Scraper (Germany)](https://apify.com/haketa/immobilienscout24-scraper) — Germany's largest property marketplace
- [Daft.ie Scraper (Ireland)](https://apify.com/haketa/daft-scraper) — Ireland's leading property site

***

### Comparison vs. Alternatives

| Approach | Setup time | Anti-bot bypass | Schema normalization | Bilingual EN/FR | Agent + Office join | Cost (1K listings) |
|---|---|---|---|---|---|---|
| **This actor** | < 5 minutes | Built-in (CA residential + fresh-IP rotation) | Built-in (X+Y decode, CAD, taxonomies) | Yes | Yes | Pay-per-event, transparent |
| CREA DDF / VOW data feed | Weeks (board membership required) | N/A | DIY | Yes | Yes (separate feeds) | Board dues + dev cost |
| Custom Playwright script | 1-3 weeks | DIY (Cloudflare is hard) | DIY | DIY | DIY | Free + infra + ongoing maintenance |
| Manual copy-paste | Hours per run | N/A | None | Manual | None | Free + brutal labour |
| Generic web scraping APIs | Hours | Often blocked | None | No | No | $50-500+/mo |

***

### Why Pay-Per-Event Pricing?

Most data tools either bill a flat monthly subscription (you pay even when you're not running anything) or per Compute Unit (unpredictable). This actor uses **pay-per-event** pricing, which means:

- You pay only when the actor runs and only for the records it returns
- Costs scale with actual usage — sample 50 records for pennies before committing to a full sweep
- No monthly minimums, no annual contracts, no "seat" fees
- Transparent line-item billing inside the Apify Console
- Free to evaluate — set `maxItems: 20` and the run costs effectively nothing

***

### Support

- **Bug reports:** Use the **Issues** tab on the [Apify Store actor page](https://apify.com/haketa/realtor-ca-scraper)
- **Feature requests:** Same place — please describe your use case, target geography, and expected output
- **Direct contact:** Through the Apify developer profile messaging
- **Schema breaks:** If Realtor.ca changes its layout, open an issue with a `debugMode: true` run log — patches are typically shipped within hours

If this actor saves you time, a **5-star rating** on the Apify Store helps other Canadian real-estate, prop-tech, and investor research teams discover it. Thank you!

***

### 📋 Changelog

| Version | Date | Notes |
|---|---|---|
| **1.2.0** | **2026-11** | **Unlocked 1,000+ MLS pagination & faster default runs:** Changed `maxPages` default from `10` to `0` (`0` = auto/unlimited governed by `maxItems`), removed the 100-item clamp when users explicitly request `maxItems > 100` on default multi-city runs, and set `fetchDetails: false` by default for fast `<5 min` search sweeps while keeping full detail enrichment opt-in. |
| **1.1.0** | **2026-11** | **Enhanced residential session resilience:** Upgraded HTTP transport and sticky-session proxy rotation for high reliability across area pages, agent profiles, and brokerage office profiles. |
| **1.0.0** | **2026-05** | Initial public release — 27-city dropdown + 5 advanced modes (`search-area`, `map-bbox`, `single-listing`, `agent-profile`, `office-profile`), Canadian X+Y bed/bath decoder, EN/FR detection, agent + office enrichment, and pay-per-event pricing. |

# Changelog

This Actor's version history is a separate document: https://apify.com/haketa/realtor-ca-scraper/changelog.md

# Actor input Schema

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

Pick the Canadian cities you want listings from. This is the easiest way to run the scraper — no links or codes required. Choose several to compare markets. Leave empty to automatically pull a fresh mixed sample from popular markets across Canada.

## `transactionType` (type: `string`):

Show properties for sale, for rent, or both.

## `propertyTypeGroup` (type: `string`):

High-level property category. Residential covers houses, condos and townhomes; Commercial covers office, retail, industrial and business.

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

Only keep listings at or above this price. Leave at 0 for no minimum.

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

Only keep listings at or below this price. Leave at 0 for no maximum.

## `bedroomsMin` (type: `integer`):

Only keep listings with at least this many bedrooms. Leave at 0 for no minimum.

## `bathroomsMin` (type: `integer`):

Only keep listings with at least this many bathrooms. Leave at 0 for no minimum.

## `newListingsOnlyDays` (type: `integer`):

Keep only listings first published within the last N days. Great for fresh-lead monitoring. Leave at 0 to keep all.

## `language` (type: `string`):

Filter listings by detected description language. Quebec listings are typically French.

## `fetchDetails` (type: `boolean`):

Visit each listing's detail page for the complete record — full remarks, all photos, features, appliances, heating/cooling, basement, financials. Richer data, slightly slower. When off, summary fields from the search response are extracted quickly.

## `fetchAgentProfiles` (type: `boolean`):

Additionally fetch the full REALTOR agent profile (bio, designations, languages, service areas, listing counts) for each listing's agent. Adds one request per unique agent.

## `fetchOfficeProfiles` (type: `boolean`):

Additionally fetch the brokerage/office profile (franchise, agent count, total listings) for each listing's brokerage. Adds one request per unique office.

## `maxItems` (type: `integer`):

Maximum total records to return across all cities and inputs. Set 0 for unlimited. Tip: keep a limit for fast, low-cost test runs.

## `maxPages` (type: `integer`):

Maximum result pages per city/market (0 = auto/unlimited, governed by Max Results). Each page returns up to ~200 listings.

## `advancedMode` (type: `string`):

For power users only. Leave on 'Cities' to use the city dropdown above. Switch to another mode to paste your own Realtor.ca links or a map bounding box in the Advanced Inputs field below.

## `searchUrls` (type: `array`):

Optional — power users only. Used when 'Advanced Input Mode' is set to one of the Advanced options. Examples — area page: 'https://www.realtor.ca/on/ottawa/real-estate'; single listing: an MLS number like 'X13126780'; agent: 'https://www.realtor.ca/agent/2182401/jane-doe'; brokerage: 'https://www.realtor.ca/office/firm/259003/royal-lepage-team'. Most users can ignore this and just pick cities above.

## `mapBoundingBox` (type: `object`):

Optional — power users only. Used when 'Advanced Input Mode' is 'Map bounding box'. Provide latMin, latMax, lngMin, lngMax in decimal degrees. Example (central Ottawa): { "latMin": 45.30, "latMax": 45.50, "lngMin": -75.85, "lngMax": -75.55 }.

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

Recommended: keep Apify Proxy with the RESIDENTIAL group and country Canada for the most reliable results.

## `requestDelay` (type: `integer`):

Delay between requests in milliseconds. Keep this at 1000+ for smooth, sustained runs.

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

How many requests run in parallel. Keep this low (2-4) for the smoothest results.

## `maxRetries` (type: `integer`):

Retry attempts per failed request.

## `applicationId` (type: `string`):

Advanced: leave empty to use the default. Only change this if results stop coming back and you have a known-good value.

## `debugMode` (type: `boolean`):

Verbose logging for troubleshooting. Leave off for normal runs.

## Actor input object example

```json
{
  "cities": [
    "toronto",
    "vancouver",
    "calgary"
  ],
  "transactionType": "for-sale",
  "propertyTypeGroup": "residential",
  "priceMin": 0,
  "priceMax": 0,
  "bedroomsMin": 0,
  "bathroomsMin": 0,
  "newListingsOnlyDays": 0,
  "language": "both",
  "fetchDetails": false,
  "fetchAgentProfiles": false,
  "fetchOfficeProfiles": false,
  "maxItems": 100,
  "maxPages": 0,
  "advancedMode": "cities",
  "searchUrls": [],
  "mapBoundingBox": {
    "latMin": 45.3,
    "latMax": 45.5,
    "lngMin": -75.85,
    "lngMax": -75.55
  },
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "CA"
  },
  "requestDelay": 1500,
  "maxConcurrency": 2,
  "maxRetries": 4,
  "applicationId": "",
  "debugMode": false
}
```

# Actor output Schema

## `recordType` (type: `string`):

listing (default, blank) / agent / office

## `mlsNumber` (type: `string`):

MLS listing ID (board-prefixed, e.g. X8234567)

## `listingId` (type: `string`):

Realtor.ca internal listing ID

## `source` (type: `string`):

Always realtor.ca

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

Mode that produced this record

## `listingUrl` (type: `string`):

Full Realtor.ca listing URL

## `transactionType` (type: `string`):

For Sale / For Rent

## `status` (type: `string`):

active (v1 — sold inference reserved for future)

## `priceRaw` (type: `string`):

Original price string

## `priceAmount` (type: `string`):

Parsed numeric price in CAD

## `priceCurrency` (type: `string`):

Always CAD

## `priceFrequency` (type: `string`):

Monthly / Yearly / Weekly (rentals)

## `pricePerSqft` (type: `string`):

Price divided by interior size

## `propertyType` (type: `string`):

Single Family / Condo / Vacant Land / Commercial...

## `buildingType` (type: `string`):

House / Apartment / Row-Townhouse / Duplex...

## `ownershipType` (type: `string`):

Freehold / Condominium / Strata / Leasehold / Co-op...

## `bedroomsRaw` (type: `string`):

Original bedroom string (e.g. '3 + 1')

## `bedroomsTotal` (type: `string`):

Total bedrooms (above + below ground)

## `bedroomsAbove` (type: `string`):

Above-ground bedrooms

## `bedroomsBelow` (type: `string`):

Basement bedrooms (may be non-conforming)

## `bathroomsRaw` (type: `string`):

Original bathroom string (e.g. '2 + 1')

## `bathroomsTotal` (type: `string`):

Total bathrooms (full + half×0.5)

## `bathroomsFull` (type: `string`):

Full bathrooms

## `bathroomsHalf` (type: `string`):

Half bathrooms (powder rooms)

## `sizeInteriorRaw` (type: `string`):

Original interior size string

## `sizeInterior` (type: `string`):

Parsed interior square footage

## `lotSizeRaw` (type: `string`):

Lot / land size string

## `sizeFrontage` (type: `string`):

Lot frontage width

## `yearBuilt` (type: `string`):

Construction year

## `parkingType` (type: `string`):

Garage / Carport / Underground / Street...

## `parkingSpaces` (type: `string`):

Number of parking spaces

## `basementType` (type: `string`):

Full / Partial / Crawl Space / None

## `basementFeatures` (type: `string`):

Finished / Walk-out / Separate Entrance...

## `heatingType` (type: `string`):

Forced Air / Baseboard / Heat Pump / Radiant

## `heatingFuel` (type: `string`):

Natural Gas / Electric / Oil / Propane / Wood

## `coolingType` (type: `string`):

Central Air / Wall Unit / None

## `exteriorFinish` (type: `string`):

Brick / Vinyl / Stucco / Wood / Stone

## `roofType` (type: `string`):

Asphalt Shingle / Metal / Tile

## `flooringType` (type: `string`):

Hardwood / Carpet / Tile / Laminate / Vinyl

## `poolType` (type: `string`):

Inground / Above Ground / None

## `waterSource` (type: `string`):

Municipal / Well / Lake

## `sewer` (type: `string`):

Municipal / Septic

## `zoningDescription` (type: `string`):

Zoning description

## `fireplacePresent` (type: `string`):

Whether the building has a fireplace

## `locationDescription` (type: `string`):

Cross-streets / directions to the property

## `viewType` (type: `string`):

View type (water, mountain, city, etc.)

## `waterfrontName` (type: `string`):

Name of the adjacent waterfront, if any

## `addressLine` (type: `string`):

Street address line

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

City name

## `province` (type: `string`):

Province code (ON, QC, BC...)

## `postalCode` (type: `string`):

Canadian postal code (A1A 1A1)

## `latitude` (type: `string`):

Latitude coordinate

## `longitude` (type: `string`):

Longitude coordinate

## `communityName` (type: `string`):

Community / subdivision name

## `neighbourhood` (type: `string`):

Neighbourhood name

## `taxAnnualAmount` (type: `string`):

Annual property tax in CAD

## `taxYear` (type: `string`):

Tax assessment year

## `taxToPrice` (type: `string`):

Annual tax divided by price

## `condoFeeMonthly` (type: `string`):

Monthly condo/strata/maintenance fee in CAD

## `condoFeeIncludes` (type: `string`):

What the condo fee covers (heat/water/...)

## `associationFee` (type: `string`):

HOA-style association fee if present

## `features` (type: `string`):

Property features / amenities list

## `appliances` (type: `string`):

Included appliances list

## `listingDate` (type: `string`):

Date the listing was published

## `lastUpdated` (type: `string`):

Date the listing was last modified

## `daysOnMarket` (type: `string`):

DOM — reserved for future version (needs cross-run state)

## `hasOpenHouse` (type: `string`):

Whether an open house is scheduled

## `openHouses` (type: `string`):

Scheduled open house dates/times

## `photoCount` (type: `string`):

Number of listing photos

## `thumbnailUrl` (type: `string`):

First / primary photo URL

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

All listing photo URLs

## `virtualTourUrl` (type: `string`):

3D / virtual tour link

## `videoUrl` (type: `string`):

Video link if present

## `publicRemarks` (type: `string`):

Public listing description / remarks

## `descriptionLang` (type: `string`):

Detected language: en / fr

## `agentName` (type: `string`):

REALTOR agent name

## `agentId` (type: `string`):

Realtor.ca agent ID

## `agentUrl` (type: `string`):

Agent profile URL (agent-profile mode)

## `agentPhone` (type: `string`):

Agent contact phone

## `agentEmail` (type: `string`):

Agent contact email

## `agentWebsite` (type: `string`):

Agent personal website

## `agentPhoto` (type: `string`):

Agent photo URL

## `agentBio` (type: `string`):

Agent biography (profile modes)

## `agentDesignations` (type: `string`):

Professional designations / certifications

## `agentLanguages` (type: `string`):

Languages the agent speaks

## `agentServiceAreas` (type: `string`):

Geographic service areas (agent-profile mode)

## `agentSpecializations` (type: `string`):

Residential / Commercial / Luxury / Rural

## `currentListingCount` (type: `string`):

Agent's active listing count (agent-profile mode)

## `officeName` (type: `string`):

Brokerage / office name

## `officeId` (type: `string`):

Realtor.ca office firm ID

## `officeUrl` (type: `string`):

Office profile URL (office-profile mode)

## `officeAddress` (type: `string`):

Brokerage address

## `officePhone` (type: `string`):

Brokerage phone

## `officeWebsite` (type: `string`):

Brokerage website

## `officeEmail` (type: `string`):

Brokerage email (office-profile mode)

## `brokerageFranchise` (type: `string`):

Parent franchise (RE/MAX, Royal LePage...)

## `officeAgentCount` (type: `string`):

Agents at the office (office-profile mode)

## `officeListingCount` (type: `string`):

Active listings at the office (office-profile mode)

## `firstSeenAt` (type: `string`):

Intelligence — reserved for future version

## `priceHistory` (type: `string`):

Intelligence — reserved for future version

## `priceChanged` (type: `string`):

Intelligence — reserved for future version

## `priceReduced` (type: `string`):

Intelligence — reserved for future version

## `changePercent` (type: `string`):

Intelligence — reserved for future version

## `hpiBenchmark` (type: `string`):

Intelligence — reserved for future version (CREA MLS HPI)

## `deviationVsHPI` (type: `string`):

Intelligence — reserved for future version

## `marketCondition` (type: `string`):

Intelligence — reserved for future version

## `estimatedStatus` (type: `string`):

Intelligence — sold inference, reserved for future version

## `searchUrl` (type: `string`):

Source search/input URL

## `scrapedAt` (type: `string`):

ISO timestamp of extraction

# 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 = {
    "cities": [
        "toronto",
        "vancouver",
        "calgary"
    ],
    "fetchDetails": false,
    "maxItems": 100,
    "searchUrls": [],
    "mapBoundingBox": {
        "latMin": 45.3,
        "latMax": 45.5,
        "lngMin": -75.85,
        "lngMax": -75.55
    },
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "CA"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("haketa/realtor-ca-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 = {
    "cities": [
        "toronto",
        "vancouver",
        "calgary",
    ],
    "fetchDetails": False,
    "maxItems": 100,
    "searchUrls": [],
    "mapBoundingBox": {
        "latMin": 45.3,
        "latMax": 45.5,
        "lngMin": -75.85,
        "lngMax": -75.55,
    },
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "CA",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("haketa/realtor-ca-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 '{
  "cities": [
    "toronto",
    "vancouver",
    "calgary"
  ],
  "fetchDetails": false,
  "maxItems": 100,
  "searchUrls": [],
  "mapBoundingBox": {
    "latMin": 45.3,
    "latMax": 45.5,
    "lngMin": -75.85,
    "lngMax": -75.55
  },
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "CA"
  }
}' |
apify call haketa/realtor-ca-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,haketa/realtor-ca-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/1Ip8F5uq4R1cje3bq/builds/fLfrqIm5x2qiC1ocY/openapi.json
