# Centris.ca Scraper — Quebec Real Estate Listings (`haketa/centris-scraper`) Actor

Scrape Quebec property listings from Centris.ca by city, type & price. Get address, price, bedrooms, bathrooms, living area, photos, GPS coordinates & broker for houses, condos & rentals — for real estate market research and lead generation.

- **URL**: https://apify.com/haketa/centris-scraper.md
- **Developed by:** [Haketa](https://apify.com/haketa) (community)
- **Categories:** Real estate, Lead generation
- **Features:** Limited permissions
- **Stats:** 8 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.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

## Centris.ca Scraper — Quebec Real Estate Listings, Prices, GPS & Broker Data

> **Extract structured real-estate listings from [Centris.ca](https://www.centris.ca) — the official MLS property portal of Quebec, Canada — without login or coding.** Search across **Montreal, Quebec City, Laval, Gatineau, Longueuil, Sherbrooke, or any Quebec town/borough** and get **price in CAD, full address, property type, bedrooms, bathrooms, GPS coordinates (`latitude`/`longitude`), living area, MLS number, photo count, and listing broker/agency**.
>
> Export clean, de-duplicated Quebec property data to **JSON, CSV, Excel, or API** in seconds.

[![Apify Actor](https://img.shields.io/badge/Apify-Actor-00C853?logo=apify\&logoColor=white)](https://apify.com/haketa/centris-scraper)
[![Market](https://img.shields.io/badge/Market-Quebec%20%28Canada%29%20MLS-007ec6)]()
[![Listings](https://img.shields.io/badge/Data-Prices%20%7C%20GPS%20%7C%20Brokers%20%7C%20MLS-2da44e)]()
[![Scale](https://img.shields.io/badge/Scale-1%2C000%2B%20Listings%20Pagination-8A2BE2)]()
[![No Auth](https://img.shields.io/badge/Authentication-None%20Required-success)]()
[![Export](https://img.shields.io/badge/Export-JSON%20%7C%20CSV%20%7C%20Excel%20%7C%20API-fb8500)]()

> **Built for** real-estate investors, Quebec property analysts, mortgage & appraisal teams, proptech platforms, GIS & mapping specialists, and B2B lead-generation agencies building Quebec broker/courtier contact lists.

***

### ⚡ At a glance

| Feature | Details |
|---|---|
| **Target Portal** | [Centris.ca](https://www.centris.ca) — Official listing system of Quebec's real-estate brokers |
| **Locations Supported** | **30 built-in Quebec cities** (`Montreal`, `Quebec City`, `Laval`, `Gatineau`, `Longueuil`, `Sherbrooke`, `Levis`, `Trois-Rivieres`, `Brossard`, etc.) + **any custom borough or town** via `customLocations` (e.g., `Westmount`, `Outremont`) |
| **Filters** | `sellingType` (`sale` / `rent`), `category` (`residential` / `commercial`), `minPrice` & `maxPrice` (CAD) |
| **Core Card Data (Fast)** | `listingId`, `url`, `propertyType`, `sellingType`, `price`, `address`, `bedrooms`, `bathrooms`, `mainPhoto`, and **exact GPS coordinates (`latitude`, `longitude`)** |
| **Enriched Data (Optional)** | `brokerName`, `brokerFirm` (agency), `description`, `mlsNumber`, `livingArea` (sqft), `photoCount`, and resolved `city` |
| **Pagination & Scale** | Automatic `GetInscriptions` pagination (`20` listings/page) with automatic session refresh — scales cleanly to **1,000+ listings per run** |
| **De-duplication** | Automatic `listingId` de-duplication across all pages and locations (`0 duplicates`) |
| **Export Formats** | JSON, CSV, Excel (XLSX), HTML, XML, RSS, and Apify API |

***

### 🔎 Why scrape Centris.ca?

[Centris.ca](https://www.centris.ca) is the authoritative multiple-listing service (MLS) portal operated by the real-estate boards of Quebec, hosting tens of thousands of active residential and commercial properties for sale and rent across Greater Montreal, Quebec City, the Laurentians, the Eastern Townships, Outaouais, and every region of the province.

While Centris is the richest source of **Quebec real estate data**, it offers **no public API and no CSV export**, and manually clicking through 20-card result pages is impractical when a single city like Montreal has over **11,000 active listings**. This **Centris.ca scraper** automates the entire pipeline: it resolves any city, borough, or municipality via Centris's geographic autocomplete, queries the listing feed with your exact price and category filters, paginates cleanly through **1,000+ properties**, extracts GPS coordinates right from the search cards, and optionally enriches each listing with broker and property details.

#### Common reasons teams use this Centris.ca data extractor:

- 📊 **Quebec housing market research** — track inventory counts, median asking prices, and property-type mix (Houses, Condos, Duplexes, Triplexes, Plexes, Land) across Montreal boroughs and Quebec regions.
- 💰 **Real-estate investment & plex sourcing** — screen multi-family plexes, condos, and single-family homes by price band (`minPrice` / `maxPrice`), room count, and location.
- 🧑‍💼 **Real-estate broker & agency lead generation** — enable `enrichDetails: true` to extract `brokerName` and `brokerFirm` (e.g., `RE/MAX`, `Royal LePage`, `Via Capitale`, `Sutton`) across target cities.
- 🗺️ **GIS mapping & spatial analytics** — every single record includes `latitude` and `longitude` out of the box (even with `enrichDetails: false`), allowing instant map plotting and radius analysis.
- 📈 **Comparative Market Analysis (CMA) & valuation models** — feed live CAD prices, bedroom/bathroom counts, and square footage (`livingArea`) into appraisal and AVM models.
- ⏱️ **Automated listing & price-change monitoring** — schedule daily or weekly runs and compare `listingId` + `price` to detect newly listed properties and price reductions.

***

### ✨ What this Centris.ca scraper does

- ✅ **30 Built-in Quebec Cities (`cities`)** — select `Montreal`, `Quebec City`, `Laval`, `Gatineau`, `Longueuil`, `Sherbrooke`, `Levis`, `Trois-Rivieres`, `Terrebonne`, `Saint-Jean-sur-Richelieu`, `Brossard`, `Repentigny`, `Drummondville`, `Saint-Jerome`, `Granby`, `Blainville`, `Saint-Hyacinthe`, `Mirabel`, `Dollard-des-Ormeaux`, `Rimouski`, `Chateauguay`, `Saint-Eustache`, `Victoriaville`, `Mascouche`, `Salaberry-de-Valleyfield`, `Shawinigan`, `Boucherville`, `Vaudreuil-Dorion`, `Cote-Saint-Luc`, or `Sorel-Tracy`.
- ✅ **Any Custom Borough, Neighbourhood, or Town (`customLocations`)** — type any Quebec locality (e.g., `"Westmount"`, `"Outremont"`, `"Mont-Tremblant"`, `"Bromont"`, `"Kirkland"`) and the scraper automatically resolves its exact Centris geographic level (`GeographicArea`, `GeographicSubArea`, or `CityDistrict`).
- ✅ **Sale vs. Rent & Residential vs. Commercial Filters** — toggle `sellingType` (`sale` or `rent`), `category` (`residential` or `commercial`), and CAD price bounds (`minPrice`, `maxPrice`).
- ✅ **Unlocked 1,000+ Deep Pagination** — with `maxPages: 0` (the default), the scraper steps through 20-listing pages continuously, refreshing session cookies automatically every 25 pages so long 1,000+ listing runs never stall.
- ✅ **Instant Map-Ready GPS Coordinates** — `latitude` and `longitude` are parsed directly from the search cards on 100% of listings without needing slow per-page detail requests.
- ✅ **Optional Detail Page Enrichment (`enrichDetails: true`)** — visits individual listing pages in parallel batches of 5 to extract `brokerName`, `brokerFirm`, full `description`, `mlsNumber`, `livingArea` (sqft), `photoCount`, and parsed `city`.

***

### 🎯 Use cases & B2B workflows

#### 1. Quebec Real-Estate Investors & Plex Buyers

Filter for residential properties in `Montreal`, `Laval`, `Longueuil`, and `Sherbrooke` within your target CAD budget (`minPrice` / `maxPrice`). Filter the resulting dataset by `propertyType` (`Duplex`, `Triplex`, `Quadruplex`, `Quintplex`, `Condo`, `House`) to spot high-yield opportunities immediately.

#### 2. B2B Proptech, Mortgage & Home-Services Lead Generation

Turn on `enrichDetails: true` to build a live roster of active Quebec real-estate brokers (`brokerName`) and brokerages (`brokerFirm`) along with the exact inventory value and neighbourhoods they represent.

#### 3. Geospatial & Retail Site-Selection Teams

Because `latitude`, `longitude`, `price`, `propertyType`, and `address` are populated in fast search-card mode (`enrichDetails: false`), you can harvest **1,000+ geocoded listings in ~1.5 minutes** and import them directly into QGIS, ArcGIS, Mapbox, or Kepler.gl.

#### 4. Commercial Real-Estate & Retail Analysts

Switch `category: "commercial"` to extract commercial buildings, industrial spaces, offices, and retail units for sale or lease across Montreal, Quebec City, Laval, and Gatineau.

***

### 🗺️ Built-in Quebec cities & custom localities

| Region | Supported `cities` Dropdown Values & `customLocations` Examples |
|---|---|
| **Greater Montreal (Island & Laval)** | `Montreal`, `Laval`, `Dollard-des-Ormeaux`, `Cote-Saint-Luc` + custom boroughs (`Westmount`, `Outremont`, `Verdun`, `Plateau-Mont-Royal`, `Saint-Laurent`, `Lasalle`, `Kirkland`, `Pointe-Claire`) |
| **Montérégie (South Shore)** | `Longueuil`, `Brossard`, `Saint-Jean-sur-Richelieu`, `Saint-Hyacinthe`, `Granby`, `Chateauguay`, `Salaberry-de-Valleyfield`, `Boucherville`, `Vaudreuil-Dorion`, `Sorel-Tracy` |
| **Capitale-Nationale & Chaudière-Appalaches** | `Quebec City`, `Levis` + custom localities (`Beauport`, `Sainte-Foy`, `Charlesbourg`) |
| **Lanaudière & Laurentides (North Shore)** | `Terrebonne`, `Repentigny`, `Saint-Jerome`, `Blainville`, `Mirabel`, `Saint-Eustache`, `Mascouche` + custom resort towns (`Mont-Tremblant`, `Sainte-Adele`, `Saint-Sauveur`) |
| **Outaouais, Estrie, Mauricie & Centre-du-Québec** | `Gatineau`, `Sherbrooke`, `Trois-Rivieres`, `Drummondville`, `Victoriaville`, `Shawinigan`, `Rimouski` |

***

### 📥 Input configuration

All fields are optional (`required: []`). Leave `cities` and `customLocations` empty to search across all of Quebec.

| Parameter | Type | Default / Prefill | Description |
|---|---|---|---|
| `cities` | `array<string>` | Prefill: `["Montreal"]` | One or more Quebec cities from the 30-city dropdown. Leave empty to search across all of Quebec. |
| `customLocations` | `array<string>` | `[]` | Any other Quebec city, borough, or municipality not in the dropdown (e.g., `["Westmount", "Outremont", "Mont-Tremblant"]`). |
| `sellingType` | `string` | `"sale"` | Market segment: `"sale"` (For Sale) or `"rent"` (For Rent). |
| `category` | `string` | `"residential"` | Property category: `"residential"` (houses, condos, plexes, land) or `"commercial"`. |
| `minPrice` | `integer\|null` | `null` | Minimum listing price in CAD (optional). |
| `maxPrice` | `integer\|null` | `null` | Maximum listing price in CAD (optional). |
| `enrichDetails` | `boolean` | `false` | Open each listing page to extract `brokerName`, `brokerFirm`, `description`, `mlsNumber`, `livingArea`, `photoCount`, and `city`. Keep `false` for fast sweeps (GPS coordinates are always included). |
| `maxItems` | `integer` | `100` | Maximum total listings to collect across all selected locations (`0` = unlimited). Set `1000`+ for deep market extraction. |
| `maxPages` | `integer` | `0` | Maximum result pages per location (20 listings per page). `0` = automatic pagination governed by `maxItems`. |
| `proxyConfiguration` | `object` | CA Residential | Apify Proxy configuration. **Canadian Residential proxy** (`CA`) is preconfigured by default. |

#### Example 1 — 1,000+ Residential properties for sale in Montreal & Laval

```json
{
  "cities": ["Montreal", "Laval"],
  "sellingType": "sale",
  "category": "residential",
  "enrichDetails": false,
  "maxItems": 1000,
  "maxPages": 0,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "CA"
  }
}
```

#### Example 2 — Enriched broker & property details in Quebec City and Lévis

```json
{
  "cities": ["Quebec City", "Levis"],
  "sellingType": "sale",
  "category": "residential",
  "minPrice": 300000,
  "maxPrice": 800000,
  "enrichDetails": true,
  "maxItems": 150
}
```

#### Example 3 — Custom Montreal boroughs & resort towns (`customLocations`)

```json
{
  "customLocations": ["Westmount", "Outremont", "Mont-Tremblant"],
  "sellingType": "sale",
  "category": "residential",
  "maxItems": 300
}
```

***

### 📤 Output schema & field reference

Every listing is stored as a clean JSON object. Below is a standard fast-mode record (`enrichDetails: false`) with built-in GPS coordinates:

```json
{
  "listingId": "26767752",
  "url": "https://www.centris.ca/en/condos~for-sale~montreal-rosemont-la-petite-patrie/26767752",
  "propertyType": "Condo",
  "sellingType": "sale",
  "price": 378900,
  "address": "2530, Place Michel-Brault, apt. 549 Montréal (Rosemont/La Petite-Patrie)",
  "city": null,
  "bedrooms": 1,
  "bathrooms": 1,
  "livingArea": null,
  "lotArea": null,
  "latitude": 45.60090266,
  "longitude": -73.57170582,
  "brokerName": null,
  "brokerFirm": null,
  "description": null,
  "mlsNumber": null,
  "mainPhoto": "https://mspublic.centris.ca/media.ashx?id=ADDD250DA4AE2B4DDDDDDDDDDE&t=pi&w=320&h=240&sm=c",
  "photoCount": null,
  "scrapedAt": "2026-11-10T02:58:00.000Z"
}
```

With `enrichDetails: true`, the broker, agency, description, MLS number, living area, photo count, and city fields are populated:

```json
{
  "listingId": "24338005",
  "url": "https://www.centris.ca/en/houses~for-sale~quebec-beauport/24338005",
  "propertyType": "House",
  "sellingType": "sale",
  "price": 359900,
  "address": "122, Rue Saint-Théophile Québec (Beauport)",
  "city": "Québec",
  "bedrooms": 2,
  "bathrooms": 2,
  "livingArea": "864",
  "lotArea": null,
  "latitude": 46.88682,
  "longitude": -71.163238,
  "brokerName": "Richard Turmel",
  "brokerFirm": "VIA CAPITALE SÉLECT",
  "description": "Charming well-maintained bungalow offering 2 bedrooms...",
  "mlsNumber": "24338005",
  "mainPhoto": "https://mspublic.centris.ca/media.ashx?id=...",
  "photoCount": 33,
  "scrapedAt": "2026-11-10T02:58:00.000Z"
}
```

#### 🏡 Property Identity, Type & Pricing

| Field | Type | Coverage | Description |
|---|---|---|---|
| `listingId` | `string` | 🟢 High (100%) | Unique Centris listing ID used for de-duplication across pages and cities. |
| `mlsNumber` | `string\|null` | 🟡 Enriched Mode | Official Centris / MLS number (`enrichDetails: true`). |
| `url` | `string` | 🟢 High (100%) | Canonical URL to the property page on Centris.ca. |
| `propertyType` | `string\|null` | 🟢 High (100%) | Normalized property category (`House`, `Condo`, `Duplex`, `Triplex`, `Lot`, `Commercial`, etc.). |
| `sellingType` | `string` | 🟢 High (100%) | `"sale"` or `"rent"`. |
| `price` | `number\|null` | 🟢 High (100%) | Numeric asking price in Canadian Dollars (`CAD`). |

#### 📍 Location, Coordinates & Dimensions

| Field | Type | Coverage | Description |
|---|---|---|---|
| `address` | `string\|null` | 🟢 High (100%) | Full street address, municipality, and borough as displayed on Centris. |
| `city` | `string\|null` | 🟡 Enriched Mode | Parsed municipality / `Ville` name (`enrichDetails: true`). |
| `latitude` | `number\|null` | 🟢 High (100%) | WGS84 GPS latitude extracted directly from the search card. |
| `longitude` | `number\|null` | 🟢 High (100%) | WGS84 GPS longitude extracted directly from the search card. |
| `bedrooms` | `number\|null` | 🟢 High (~96%) | Number of bedrooms (present on residential units; `null` on vacant lots/land). |
| `bathrooms` | `number\|null` | 🟢 High (~98%) | Number of bathrooms / powder rooms. |
| `livingArea` | `string\|null` | 🟡 Enriched Mode | Living / habitable area in square feet (`sqft`) when published (`enrichDetails: true`). |
| `lotArea` | `string\|null` | 🟡 Conditional | Lot area when exposed on the card. |

#### 🧑‍💼 Broker, Media & Run Metadata

| Field | Type | Coverage | Description |
|---|---|---|---|
| `brokerName` | `string\|null` | 🟡 Enriched Mode | Full name of the listing real-estate broker / courtier (`enrichDetails: true`). |
| `brokerFirm` | `string\|null` | 🟡 Enriched Mode | Real-estate agency / brokerage firm name (`enrichDetails: true`). |
| `description` | `string\|null` | 🟡 Enriched Mode | Property description text (`enrichDetails: true`). |
| `mainPhoto` | `string\|null` | 🟢 High (100%) | Primary property image URL. |
| `photoCount` | `number\|null` | 🟡 Enriched Mode | Total number of gallery photos on the listing (`enrichDetails: true`). |
| `scrapedAt` | `string` | 🟢 High (100%) | ISO 8601 UTC timestamp of extraction. |

***

### 🔁 Pagination, scale & 1,000+ listing extraction

Centris paginates search results in blocks of **20 listings per page** (`pageSize: 20`). This scraper is engineered for deep, uninterrupted pagination:

- **Fast 100-item prefill**: Collects 5 pages (`100` listings) in **~12–20 seconds** (`enrichDetails: false`).
- **1,000+ listing sweeps**: Set `maxItems: 1000`+ (and `maxPages: 0`). The actor automatically steps through 50+ pages, synchronizes `page` and `startPosition`, and refreshes session cookies every 25 pages (or immediately if a page response comes back empty) so 1,000+ listing runs complete reliably.
- **Total count awareness**: Before paging a city, the actor queries the total matching count (`listingCount` — e.g., `11,205` active listings in Montreal) and stops cleanly as soon as all available listings or `maxItems` have been collected.

***

### ⚙️ How to use

1. **Open the Actor** in the Apify Console.
2. **Choose your locations**: select one or more cities from `cities` (or type custom boroughs/towns into `customLocations`).
3. **Set filters**: pick `sellingType` (`sale` / `rent`), `category` (`residential` / `commercial`), and optional `minPrice` / `maxPrice`.
4. **Configure volume & detail**: set `maxItems` (e.g., `100` for a quick test, `1000`+ for bulk collection) and toggle `enrichDetails: true` if you need broker names and descriptions.
5. **Click Start** and export your results to **JSON, CSV, Excel, or API**.

#### API Example (Python)

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run = client.actor("haketa/centris-scraper").call(run_input={
    "cities": ["Montreal", "Quebec City"],
    "sellingType": "sale",
    "category": "residential",
    "enrichDetails": False,
    "maxItems": 1000,
    "maxPages": 0,
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(f"{item['listingId']} | ${item['price']} | {item['propertyType']} | {item['address']} | ({item['latitude']}, {item['longitude']})")
```

#### API Example (Node.js)

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

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

const run = await client.actor('haketa/centris-scraper').call({
    cities: ['Laval', 'Longueuil'],
    sellingType: 'sale',
    enrichDetails: true,
    maxItems: 100,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(`Collected ${items.length} enriched Centris listings`);
```

#### API Example (cURL)

```bash
curl -X POST "https://api.apify.com/v2/acts/haketa~centris-scraper/runs?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cities": ["Montreal"],
    "sellingType": "sale",
    "category": "residential",
    "enrichDetails": false,
    "maxItems": 1000,
    "maxPages": 0
  }'
```

***

### 🔄 Automation & integration workflows

- **Google Sheets / Airtable CMA Trackers**: Sync Quebec listings (`price`, `propertyType`, `address`, `bedrooms`, `bathrooms`, `livingArea`, `url`) directly into valuation spreadsheets.
- **Make.com / Zapier / n8n Deal Alerts**: Run scheduled searches for plexes or condos in your target price range and notify your acquisitions team via Slack or email whenever new `listingId` records appear.
- **CRM & Broker Outreach (HubSpot / Salesforce / Pipedrive)**: Run with `enrichDetails: true` to populate `brokerName`, `brokerFirm`, `mlsNumber`, and `city` for B2B real-estate marketing.

***

### 💡 Pro tips for best results

- **Keep `enrichDetails: false` for fast 1,000+ listing sweeps**: Because `price`, `address`, `propertyType`, `bedrooms`, `bathrooms`, `mainPhoto`, and `latitude`/`longitude` are all extracted directly from the search feed, you can collect 1,000+ map-ready listings in under 2 minutes without visiting individual property pages.
- **Segment very large markets by price band**: Montreal alone has over 11,000 active residential listings. For exhaustive city-wide harvesting, segment runs using `minPrice` and `maxPrice` bands (e.g., `$0–$500k`, `$500k–$1M`, `$1M+`).
- **Keep Canadian Residential proxy enabled**: Centris sits behind Cloudflare and works best with the preconfigured Canadian (`CA`) Residential proxy.

***

### ⚠️ Honest limitations

- **Enrichment speed**: Turning on `enrichDetails: true` fetches each property page individually (in parallel batches of 5). For enriched runs, keep `maxItems` between `50` and `300` so runs finish quickly and residential proxy bandwidth stays low.
- **Vacant land & commercial listings**: Land/lot listings (`propertyType: "Lot"`) naturally have `bedrooms: null` and `bathrooms: null`.

***

### ❓ FAQ

#### Do I need a Centris.ca account or API key?

No. All listing data is extracted from publicly visible Centris search and property pages with no login required.

#### Are GPS coordinates included without enrichment?

Yes! Both `latitude` and `longitude` are extracted directly from the search cards on 100% of records even when `enrichDetails: false`.

#### How does the scraper handle Quebec City vs. Montreal geographic types?

Centris uses different internal geographic types for different municipalities (`GeographicArea` for Montreal Island, `GeographicSubArea` for Quebec City, `CityDistrict` for boroughs). This scraper dynamically inspects the autocomplete `Type` and `TypeId` for every location so both cities and neighbourhoods return accurate counts and results.

#### How many listings can I extract in a single run?

With `maxPages: 0` (auto up to 1,000 pages / 20,000 listings per location), you can extract **1,000+ listings** in a single run governed by your `maxItems` parameter.

***

### 🔗 Related scrapers

Building a Canadian or international real-estate pipeline? Pair **Centris.ca Scraper** with these related actors by **haketa**:

- [Realtor.ca Scraper](https://apify.com/haketa/realtor-ca-scraper) — Canada-wide MLS listings, prices, and real estate agents.
- [Kijiji.ca Scraper](https://apify.com/haketa/kijiji-scraper) — Canadian classifieds, apartment rentals, and real estate listings.
- [Property24 Scraper](https://apify.com/haketa/property24-scraper) — South Africa real estate listings and prices.
- [ZAP Imóveis Scraper](https://apify.com/haketa/zapimoveis-scraper) — Brazil real estate portal listings.

***

### 📋 Changelog

#### v1.2 (2026-11)

- **Unlocked 1,000+ Deep Pagination**: Raised default page cap to 1,000 pages (`maxPages: 0` = auto governed by `maxItems`), added `startPosition` synchronization, and added automatic session/cookie refresh every 25 pages (and on empty responses) so 1,000+ listing runs paginate cleanly without stalling.
- **Empty-Location Explicit `maxItems` Fix**: Honored explicit user `maxItems > 100` even when `cities` is left empty (all-Quebec search).
- **Documentation Upgrade**: Added `⚡ At a glance` table, coverage indicators, and bulk Python/Node.js API examples.

#### v1.1 (2026-10)

- Added direct search-card GPS coordinate extraction (`latitude`, `longitude`) so map coordinates are returned on 100% of records without enabling `enrichDetails`.
- Fixed dynamic `GeographicSubArea` / `CityDistrict` query field mapping for Quebec City and custom boroughs.

#### v1.0 (2026-09)

- Initial release with 30 Quebec cities, custom location autocomplete, sale/rent and residential/commercial filters, and optional broker/detail enrichment.

# Changelog

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

# Actor input Schema

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

Pick one or more Quebec cities or regions to search. Leave empty to search across all of Quebec.

## `customLocations` (type: `array`):

Optional. Add any other Quebec city or neighbourhood name not in the list above, e.g. "Westmount", "Outremont", "Kirkland". Each is matched to a Centris search area.

## `sellingType` (type: `string`):

Whether to scrape properties listed for sale or for rent.

## `category` (type: `string`):

Choose residential (houses, condos, plexes) or commercial properties.

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

Only return listings at or above this price in CAD. Leave empty for no minimum.

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

Only return listings at or below this price in CAD. Leave empty for no maximum.

## `enrichDetails` (type: `boolean`):

Visit each listing page for extra data (full description, GPS coordinates, broker, extra features and all photos). Slower but richer.

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

Maximum number of listings to return across all selected locations. Set 0 for no limit.

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

Maximum result pages to paginate per location (20 listings per page). Set 0 for automatic pagination governed by maxItems.

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

Apify Proxy settings. Canadian residential is recommended and set by default.

## Actor input object example

```json
{
  "cities": [
    "Montreal"
  ],
  "sellingType": "sale",
  "category": "residential",
  "enrichDetails": false,
  "maxItems": 100,
  "maxPages": 0,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "CA"
  }
}
```

# Actor output Schema

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

Centris listing number

## `url` (type: `string`):

Listing page URL

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

Property type (House, Condo, ...)

## `sellingType` (type: `string`):

For sale or for rent

## `price` (type: `string`):

Listing price (CAD)

## `address` (type: `string`):

Street address

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

City / borough

## `bedrooms` (type: `string`):

Number of bedrooms

## `bathrooms` (type: `string`):

Number of bathrooms

## `livingArea` (type: `string`):

Living area (sqft)

## `lotArea` (type: `string`):

Lot area (sqft)

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

GPS latitude (listing enrichment)

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

GPS longitude (listing enrichment)

## `brokerName` (type: `string`):

Listing broker (enrichment)

## `brokerFirm` (type: `string`):

Broker agency (enrichment)

## `description` (type: `string`):

Listing description (enrichment)

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

Centris / MLS number (enrichment)

## `mainPhoto` (type: `string`):

Main photo URL

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

Number of photos

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

Extraction timestamp

# 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": [
        "Montreal"
    ],
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "CA"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("haketa/centris-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": ["Montreal"],
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "CA",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("haketa/centris-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": [
    "Montreal"
  ],
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "CA"
  }
}' |
apify call haketa/centris-scraper --silent --output-dataset

```

## MCP server setup

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