# Immoweb.be Scraper - Properties, Prices & 70+ Fields (`abotapi/immoweb-scraper`) Actor

From $1/1K. Extract property listings from immoweb.be. Get price, location, coordinates, images, and 27 structured fields per listing. Enable detail page fetching for 70+ additional fields, including description, EPC score, building details, energy features, and agent contact.

- **URL**: https://apify.com/abotapi/immoweb-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Real estate, Automation, Lead generation
- **Features:** Limited permissions
- **Stats:** 13 total users, 2 monthly users, 52.8% runs succeeded, 2 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
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

## Immoweb Scraper: Belgian Property Listings, Prices & Agent Details

Immoweb Scraper turns Immoweb into structured Belgian real estate data. Search by filters (transaction type, location, price, bedrooms, area, building condition, EPC grade, amenities) or paste immoweb.be search result URLs directly, and get 20+ core fields per listing: price, full address, coordinates and photos. Switch on detail pages for 70+ additional fields, including description, EPC score, building details, energy features and agent contact. Export to JSON, CSV or Excel, or pull results through the API. Incremental mode tracks a recurring search and returns only what changed.

### Why This Scraper?

- **Two ways to search.** Fill in filter fields (transaction type, property type, postal codes, price, bedrooms, area, building condition, EPC grade, amenities) or paste immoweb.be search URLs you already refined in your browser.
- **Every property type and transaction.** Houses, apartments, land, parking, offices, industry, commercial, apartment blocks, garages and new-build projects, for sale or for rent.
- **Full Belgian location data.** Street, number, postal code, locality, district, province, region and coordinates on every listing.
- **Optional deep enrichment.** Turn on detail pages for 70+ extra fields per listing (description, EPC score, building and energy details, agent contact, amenities), only when you need them.
- **Built for monitoring.** Incremental mode returns only new and changed listings on a recurring search, and a resume option continues an interrupted run instead of re-collecting what you already have.
- **Automatic pagination and deduplication.** The walk stops on its own once results run out, and no listing is returned twice across pages or URLs.

### Use Cases

- **Property portals and aggregators:** mirror fresh Belgian listings by postal code, region or property type.
- **Investors and analysts:** track price per square meter, building condition and EPC ratings across a market segment.
- **Agencies and lead generation:** collect agent and agency contact details from the listings you search. The default search covers houses and apartments; garages, offices, commercial and new-build projects must be selected under Property types.
- **Market monitoring:** schedule a recurring search and get only new, updated and expired listings with incremental mode.
- **Real estate research:** build historical datasets of prices, locations and property attributes.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `id` | `12345678` |
| `url` | `"https://www.immoweb.be/en/classified/12345678"` |
| `title` | `"Charming villa with garden"` |
| `transactionType` | `"FOR_SALE"` |
| `propertyType` | `"HOUSE"` |
| `propertySubtype` | `"VILLA"` |
| `price` | `425000` |
| `displayPrice` | `"€425,000"` |
| `price_per_sqm` | `2125` |
| `bedrooms` / `rooms` | `4` / `7` |
| `livingArea` | `200` |
| `landArea` | `850` |
| `street` / `number` | `"Rue de la Gare"` / `"15"` |
| `postalCode` | `"1000"` |
| `locality` / `district` | `"Bruxelles"` / `"Brussels"` |
| `province` / `region` | `"Brussels"` / `"Brussels"` |
| `latitude` / `longitude` | `50.8503` / `4.3517` |
| `images` / `imageCount` | array of photo URLs / `12` |
| `salePrice` | `425000` |
| `lastModified` | `"2026-04-15T10:30:00.000Z"` |
| `customerName` | `"Example Agency"` |

With Fetch Detail Pages on, each listing also carries `description` (English), `descriptionFr` and `descriptionNl`, building details (`building_constructionYear`, `building_condition`, `building_facadeCount`, `building_floorCount`), energy fields (`epcScore`, `energy_heatingType`, `energy_hasHeatPump`, `energy_hasPhotovoltaicPanels`, `energy_hasDoubleGlazing`), interior counts (`bathrooms`, `toiletCount`, `showerRoomCount`, `kitchen_type`), garden and terrace details (`gardenSurface`, `terraceSurface`, `hasGarden`, `hasTerrace`, `hasSwimmingPool`), amenities (`hasLift`, `hasAirConditioning`, `hasJacuzzi`, `hasSauna`, `hasWorkspace`, `hasDisabledAccess`), transaction details (`sale_cadastralIncome`, `availabilityPeriod`, `sale_isSubjectToVat`), agent fields (`agencyName`, `agencyId`, `agencyPhone`, `agencyEmail`, `customerType`), status flags (`flag_isNewlyBuilt`, `flag_isPublicSale`, `flag_isSoldOrRented`), view and bookmark counters (`stat_viewCount`, `stat_bookmarkCount`), publication dates (`publication_creationDate`, `publication_expirationDate`), rental terms as `rental_*` fields on rentals, and permit and land fields such as `permit_floodZoneType`, `permit_hasPlotDivisionAuthorization` and `land_latestUseDesignation` where Immoweb provides them. Which fields appear depends on the property type, since the actor flattens whatever Immoweb's own detail page returns for that listing (nested sections keep a prefix such as `building_`, `energy_`, `sale_`, `rental_`, `flag_`). In Incremental mode, every returned record also carries `changeType` (`NEW`, `UPDATED`, `UNCHANGED`, `REAPPEARED` or `EXPIRED`), `changedFields`, `firstSeenAt` and `lastSeenAt`.

#### Output views and run summary

The Output tab offers four views of the same rows: Overview, Detail (extra attributes when Fetch details is on), Agents and agencies (agency name, id, phone, email) and Changes (change type, changed fields, first and last seen, for incremental runs). Each run also stores an `OUTPUT` record in the key-value store with, per start URL, the pages walked, rows returned and whether the end of results was reached.

### How to Use

1. Pick a **mode**: Search mode (use the filter fields) or URL mode (paste one or more immoweb.be search result URLs already filtered in your browser).
2. In Search mode, set Transaction Type, Property Types, Postal Codes, and any of the price, bedroom, area, building condition, EPC grade or amenity filters you need.
3. Turn on **Fetch Detail Pages** if you want the 70+ extra fields per listing; leave it off for search result data only.
4. Set **Maximum Listings** (and, optionally, **Maximum Pages per URL**) to control run size and cost, then click **Start**.

**Search Brussels houses for sale:**

```json
{
  "mode": "search",
  "transactionType": "for-sale",
  "propertyTypes": ["house"],
  "postalCodes": ["1000"],
  "maxItems": 20
}
```

**Search with price, amenity and EPC filters, enriched:**

```json
{
  "mode": "search",
  "transactionType": "for-sale",
  "propertyTypes": ["apartment"],
  "priceMax": 350000,
  "bedroomsMin": 2,
  "hasTerrace": true,
  "epcGradeMax": "C",
  "fetchDetails": true,
  "maxItems": 50
}
```

#### URL shapes

URL mode accepts these shapes; each is routed automatically. With the `urls` field untouched, one example per shape is scraped.

| Shape | Example path | Result |
|---|---|---|
| Search (English) | `/en/search/house/for-sale?countries=BE` | listings, paginated |
| Search page N | `...&page=2` | listings from page N onward (partial scope) |
| Filtered and sorted search | `/en/search/house-and-apartment/for-rent?postalCodes=BE-1000&minPrice=500&orderBy=cheapest` | listings |
| Dutch search | `/nl/zoeken/huis/te-koop` | listings |
| French search | `/fr/recherche/maison/a-vendre` | listings |
| Location path | `/en/search/apartment/for-sale/brussels/province` or `.../antwerpen/2000` | listings |
| New projects | `/en/search/new-real-estate-project-houses/for-sale` | listings |
| Listing page | `/en/classified/apartment/for-sale/aalst-9300/9300/21925957` or `/en/classified/21925957` | one row from the detail page |
| Agency page | `/en/agency/immo-bastogne-sa/318779` | that agency's listings: houses and apartments for sale and for rent, and land for sale (new-project listings are not included) |

**Paste a search URL directly:**

```json
{
  "mode": "url",
  "urls": [
    "https://www.immoweb.be/en/search/house/for-sale?countries=BE&postalCodes=1000&page=1&orderBy=newest"
  ],
  "maxItems": 100
}
```

**Recurring monitoring of a rental search:**

```json
{
  "mode": "search",
  "transactionType": "for-rent",
  "propertyTypes": ["apartment"],
  "postalCodes": ["2000"],
  "incrementalMode": true,
  "stateKey": "antwerp-rentals",
  "maxItems": 0
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/immoweb-scraper").call(run_input={
    "mode": "search",
    "transactionType": "for-sale",
    "propertyTypes": ["house"],
    "postalCodes": ["1000"],
    "maxItems": 20,
})
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(listing.get("title"), listing.get("displayPrice"))
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/immoweb-scraper').call({
  mode: 'search',
  transactionType: 'for-sale',
  propertyTypes: ['house'],
  postalCodes: ['1000'],
  maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Pagination and limits

Both modes forward-walk search result pages (about 30 listings each) until one of three things happens: **Maximum Listings** is reached, Immoweb's own reported total for that search is reached, or a page returns no new listings. **Maximum Pages per URL** is an optional extra safety bound on top of that; leave it empty to let the walk run its course.

#### Resume and recurring updates

- **Resume from a previous run** (`resumeFromRunId`): paste a previous run id or dataset id from this account to collect only the listings that run doesn't already have. Combine both runs' datasets for the full set. Long runs also checkpoint their own progress, so a platform migration or a Resurrect of a failed run picks up where it left off instead of starting over.
- **Incremental mode** (`incrementalMode`): for scheduling this actor on the SAME search repeatedly. The first run returns everything as `NEW`. Later runs compare against the stored state and mark each listing `NEW`, `UPDATED` (with `changedFields`), `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on), or `EXPIRED` (only once a run has proven it scanned the whole tracked search to completion, and only with `emitExpired` on). `REAPPEARED` only appears for a listing that was previously marked `EXPIRED` that way and is then found again in a later run, so it needs at least one prior complete scan with `emitExpired` on before it can ever show up. `stateKey` names or shares a monitoring baseline; leave it empty and one is derived from your search filters automatically. With incremental mode off, the output is exactly as before.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape, the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each listing, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (use filter fields) or `url` (paste immoweb.be search result URLs). |
| `transactionType` | string | `for-sale` | `for-sale` or `for-rent`. Search mode only. |
| `propertyTypes` | array | none (houses and apartments) | House, apartment, house or apartment, land, parking, office, industry, commercial, apartment block, garage, or new-build houses/apartments. Leave empty to search houses and apartments; select other types explicitly. |
| `postalCodes` | array | none (all of Belgium) | Belgian 4-digit postal codes to search in. |
| `countries` | array | `["BE"]` | Countries to include; Immoweb also lists properties in neighboring regions. |
| `orderBy` | string | `newest` | `relevance`, `newest`, `cheapest`, `most-expensive`, or `postal-code`. |
| `priceMin` | integer | none | Minimum property price in EUR. |
| `priceMax` | integer | none | Maximum property price in EUR. |
| `bedroomsMin` | integer | none | Minimum number of bedrooms. |
| `bedroomsMax` | integer | none | Maximum number of bedrooms. |
| `livingAreaMin` | integer | none | Minimum living area in m². |
| `livingAreaMax` | integer | none | Maximum living area in m². |
| `landAreaMin` | integer | none | Minimum land area in m² (mainly houses and land). |
| `landAreaMax` | integer | none | Maximum land area in m². |
| `constructionYearMin` | integer | none | Filter out buildings older than this year. |
| `buildingConditions` | array | none | As new, just renovated, good, to be done up, to renovate, or to restore. |
| `epcGradeMax` | string | none | Only listings at or better than this EPC grade (A++ to G). |
| `hasGarden` | boolean | `false` | Only listings with a garden. |
| `hasTerrace` | boolean | `false` | Only listings with a terrace. |
| `hasSwimmingPool` | boolean | `false` | Only listings with a swimming pool. |
| `newBuildsOnly` | boolean | `false` | Only new-build projects. |
| `urls` | array | one example per URL shape (see URL shapes) | Immoweb URLs (URL mode): searches and single listings. The `page` query param sets the starting page; the walk paginates forward from there. Left untouched, URL mode scrapes one example per shape. |
| `fetchDetails` | boolean | `false` | Fetch each listing's detail page for 70+ extra fields (description, EPC score, building details, energy features, agent contact). Billed per listing enriched. |
| `maxItems` | integer | `20` | Stop after scraping this many listings (`0` = the hard cap of 100000 listings per run). |
| `maxPagesPerUrl` | integer | none (hard cap of 1000 pages per URL) | Optional safety bound on how many search result pages to walk forward per URL (about 30 listings each). |
| `resumeFromRunId` | string | none | Run id or dataset id from this account; continues ONE interrupted run by skipping listings already collected there. |
| `incrementalMode` | boolean | `false` | Track this search across scheduled runs and classify each listing as NEW/UPDATED/UNCHANGED/REAPPEARED/EXPIRED. |
| `stateKey` | string | none (auto-derived from filters or URLs) | Name this monitoring campaign, or share one baseline across runs, for Incremental mode. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) listings that did not change. Incremental mode only. |
| `ignoreFieldsForChanges` | array | none | Field names (or dotted paths) whose changes do not count as UPDATED in Incremental mode. The fields stay in the output. View and bookmark counters are always ignored. |
| `emitExpired` | boolean | `false` | Also return, once, listings that disappeared, but only after a run proves it scanned the whole tracked search. Incremental mode only. |
| `proxyConfiguration` | object | Apify Proxy, Residential group | Connection settings. This actor requires Apify residential proxy access to reach immoweb.be reliably. |
| `mcpConnectors` | array | none | Optional: send a summary of each listing to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | none | Notion connector only: page under which listing pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "id": 12345678,
  "url": "https://www.immoweb.be/en/classified/12345678",
  "title": "Charming villa with garden",
  "transactionType": "FOR_SALE",
  "propertyType": "HOUSE",
  "propertySubtype": "VILLA",
  "price": 425000,
  "displayPrice": "€425,000",
  "price_per_sqm": 2125,
  "bedrooms": 4,
  "rooms": 7,
  "livingArea": 200,
  "landArea": 850,
  "street": "Rue de la Gare",
  "number": "15",
  "postalCode": "1000",
  "locality": "Bruxelles",
  "district": "Brussels",
  "province": "Brussels",
  "region": "Brussels",
  "regionCode": "BRUSSELS",
  "country": "Belgium",
  "latitude": 50.8503,
  "longitude": 4.3517,
  "images": [
    "https://media-resize.immowebstatic.be/classifieds/example/736x736/image1.jpg",
    "https://media-resize.immowebstatic.be/classifieds/example/736x736/image2.jpg"
  ],
  "imageCount": 12,
  "lastModified": "2026-04-15T10:30:00.000Z",
  "customerName": "Example Agency"
}
```

### Plan Requirement

Immoweb sits behind Cloudflare and blocks datacenter proxies with a 403 "Attention Required" block page, so this actor needs Apify residential proxy access (the default Residential group). Apify free plans do not include residential proxies. When the default connection is unavailable the run logs "The selected connection is unavailable to this run" and continues on a backup connection, which may still be blocked; if it serves pages it logs "Served over the backup connection (the default connection was unavailable)." If no results page can be loaded at all, the run ends as failed with a clear status message (for example "No results: residential proxies are unavailable on this run ..." or "Could not load any results page (the requests failed) ... This is a connection problem, not an empty search"). You can also supply your own proxy URLs. Billing is described in the FAQ below; use **Maximum Listings** to cap the size of any run.

### FAQ

#### How much does it cost?

This actor uses pay-per-event pricing with three events: a result for every listing row saved to the dataset, a start fee once per run, and a detail-enrichment event for each listing pushed with its detail page fetched and parsed (only when **Fetch Detail Pages** is on). Nothing is charged for listings that are not delivered, such as unchanged listings in Incremental mode (unless you turn on **Emit unchanged listings**) or failed requests. The **Pricing** tab shows the current rate of each event. Use **Maximum Listings** to cap the size of any run.

#### Is it legal to scrape Immoweb?

This actor collects only publicly available property listing data. You are responsible for how you use it: follow Immoweb's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing facts like price, size and address are generally not protected, but photos and full descriptions may be subject to third-party rights.

#### Can I get only new or changed listings on a schedule?

Yes. Schedule the actor from the **Schedules** tab on the SAME search and turn on **Incremental mode**. The first run returns everything as `NEW`; later runs return only `NEW`, `UPDATED` and, once a prior run has tombstoned a listing as `EXPIRED`, `REAPPEARED` listings. Unchanged listings are not billed unless you turn **Emit unchanged listings** on.

#### Why do I see EXPIRED listings sometimes but never REAPPEARED ones?

`EXPIRED` only fires when **Emit expired listings** is on AND a run proves it scanned the whole tracked search to completion, never on a run capped by Maximum Listings, a resumed run, or one that hit a block partway through. `REAPPEARED` then only shows up for a listing that was marked `EXPIRED` that way and is later found again. If you have never turned **Emit expired listings** on, `REAPPEARED` can never appear, because nothing has been marked gone yet.

#### What's the difference between Resume and Incremental mode?

**Resume** (`resumeFromRunId`) continues ONE specific interrupted run by its id, skipping listings that run already collected, for a one-off continuation. **Incremental mode** is for running the SAME search repeatedly on a schedule; it remembers its own baseline and classifies every listing against the previous run. Combining `resumeFromRunId` with Incremental mode is only allowed to bootstrap a brand-new monitoring baseline; it is rejected if that search already has saved state.

#### Why did my run fail instead of returning an empty dataset?

A run is marked failed for a bad input, such as a `resumeFromRunId` that doesn't resolve to a run or dataset you can read, or combining it with Incremental mode on a search that already has saved state. If no results page can be loaded at all (for example, the account has no residential proxy access and the backup connection is also blocked), the run is marked failed with a status message explaining the connection problem, rather than finishing as an empty success. An empty dataset on a successful run means the search genuinely matched nothing.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push a summary of each listing into Notion, Linear or Airtable.

### 🔗 Want more real estate data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🏠 <a href="https://apify.com/abotapi/immoscout24-scraper"><b>ImmobilienScout24.de Scraper</b></a><br>Extract property listings from immobilienscout24.de, Germany's #1 real estate platform...</td><td>🏠 <a href="https://apify.com/abotapi/seloger-france-scraper"><b>SeLoger Scraper</b></a><br>Scrape SeLoger.com properties for sale and rent. Search by location with price, room and...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/realestate-au-scraper"><b>Real Estate AU</b></a><br>Extract detailed Australian real estate property listings with 30+ structured fields...</td><td>🏠 <a href="https://apify.com/abotapi/otodom-pl-scraper"><b>Otodom.pl Scraper</b></a><br>Scrape Otodom.pl properties for sale and rent, including apartments, houses, plots...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/zumper-scraper"><b>Zumper Scraper</b></a><br>Scrape Zumper.com rental listings: apartments, houses, condos and rooms. Pulls 60+ fields...</td><td>🚗 <a href="https://apify.com/abotapi/dubizzle-com-scraper"><b>Dubizzle.com Scraper</b></a><br>Scrape dubizzle.com across the UAE for property, motors, and agent listings. Search by...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/immoweb-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

Search mode = use the filter fields below to build the search query. URL mode = paste any immoweb.be search URL already refined in a browser (recommended for complex filters).

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

Listings to search for. Search mode only.

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

Filter by property type. Leave empty to search houses and apartments; select other types (land, parking, office, industry, commercial, apartment block, garage, new-build projects) explicitly.

## `postalCodes` (type: `array`):

Belgian postal codes to search in (e.g. 1000 for Brussels, 2000 for Antwerp). Leave empty to search all of Belgium.

## `countries` (type: `array`):

Countries to include (Immoweb also lists properties in neighboring regions).

## `orderBy` (type: `string`):

Sort order for search results (search mode only).

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

Minimum property price in EUR.

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

Maximum property price in EUR.

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

Minimum number of bedrooms.

## `bedroomsMax` (type: `integer`):

Maximum number of bedrooms.

## `livingAreaMin` (type: `integer`):

Minimum living area in square meters.

## `livingAreaMax` (type: `integer`):

Maximum living area in square meters.

## `landAreaMin` (type: `integer`):

Only relevant for houses and land listings.

## `landAreaMax` (type: `integer`):

Maximum land area in square meters.

## `constructionYearMin` (type: `integer`):

Filter out buildings older than this year.

## `buildingConditions` (type: `array`):

Filter by building condition (leave empty for any).

## `epcGradeMax` (type: `string`):

Only include listings with an energy rating at or better than this grade (A++ is best).

## `hasGarden` (type: `boolean`):

Only include listings with a garden.

## `hasTerrace` (type: `boolean`):

Only include listings with a terrace.

## `hasSwimmingPool` (type: `boolean`):

Only include listings with a swimming pool.

## `newBuildsOnly` (type: `boolean`):

Only include new-build projects.

## `urls` (type: `array`):

Immoweb URLs: search pages (en, nl and fr), filtered/sorted searches, location-path searches, new-project searches, and single listing pages. The ?page=N query param sets the starting page; pagination then walks forward up to Maximum Pages per URL. Agency pages (/en/agency/<name>/<id>) are expanded into that agency's listings: houses and apartments for sale and for rent, and land for sale. Leaving this field untouched scrapes one example per shape. In URL mode, all the search-mode filter fields above are ignored.

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

Fetch each listing's detail page for 70+ extra attributes: description, EPC score, building details, energy features, agent contact, amenities, view count, etc. Applies regardless of search mode.

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

Stop after scraping this many listings (0 = the hard cap of 100000 listings per run).

## `maxPagesPerUrl` (type: `integer`):

Optional safety limit on how many search result pages to walk forward per URL (~30 listings each). Leave empty to walk result pages up to a hard cap of 1000 pages per URL — 'Maximum Listings' above is the real hard cap; the walk also stops on its own once the site's reported total is reached or a page returns no new listings.

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

Requires Apify residential proxy access ($8/GB). Add residential proxy credit to your account to use this actor.

## `resumeFromRunId` (type: `string`):

Optional. ID of a previous run of this actor (or a dataset ID) to continue ONE interrupted crawl. Listings already in that dataset are skipped, so this run returns only NEW listings (a delta). Combine both runs' datasets for the full set. For a search you run on a RECURRING schedule, use Incremental mode below instead — it remembers state itself so you don't paste an id every time.

## `incrementalMode` (type: `boolean`):

For a search you run repeatedly on a schedule. When ON, the actor remembers the previous run of the SAME search itself (see Resume above instead for a one-off continuation by id) and adds changeType (NEW/UPDATED/REAPPEARED/EXPIRED/UNCHANGED) + changedFields/firstSeenAt/lastSeenAt to every listing. UNCHANGED listings are neither pushed nor billed unless 'Emit unchanged listings' is on. Off by default — existing schedules and output are unaffected.

## `stateKey` (type: `string`):

Only used when Incremental mode is on. Name this monitoring campaign so its baseline is tracked separately from any other search. Leave empty to auto-derive a key from your search filters or URLs — two differently-filtered searches never share a baseline either way.

## `emitUnchanged` (type: `boolean`):

Only used when Incremental mode is on. When ON, listings with no change since the last run are still pushed to the dataset (changeType=UNCHANGED) and billed like any other row. Off by default, so a recurring run returns and bills only what actually changed.

## `emitExpired` (type: `boolean`):

Only used when Incremental mode is on. When ON, and this run scans the tracked search to completion, listings that were previously tracked but no longer appear are pushed once with changeType=EXPIRED (and billed). Off by default. Never fires on a capped, resumed, or otherwise partial run.

## `ignoreFieldsForChanges` (type: `array`):

Only used when Incremental mode is on. Field names whose changes should NOT mark a listing as UPDATED, for example agencyPhone or a dotted path for a nested field. A bare name also matches at any nested depth. The fields stay in the output; they are just left out of the comparison and out of changedFields. View and bookmark counters (stat_viewCount, stat_bookmarkCount) are always ignored. Changing this list never causes a one-time wave of UPDATED rows.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "transactionType": "for-sale",
  "countries": [
    "BE"
  ],
  "orderBy": "newest",
  "hasGarden": false,
  "hasTerrace": false,
  "hasSwimmingPool": false,
  "newBuildsOnly": false,
  "urls": [
    "https://www.immoweb.be/en/search/house/for-sale?countries=BE&page=1&orderBy=newest",
    "https://www.immoweb.be/en/search/house/for-sale?countries=BE&page=2&orderBy=newest",
    "https://www.immoweb.be/en/search/house-and-apartment/for-rent?countries=BE&postalCodes=BE-1000&minPrice=500&maxPrice=1500&orderBy=cheapest",
    "https://www.immoweb.be/nl/zoeken/huis/te-koop?countries=BE&page=1",
    "https://www.immoweb.be/fr/recherche/maison/a-vendre?countries=BE&page=1",
    "https://www.immoweb.be/en/search/apartment/for-sale/brussels/province?countries=BE",
    "https://www.immoweb.be/en/search/house/for-sale/antwerpen/2000?countries=BE",
    "https://www.immoweb.be/en/search/new-real-estate-project-houses/for-sale",
    "https://www.immoweb.be/en/classified/apartment/for-sale/aalst-9300/9300/21925957",
    "https://www.immoweb.be/en/classified/21925957",
    "https://www.immoweb.be/en/agency/immo-bastogne-sa/318779"
  ],
  "fetchDetails": false,
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": []
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

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

No description

## `changes` (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 = {
    "mode": "search",
    "urls": [
        "https://www.immoweb.be/en/search/house/for-sale?countries=BE&page=1&orderBy=newest",
        "https://www.immoweb.be/en/search/house/for-sale?countries=BE&page=2&orderBy=newest",
        "https://www.immoweb.be/en/search/house-and-apartment/for-rent?countries=BE&postalCodes=BE-1000&minPrice=500&maxPrice=1500&orderBy=cheapest",
        "https://www.immoweb.be/nl/zoeken/huis/te-koop?countries=BE&page=1",
        "https://www.immoweb.be/fr/recherche/maison/a-vendre?countries=BE&page=1",
        "https://www.immoweb.be/en/search/apartment/for-sale/brussels/province?countries=BE",
        "https://www.immoweb.be/en/search/house/for-sale/antwerpen/2000?countries=BE",
        "https://www.immoweb.be/en/search/new-real-estate-project-houses/for-sale",
        "https://www.immoweb.be/en/classified/apartment/for-sale/aalst-9300/9300/21925957",
        "https://www.immoweb.be/en/classified/21925957",
        "https://www.immoweb.be/en/agency/immo-bastogne-sa/318779"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": []
    },
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/immoweb-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 = {
    "mode": "search",
    "urls": [
        "https://www.immoweb.be/en/search/house/for-sale?countries=BE&page=1&orderBy=newest",
        "https://www.immoweb.be/en/search/house/for-sale?countries=BE&page=2&orderBy=newest",
        "https://www.immoweb.be/en/search/house-and-apartment/for-rent?countries=BE&postalCodes=BE-1000&minPrice=500&maxPrice=1500&orderBy=cheapest",
        "https://www.immoweb.be/nl/zoeken/huis/te-koop?countries=BE&page=1",
        "https://www.immoweb.be/fr/recherche/maison/a-vendre?countries=BE&page=1",
        "https://www.immoweb.be/en/search/apartment/for-sale/brussels/province?countries=BE",
        "https://www.immoweb.be/en/search/house/for-sale/antwerpen/2000?countries=BE",
        "https://www.immoweb.be/en/search/new-real-estate-project-houses/for-sale",
        "https://www.immoweb.be/en/classified/apartment/for-sale/aalst-9300/9300/21925957",
        "https://www.immoweb.be/en/classified/21925957",
        "https://www.immoweb.be/en/agency/immo-bastogne-sa/318779",
    ],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": [],
    },
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/immoweb-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 '{
  "mode": "search",
  "urls": [
    "https://www.immoweb.be/en/search/house/for-sale?countries=BE&page=1&orderBy=newest",
    "https://www.immoweb.be/en/search/house/for-sale?countries=BE&page=2&orderBy=newest",
    "https://www.immoweb.be/en/search/house-and-apartment/for-rent?countries=BE&postalCodes=BE-1000&minPrice=500&maxPrice=1500&orderBy=cheapest",
    "https://www.immoweb.be/nl/zoeken/huis/te-koop?countries=BE&page=1",
    "https://www.immoweb.be/fr/recherche/maison/a-vendre?countries=BE&page=1",
    "https://www.immoweb.be/en/search/apartment/for-sale/brussels/province?countries=BE",
    "https://www.immoweb.be/en/search/house/for-sale/antwerpen/2000?countries=BE",
    "https://www.immoweb.be/en/search/new-real-estate-project-houses/for-sale",
    "https://www.immoweb.be/en/classified/apartment/for-sale/aalst-9300/9300/21925957",
    "https://www.immoweb.be/en/classified/21925957",
    "https://www.immoweb.be/en/agency/immo-bastogne-sa/318779"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": []
  },
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/immoweb-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/immoweb-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/LnaRwKm4VKoKoZf9G/builds/6hoqIH68kdDKp3EL1/openapi.json
