# Immoweb Scraper: Belgian Property Listings, EPC & Agents (`studioxl/immoweb-scraper`) Actor

Every Immoweb.be listing in Belgium, for sale or rent, as a clean spreadsheet: price, address, GPS, living area, bedrooms, EPC score, cadastral income, photos and agency contacts. Type a postal code or paste a search. For investors, agencies and market research.

- **URL**: https://apify.com/studioxl/immoweb-scraper.md
- **Developed by:** [Kamil Ertekin](https://apify.com/studioxl) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 listings

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

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

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

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

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

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

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

# README

## Immoweb Scraper: Belgian Property Listings, EPC & Agents

Get property listings from Immoweb.be, Belgium's main property portal, as one clean row per home: price, address, GPS, living area, bedrooms, EPC energy score, cadastral income, photos and the listing agency. Type a postal code like `9000` and you get every house and apartment for sale in Ghent, ready for Excel, Google Sheets or your CRM.

It's built for investors and analysts pricing a town, agencies watching new listings in their area, relocation teams building shortlists, and researchers who need the whole market rather than a sample.

### What you get

Every row has the listing basics. Switch on **Include full listing details** and each row grows from 50 to 122 columns.

| Field | Example | Notes |
|---|---|---|
| `url` | `https://www.immoweb.be/en/classified/apartment/for-sale/gent/9000/21918861` | Link to the listing |
| `transactionType` | `FOR_SALE` | `FOR_SALE` or `FOR_RENT` |
| `propertyType`, `propertySubtype` | `APARTMENT`, `PENTHOUSE` | House, apartment, villa, land, office... |
| `title` | `Bright apartment with 2 bedrooms...` | As written by the agency. Immoweb cuts most titles at about 60 characters |
| `price` | `449000` | EUR. Monthly rent for rentals. Projects show their lowest unit price |
| `pricePerSqm` | `4490` | EUR per m² of living area (per month for rentals) |
| `bedroomCount`, `netHabitableSurface`, `landSurface` | `2`, `100`, `null` | Living area and plot in m² |
| `street`, `streetNumber`, `postalCode`, `locality`, `province` | `Henri Dunantlaan`, `22`, `9000`, `Gent`, `East Flanders` | Number always in its own column |
| `latitude`, `longitude` | `51.0513346`, `3.6994978` | Missing when the agency hides the address |
| `agencyName` | `Kleyo` | The advertiser |
| `pictures`, `pictureCount` | 4 preview photos | Every photo with full details |
| `lastModificationDate` | `2026-10-16T06:02:01.770Z` | ISO 8601, UTC |
| `epcScore`, `primaryEnergyConsumptionPerSqm` | `B`, `101` | Full details. Energy label A++ to G, kWh/m² per year |
| `constructionYear`, `buildingCondition`, `facadeCount` | `2013`, `AS_NEW`, `3` | Full details. 4 facades means detached |
| `cadastralIncome`, `floodZoneType`, `monthlyCosts` | `1086`, `NON_FLOOD_ZONE`, `null` | Full details |
| `bathroomCount`, `heatingType`, `kitchenType` | `1`, `GAS`, `INSTALLED` | Full details |
| `hasLift`, `hasTerrace`, `hasGarden` and 22 more | `true` | Full details. `null` means the agency didn't say |
| `agencyPhone`, `agencyEmail`, `agencyWebsite`, `agencyIpiNumber` | `+3292252524`, `info@agency.be` | Full details. `agencyEmail` is only ever the agency's own address |
| `agencyLeadEmail` | `200484@secure.fortissimmo.net` | Full details. Lead-form inbox, kept apart from the agency's own email |
| `description`, `creationDate`, `viewCount` | full text, `2026-10-16T06:01:57.821Z`, `624` | Full details |
| `daysOnMarket` | `13` | Full details. Whole days since the listing went online |
| `floorPlanUrls`, `virtualTourUrl` | PDF links, `https://youtu.be/…` | Full details. Only when the agency added them. Floor plans are rare: 1 listing in 17 in our check |
| `monitorStatus`, `changedFields` | `changed`, `["price"]` | Daily feed only. `new`, `changed` or `removed`, and which columns changed |
| `isProject`, `projectName`, `priceMin`, `priceMax`, `bedroomRange` | `true`, `Dungelpark`, `287500`, `691500`, `1 - 3` | New-build developments. Unit counts with full details |

How often fields are filled, measured on real runs: street and GPS 98%, bedrooms 96%, EPC score 96%, construction year 90%, agency phone 88%, flood zone 87%, cadastral income 53%.

### How to use it

1. Type one or more places into **Places to search**: a postal code (`9000`), a province (`Antwerp`) or a larger town (`Leuven`). Or paste a search link from immoweb.be into **Immoweb links**.
2. Pick **For sale or for rent**, a **Property type**, and set **Maximum results**. That number also caps what you pay.
3. Click **Start**. A 100-row run finishes in under 10 seconds. Download the results as CSV, Excel or JSON.

This input works as-is:

```json
{
  "locations": ["9000"],
  "listingType": "forSale",
  "propertyType": "apartment",
  "maxPrice": 400000,
  "maxItems": 100
}
```

#### Postal codes for the big cities

| City | Postal code | | City | Postal code |
|---|---|---|---|---|
| Brussels centre | 1000 | | Ghent | 9000 |
| Ixelles | 1050 | | Leuven | 3000 |
| Uccle | 1180 | | Bruges | 8000 |
| Antwerp city | 2000 | | Liège city | 4000 |
| Mechelen | 2800 | | Namur city | 5000 |
| Hasselt | 3500 | | Charleroi | 6000 |

Typing "Antwerp", "Liège" or "Namur" searches the whole province. Use the postal code for the city only.

### Input reference

| Field | What it does |
|---|---|
| **Places to search** (`locations`) | Postal codes, provinces or larger towns. `All` searches the whole of Belgium. |
| **Immoweb links** (`startUrls`) | Search or listing links from immoweb.be, in English, French or Dutch. Filters in the link are kept. |
| **For sale or for rent** (`listingType`) | `forSale` or `forRent`. Used for places; a link keeps its own choice. |
| **Property type** (`propertyType`) | House and apartment, house, apartment, villa, kot, land, office and more. |
| **Maximum results** (`maxItems`) | Stops the run after this many listings. 0 means no limit. API callers can send `limit` or `maxResults` instead. |
| **Include full listing details** (`scrapeDetails`) | Adds EPC, condition, cadastral income, agency contacts and every photo. |
| **Filters** | Price, bedrooms, living area, plot size, construction year, EPC labels, condition, sub-types and must-haves like a garden or a lift. |
| **Published or updated within** (`modifiedWithinDays`) | Only listings put online or updated in the last this many days. A new listing always counts. |
| **Only new or changed listings** (`onlyNewSinceLastRun`) | For scheduled runs. Returns only what's new or changed since the last run, says what changed, and lists listings that have left your search. |

### Output example

One row with full details, trimmed:

```json
{
  "id": 21918861,
  "url": "https://www.immoweb.be/en/classified/apartment/for-sale/gent/9000/21918861",
  "transactionType": "FOR_SALE",
  "propertyType": "APARTMENT",
  "title": "Bright apartment with 2 bedrooms and 2 parking spaces for sale in Gent",
  "price": 449000,
  "pricePerSqm": 4490,
  "bedroomCount": 2,
  "bathroomCount": 1,
  "netHabitableSurface": 100,
  "street": "Henri Dunantlaan",
  "streetNumber": "22",
  "postalCode": "9000",
  "locality": "Gent",
  "province": "East Flanders",
  "latitude": 51.0513346,
  "longitude": 3.6994978,
  "epcScore": "B",
  "primaryEnergyConsumptionPerSqm": 101,
  "constructionYear": 2013,
  "buildingCondition": "AS_NEW",
  "cadastralIncome": 1086,
  "floodZoneType": "NON_FLOOD_ZONE",
  "hasLift": true,
  "agencyName": "Kleyo",
  "agencyEmail": null,
  "agencyLeadEmail": "inbound-automation@kleyo.be",
  "agencyIpiNumber": "800115",
  "pictureCount": 25,
  "creationDate": "2026-10-16T06:01:57.821Z",
  "viewCount": 624
}
```

### Pricing

You pay per listing delivered. There's no start fee.

| Apify plan | Listing / 1,000 | Listing with full details / 1,000 |
|---|---:|---:|
| Free | $1.00 | $1.80 |
| Starter (Bronze) | $0.90 | $1.60 |
| Scale (Silver) | $0.80 | $1.40 |
| Business (Gold) and higher | $0.70 | $1.20 |

A row with full details is charged once, at the full-details price, not on top of the listing price.

Example: all 1,162 houses and apartments for sale in Ghent (9000) with full details cost about $2.09 on the Free plan, or about $1.39 on the Business plan. The same search without details costs about $1.16.

Not charged: duplicates, listings that were removed, places Immoweb doesn't know, listings outside your date window, and in a daily feed, unchanged listings and the rows that report a removed listing.

### FAQ

**How fresh is the data?** Every run reads Immoweb live, so you get what's online at that moment. Schedule the run daily or hourly to keep a sheet up to date.

**Can I get the newest listings first?** Results come in Immoweb's own order, which isn't by date. To get only recent listings, set **Published or updated within**, for example to 7 days. For a running list of what's new, schedule a daily feed with **Only new or changed listings**.

**How does the daily feed show what changed?** Each row has `monitorStatus`: `new` for a listing the feed hasn't seen before, `changed` when its price, title, bedrooms, surface or project sales moved, with the changed columns named in `changedFields` (for example `["price"]`). A listing that has left your search (sold, withdrawn, or no longer matching your filters) comes back once as a `removed` row with its link, free of charge. Removed listings are reported when the run reads the whole search, so set **Maximum results** above the number of matches, or to 0. They show up from the second run on.

**Why do I get fewer rows than Immoweb shows?** The same home is often listed by several agencies. Those copies are merged into one row so you don't pay five times for one villa. Turn off **Merge duplicate listings** under More options to get every listing.

**Can I get more than 10,000 results?** Yes. Immoweb shows at most about 10,000 results per search, but this scraper still returns every match. All of Belgium for sale is over 86,000 listings.

**Why is `agencyEmail` empty when the agency clearly has an inbox?** Many agencies publish a lead-form address first. That one goes into `agencyLeadEmail`, so `agencyEmail` only ever holds the agency's own address. For a full list of agencies with their own emails, VAT and IPI numbers, use the companion **Immoweb Agency Scraper**.

**What do EPC, cadastral income and IPI mean?** EPC (PEB in Brussels and Wallonia) is the energy label, A++ best to G worst. Cadastral income (KI or RC) is the notional rent used for property tax. IPI (BIV in Dutch) is the Belgian estate-agent licence number.

**Can I run it on a schedule or connect it to other tools?** Yes. Use Apify schedules for a daily feed, and Apify's integrations to send results to Google Sheets, Make, Zapier, n8n, Slack or a webhook. You can also fetch results from your own code through the Apify API.

### Limits

- Belgian listings on Immoweb only, for sale or for rent. Immoweb doesn't publish sold prices.
- Some agencies hide the exact address. Those rows have the postal code and town but no street or GPS.
- Titles are cut by Immoweb itself at about 60 characters. The full text is in `description` (full details).
- A town name alone works for the larger towns listed in the input help. For anywhere else, use the 4-digit postal code.
- New-build projects have a price range (`priceMin` to `priceMax`) rather than one price. Switch on **One row per new-build unit** for a row per flat.
- Private sellers' personal contact details are not included. You're responsible for using agency contact data in line with GDPR.

Missing a field? Open an issue on the Issues tab.

# Changelog

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

# Actor input Schema

## `locations` (type: `array`):

Postal codes ("9000", "1050"), provinces ("Antwerp", "Liège", "West-Vlaanderen") or larger towns ("Ghent", "Leuven"). Type "All" for the whole of Belgium.

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

Or paste a search or listing link from immoweb.be, in English, French or Dutch. The filters in the link are kept, for example https://www.immoweb.be/en/search/apartment/for-rent/brussels/province

## `listingType` (type: `string`):

Used for the places above. A pasted link keeps its own choice.

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

Used for the places above. "House and apartment" returns both.

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

The run stops after this many listings, so this also caps what you pay. Use 0 for no limit.

## `scrapeDetails` (type: `boolean`):

Adds about 70 fields per listing: EPC energy score, construction year, condition, cadastral income, flood zone, agency phone and email, and every photo. Charged at the full-details price.

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

Total price for sales, monthly rent for rentals. Example: 250000.

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

Total price for sales, monthly rent for rentals. Example: 450000.

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

Example: 2.

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

Example: 4.

## `minLivingArea` (type: `integer`):

Habitable surface. Example: 80.

## `maxLivingArea` (type: `integer`):

Habitable surface. Example: 200.

## `minLandSurface` (type: `integer`):

Land around a house. Example: 500.

## `maxLandSurface` (type: `integer`):

Example: 2000.

## `minYearBuilt` (type: `integer`):

Earliest construction year. Example: 1990.

## `maxYearBuilt` (type: `integer`):

Latest construction year. Example: 2010.

## `minRooms` (type: `integer`):

All rooms counted. Example: 4.

## `maxRooms` (type: `integer`):

All rooms counted. Example: 8.

## `minTerraceSurface` (type: `integer`):

Example: 10.

## `minGardenSurface` (type: `integer`):

Example: 100.

## `minKitchenSurface` (type: `integer`):

Example: 12.

## `minParkingPlaces` (type: `integer`):

Indoor and outdoor together. Example: 1.

## `minFacades` (type: `integer`):

4 facades is a detached house, 3 is semi-detached, 2 is terraced.

## `maxFacades` (type: `integer`):

Example: 2 for terraced houses only.

## `epcScores` (type: `array`):

Keep only these energy labels (EPC in Flanders, PEB in Brussels and Wallonia). A++ is the most efficient, G the least.

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

Keep only listings in these conditions, for example "To renovate".

## `propertySubtypes` (type: `array`):

Narrow to specific kinds, for example only penthouses or only villas. Leave empty for all.

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

Only listings with a terrace.

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

Only listings with a garden.

## `hasTerraceOrGarden` (type: `boolean`):

Only listings with at least one of the two.

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

Only listings with a swimming pool.

## `hasFireplace` (type: `boolean`):

Only listings with a fireplace.

## `hasLift` (type: `boolean`):

Only listings with a lift.

## `hasDisabledAccess` (type: `boolean`):

Only listings marked as accessible.

## `isImmediatelyAvailable` (type: `boolean`):

Only listings you can move into right away.

## `isFurnished` (type: `string`):

Only used for rentals. Ignored for sales.

## `isAPublicSale` (type: `boolean`):

Properties sold at public auction. Ignored for rentals.

## `isALifeAnnuitySale` (type: `boolean`):

Life-annuity sales (viager / lijfrente). Ignored for rentals.

## `isUnderOption` (type: `boolean`):

Properties that already have an option on them. Ignored for rentals.

## `modifiedWithinDays` (type: `integer`):

Only listings put online or updated in the last this many days, for example 7. A new listing always counts. Updates include price changes and agencies re-saving a listing. Leave empty for any date. Large searches take longer with this on, because every result is checked.

## `onlyNewSinceLastRun` (type: `boolean`):

For scheduled runs: return only listings that are new, or whose price or details changed, since the last run. Changed rows name what changed in changedFields. Listings that have left your search come back as free rows marked removed. Unchanged listings are skipped and not charged.

## `dedupeStoreName` (type: `string`):

Runs with the same name share one memory. Use a different name per feed, for example "ghent-rentals".

## `maxItemsPerLocation` (type: `integer`):

Cap each place or pasted link separately, so one busy area cannot fill the whole run. Use 0 for no cap.

## `mergeDuplicateListings` (type: `boolean`):

The same home listed by several agencies comes back as one row, so you pay once. Turn off to get every listing.

## `expandProjectUnits` (type: `boolean`):

Needs "Include full listing details". Each unit in a new-build project gets its own row with price, size, floor and sale status.

## `splitLargeSearches` (type: `boolean`):

Immoweb shows at most about 10,000 results per search. Leave this on to still get every match when your maximum is higher.

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

Leave as is. The default works on every Apify plan.

## Actor input object example

```json
{
  "locations": [
    "9000",
    "1050",
    "Antwerp"
  ],
  "startUrls": [
    {
      "url": "https://www.immoweb.be/en/search/apartment/for-rent/brussels/province?countries=BE"
    }
  ],
  "listingType": "forSale",
  "propertyType": "house-and-apartment",
  "maxItems": 100,
  "scrapeDetails": false,
  "epcScores": [],
  "buildingConditions": [],
  "propertySubtypes": [],
  "hasTerrace": false,
  "hasGarden": false,
  "hasTerraceOrGarden": false,
  "hasSwimmingPool": false,
  "hasFireplace": false,
  "hasLift": false,
  "hasDisabledAccess": false,
  "isImmediatelyAvailable": false,
  "isFurnished": "any",
  "isAPublicSale": false,
  "isALifeAnnuitySale": false,
  "isUnderOption": false,
  "onlyNewSinceLastRun": false,
  "dedupeStoreName": "immoweb-dedupe",
  "maxItemsPerLocation": 0,
  "mergeDuplicateListings": true,
  "expandProjectUnits": false,
  "splitLargeSearches": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

## `details` (type: `string`):

No description

## `projects` (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 = {
    "locations": [
        "9000"
    ],
    "listingType": "forSale",
    "propertyType": "house-and-apartment",
    "maxItems": 100,
    "epcScores": [],
    "buildingConditions": [],
    "propertySubtypes": [],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("studioxl/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 = {
    "locations": ["9000"],
    "listingType": "forSale",
    "propertyType": "house-and-apartment",
    "maxItems": 100,
    "epcScores": [],
    "buildingConditions": [],
    "propertySubtypes": [],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("studioxl/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 '{
  "locations": [
    "9000"
  ],
  "listingType": "forSale",
  "propertyType": "house-and-apartment",
  "maxItems": 100,
  "epcScores": [],
  "buildingConditions": [],
  "propertySubtypes": [],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call studioxl/immoweb-scraper --silent --output-dataset

```

## MCP server setup

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