# DuProprio Quebec FSBO New Listings Alert (`superslowsloth/duproprio-listings`) Actor

Newest for-sale-by-owner homes, condos, lots and farms on DuProprio (Quebec) from a search URL or filters, one flat row each: price in CAD, address, city, bedrooms, bathrooms, living area, lot, tags, open house, photo, coordinates.

- **URL**: https://apify.com/superslowsloth/duproprio-listings.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** Real estate, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.10 / 1,000 listing scrapeds

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

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

## What's an Apify Actor?

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

## DuProprio Quebec FSBO New Listings Alert

Read [DuProprio](https://duproprio.com), Quebec's for-sale-by-owner real estate
site, and get one flat row per property: price in Canadian dollars, address,
city, region, property type, bedrooms, bathrooms, living area, lot area, the
site's badges, open-house day, photo and coordinates. Newest first by default,
so a scheduled run is a **Quebec FSBO new-listing alert**: you give it a search
(a URL copied from the site, or a few filters) and it returns the latest homes,
condos, cottages, multiplexes, lots, farms and commercial properties that match.

No account, no API key, no login, no browser. Each page of results holds 11
properties.

### Input

| Field | Notes |
|---|---|
| `searchUrls` | The most flexible input. Open duproprio.com in English, set any filters you like, sort by "New listings first" and copy the address of the results list (`https://duproprio.com/en/search/list?...`). One URL per line. Each is walked on its own. Paging parameters are replaced; every filter in the URL is kept. |
| `regions` | Quebec regions to search (21, from Abitibi-Témiscamingue to Saguenay-Lac-Saint-Jean, with Montréal / l'Île and Laval). Several allowed. Empty means the whole province. |
| `cityIds` | DuProprio city ids, for a search narrower than a region (the number in `cities[]=` in a search URL). Cannot be combined with `regions`: the site ignores cities whenever a region is set. |
| `propertyTypes` | `house`, `condo`, `cottage`, `multiplex`, `lot`, `commercial`, `farm`. Empty means all. |
| `minPrice`, `maxPrice` | Asking-price band in Canadian dollars, inclusive, either end optional. |
| `minBedrooms`, `minBathrooms` | At least this many (1 to 5; 5 means 5 or more). |
| `listedWithin` | `any`, `2`, `7` or `30` days: the site's own "online for" filter. |
| `openHouseOnly` | Only properties with an open house scheduled. |
| `sort` | `newest` (default), `oldest`, `price-asc`, `price-desc`. A pasted URL that already has a sort keeps it. |
| `maxItems` | Listings to read per search (default 100, up to 10,000). |
| `proxyConfiguration` | Apify proxy by default. |

The filters are laid over each search URL: set `maxPrice` and every URL you
pasted gets that ceiling. With no URL, the filters build the search. With
neither, you get the newest properties in Quebec.

#### Using it as a new-listing alert

The result cards carry **no publication date**, so "new" is a comparison, not a
column. Run it on a schedule with `sort: newest`, keep the `listing_id`s you
have already seen, and treat the ids you have not as new. Newest first means a
small `maxItems` (one or two pages) is enough for a daily run.

### Output

| Field | Notes |
|---|---|
| `search_url` | The search that first found the row, as the results-page URL requested. |
| `listing_id`, `url` | DuProprio's id and the absolute `https://duproprio.com/en/...` link. |
| `address`, `city` | As the card prints them; `city` carries the borough in brackets where there is one (`Longueuil (St-Hubert)`). Address is null when the seller hides it. |
| `region_slug`, `city_slug` | The region and city as they appear in the listing URL (`laurentides`, `ste-adele`). |
| `property_type` | The type in the listing URL: `home`, `bungalow`, `condo`, `semi-detached`, `townhouse`, `mobile-home`, `duplex`, `triplex`, `lot`, `farm`, `commercial-property`, ... |
| `price_cad`, `price_text` | The asking price as a whole number, and exactly as printed. Null price and `Price upon request` text when no amount is shown. |
| `bedrooms` | Null when the card shows none (lots, commercial). |
| `bathrooms`, `half_bathrooms` | From the card's "Bathrooms + Half baths" figure: `1 + 1` is 1 and 1. A single number is `bathrooms` only; `half_bathrooms` is then null, not 0. |
| `living_area_sqft`, `lot_area_sqft` | Square feet, whole numbers, as the site prints them. Null when not shown. |
| `intro` | The card's one-line description, cut short by the site with `...`. |
| `tags` | The card's plain badges in order: `No Agents`, `Waterfront`, `Available Now`, `Price Taxes Not Included`, `With garage`, ... |
| `open_house` | The open-house badge as text (`Sunday October 11th`). The site prints no year, so it is not a date. |
| `has_3d_tour` | True for the "3D Virtual Tour" badge. |
| `is_featured`, `newly_built`, `builder_id` | DuProprio's flags: promoted card, filed under "newly built", and the builder's id. |
| `image_url` | The main photo (600 px). Nothing is downloaded. |
| `latitude`, `longitude` | From the results page's structured data; the point DuProprio places the listing at. |
| `page`, `position` | The result page (1-based) and place on it. |

#### Honest nulls

A field the card does not print is `null`, never `0` or `""`. A lot has no
bedrooms; a condo has no lot size; a seller can leave the price out. Prices are
taken as printed: sellers occasionally enter a placeholder (a lot listed at `$6`
was seen), and the row carries it.

#### Personal data

Cards show a property's street address and an intro the seller wrote. They are
carried as DuProprio publishes them; nothing is looked up, and no seller name,
phone number or e-mail is on the results cards or collected.

### Pricing

Pay per event:

- **$0.002** per run (`actor-start`), charged only after your input is
  validated, so a run that fails on bad input costs nothing.
- **$0.0021** per listing row written (`listing-scraped`), i.e. $2.10 per
  1,000 listings.

### How it fetches

It reads the server-rendered results list at `duproprio.com/en/search/list`
(about 1.4 MB per page, 11 properties) and parses the property cards and the
page's structured data. Measured 2026-10-11 from a plain connection: HTTP 200
on every page, and from an Apify datacenter address: pages 1 and 2 both came
back with their 11 cards. A 403, 429, 5xx, or a 200 that is neither a results page nor the
site's "No property found" page is treated as a refusal: the actor rotates to a
fresh address and retries.

Things worth knowing, all measured:

- **The site ignores a filter value it does not understand.** An unknown
  region, a text price or a made-up sort comes back as the unfiltered list of
  all ~7,900 properties with HTTP 200. The actor therefore checks every filter
  against what the site offers before sending it, and fails the run before
  charging anything when one is wrong. A search URL you paste is sent as it is;
  check the first rows if you edited it by hand.
- **A page past the end redirects to the start.** Asking for page 5 of a
  3-page search returns the unfiltered first page. The actor reads the page
  number the response says it is and stops there, so it never writes the wrong
  list as results. A walk ends on the site's own "no next page".
- **The whole province is about 7,900 properties on 719 pages** and every page
  of it was served; there is no hidden serving limit below that.
- **`search=true` is what makes the site apply a filter.** The actor always
  sends it, including on a pasted URL that lacks it.
- **Cities need ids and exclude regions.** `cities[]` filters on its own, but is
  ignored when a region is also set, so the two inputs cannot be combined.
- **Counts move.** The "properties found" figure changes between requests as
  listings are added and sold; a listing seen twice is returned once.
- **Promoted builder cards** (`is_featured`) are real listings with their own
  ids, pinned into pages of the list; they are rows like any other.

# Actor input Schema

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

Results-page addresses copied from duproprio.com: open the English site, set any filters you like (region, cities, type, price, bedrooms, more criteria), sort by 'New listings first' and copy the address of the results list (https://duproprio.com/en/search/list?...). One URL per line. Each is walked on its own and the filters below are laid over it. Leave empty to build the search from the filters below.

## `regions` (type: `array`):

Only properties in these Quebec regions. Leave empty for the whole province. Cannot be combined with City ids, because DuProprio ignores the cities whenever a region is set.

## `cityIds` (type: `array`):

DuProprio city ids, whole numbers, for a search narrower than a region. Find a city's id in the address of a search you build on duproprio.com: it is the number in cities\[]=... (cities%5B%5D=...). One id per line. Cannot be combined with Regions.

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

Only these kinds of property. Leave empty for all of them. 'Lot' also includes the site's land categories such as arable land.

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

Lowest asking price to return, in Canadian dollars, inclusive. Leave empty for no lower bound.

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

Highest asking price to return, in Canadian dollars, inclusive. Leave empty for no upper bound.

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

Only properties with at least this many bedrooms, 1 to 5 (5 means 5 or more). Leave empty or 0 for no limit.

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

Only properties with at least this many bathrooms, 1 to 5 (5 means 5 or more). Leave empty or 0 for no limit.

## `listedWithin` (type: `string`):

Only properties DuProprio shows as online for at most this many days. DuProprio offers 2, 7 and 30 days. Useful with a daily schedule to keep a run's rows to what is new, though the id comparison in your own workflow stays the exact test.

## `openHouseOnly` (type: `boolean`):

Only properties with an open house scheduled. DuProprio shows the day as text on the card ('Sunday October 11th'), carried in the open_house column.

## `sort` (type: `string`):

The order DuProprio lists the results in. 'Newest first' is DuProprio's 'New listings first' and is the one for new-listing alerts. A pasted search URL that already has its own sort keeps it.

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

Read at most this many listings for each search (a search URL, or the filters below when no URL is given). DuProprio shows 11 per page. The whole of Quebec for sale is about 7,900 listings, so values above 10,000 are not accepted.

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

Apify proxy (automatic datacenter exits) is the default. A refused request (403, 429, 5xx, or a page that is not a results page) is retried from a fresh address, which is what the proxy is for.

## Actor input object example

```json
{
  "regions": [
    "6"
  ],
  "listedWithin": "any",
  "openHouseOnly": false,
  "sort": "newest",
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

No description

# 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 = {
    "regions": [
        "6"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/duproprio-listings").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 = {
    "regions": ["6"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/duproprio-listings").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 '{
  "regions": [
    "6"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call superslowsloth/duproprio-listings --silent --output-dataset

```

## MCP server setup

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

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/4X924yz8ovG6cCNcs/builds/oAv6dzFDSm9cgUE4h/openapi.json
