# Germany Property Tracker: Immowelt, Kleinanzeigen (`apizilla/germany-property-tracker`) Actor

Scrape German property listings from Immowelt, Kleinanzeigen and ImmoScout24 (beta) in one run. Merges the same home across portals and tracks new listings, price drops, price rises, removed listings and days on market.

- **URL**: https://apify.com/apizilla/germany-property-tracker.md
- **Developed by:** [APIzilla](https://apify.com/apizilla) (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.50 / 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

### What does Germany Property Tracker do?

Germany Property Tracker scrapes **German property listings** from **[Immowelt](https://www.immowelt.de)**, **[Kleinanzeigen](https://www.kleinanzeigen.de/s-immobilien/c195)** and **[ImmoScout24](https://www.immobilienscout24.de)** (beta) in one run.

It finds the **same home on several portals** and gives you one clean record for it. It also tracks changes between runs: **new listings, price drops, price rises, removed listings** and **days on market**.

You can use it as a simple API for the German property market. Run it once, or put it on a schedule and get a daily change feed.

### Why use Germany Property Tracker?

- **Several portals, one dataset.** You do not need one scraper per portal.
- **No duplicates.** The same flat on Immowelt and Kleinanzeigen becomes one record. The other copies are listed in `alsoListedOn`.
- **Change alerts.** Turn on `monitor` and every listing gets a `changeType`: `new`, `price_drop`, `price_rise`, `back`, `unchanged`. Listings that are gone come as `removed`.
- **Days on market.** See how long a home has been for sale or for rent.
- **German rent details.** Kaltmiete, Warmmiete, Nebenkosten, Heizkosten, Kaution, Baujahr and Energieklasse.
- **Low price.** You pay per unique listing, not per page.

Typical users: investors, agents, relocation services, flat hunters, proptech teams and market analysts.

You also get everything the Apify platform offers: scheduling, API access, webhooks, integrations (Google Sheets, Make, Zapier, Slack) and proxies.

### What data can Germany Property Tracker extract?

| Field | What it is |
| --- | --- |
| `title`, `url` | Listing title and link |
| `portal`, `alsoListedOn` | Main portal and the same home on other portals |
| `operation`, `propertyType` | rent or sale, apartment or house |
| `price`, `pricePerM2` | Price in EUR. For rent this is the Kaltmiete. |
| `kaltmiete`, `warmmiete` | Cold rent and warm rent |
| `nebenkosten`, `heizkosten`, `kaution` | Service costs, heating costs, deposit |
| `sizeM2`, `rooms`, `bedrooms`, `bathrooms`, `floor` | Size and layout |
| `address`, `postcode`, `city`, `region`, `lat`, `lng` | Location |
| `baujahr`, `energieklasse` | Year built and energy class |
| `agencyName`, `isPrivateSeller` | Who offers it |
| `images`, `features`, `description` | Photos (first 5), features, full text |
| `firstSeenAt`, `lastSeenAt`, `daysOnMarket` | Time on the market |
| `priceHistory`, `changeType`, `previousPrice` | Price changes and events |

Some fields only come with **Include details** turned on. For example the full description, Nebenkosten, Kaution, Baujahr and coordinates.

### How to scrape German property listings

1. Open the Actor and go to the **Input** tab.
2. Add a search. Either paste a search URL from Immowelt, Kleinanzeigen or ImmoScout24, or type a city, for example `{"location": "Berlin", "operation": "rent", "maxPrice": 1500}`.
3. Pick the portals.
4. Set **Max listings per portal**.
5. Optional: turn on **Monitor changes** and add a schedule, for example every morning.
6. Click **Start**. Download the results as JSON, CSV, Excel or HTML.

### How much does it cost to scrape German property portals?

This Actor uses pay per event pricing. You only pay for results. Failed pages cost nothing.

| Event | When you pay | Price per 1,000 |
| --- | --- | --- |
| `listing-scraped` | Once per unique home in the results | $2.50 |
| `browser-listing` | Once per saved home that comes from ImmoScout24 (on top of `listing-scraped`) | $4.00 |
| `listing-details` | Once per home, only when Include details is on | $1.50 |
| `change-event` | Once per change (new, price drop, price rise, removed, back), only in monitor mode | $0.50 |
| `apify-actor-start` | When a run starts, set by Apify. One event per GB of memory | $1.25 (about $0.005 per run at the default 4 GB) |

Why is ImmoScout24 more expensive? ImmoScout24 blocks simple scrapers. We need a real browser and a home proxy for it. That costs us more, so a home from ImmoScout24 has an extra `browser-listing` charge. A home that is also on ImmoScout24 but comes from a cheaper portal does not pay it.

A removed home only costs a `change-event`. Unchanged homes do not cost a `change-event`.

You can set a maximum cost per run. The Actor checks the total price of all events for a home before it saves it. If they do not all fit, it stops. You never pay for a home that was not saved.

To keep costs low, leave Include details off, keep Max listings per portal small, and turn off the beta portal if you do not need it.

The Pricing tab shows the exact prices.

### Input

See the **Input** tab for all options. The most important ones:

- **Searches**: a list. Each entry is a portal URL like `{"url": "https://www.kleinanzeigen.de/s-wohnung-mieten/berlin/c203l3331"}` or a description like `{"location": "München", "operation": "sale", "propertyType": "apartment", "minPrice": 300000, "maxPrice": 600000, "minRooms": 2, "minSize": 50}`. A description runs on every selected portal.
- **Portals**: `immowelt`, `kleinanzeigen`, `immoscout24`. ImmoScout24 is beta.
- **Max listings per portal**: default 200.
- **Include details**: off by default. On means one extra page per listing.
- **Monitor changes** and **Monitor ID**: runs with the same Monitor ID are compared.
- **Proxy**: Apify Proxy is on by default.

### Output

Each item is one unique home. You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

```json
{
    "id": "de-3f9a1c2b4d5e6f70",
    "country": "DE",
    "portal": "immowelt",
    "url": "https://www.immowelt.de/expose/...",
    "alsoListedOn": [{ "portal": "kleinanzeigen", "url": "https://www.kleinanzeigen.de/s-anzeige/...", "price": 699 }],
    "title": "Modernisierte 2-Zimmer-Wohnung mit Balkon",
    "operation": "rent",
    "propertyType": "apartment",
    "price": 699,
    "pricePerM2": 14.87,
    "sizeM2": 47,
    "rooms": 2,
    "address": "Musterstraße 13, Stötteritz, 04299 Leipzig",
    "kaltmiete": 699,
    "warmmiete": 879,
    "nebenkosten": 120,
    "kaution": 2097,
    "energieklasse": "C",
    "firstSeenAt": "2026-10-10T00:00:00.000Z",
    "daysOnMarket": 12,
    "priceHistory": [{ "date": "2026-10-22T10:00:00.000Z", "price": 699 }],
    "changeType": "price_drop",
    "previousPrice": 749
}
```

The run also saves a short summary in the key-value store under `OUTPUT`. It shows the status of every portal (ok, partial, blocked, failed), how many listings came from each portal and how many duplicates were merged.

### Tips

- Start small. Use a low **Max listings per portal** to test your search.
- Leave **Include details** off if you only need price, size, rooms and address. It is faster and cheaper.
- For alerts, turn on **Monitor changes**, keep the same **Monitor ID**, and schedule the Actor. Filter the dataset by `changeType` to get only the changes.
- A listing is only marked `removed` when its portal worked fine in that run. If a portal was blocked, we do not mark its listings as removed.
- If you stop at the max listings limit, a listing must be missing in two runs in a row before it counts as removed.

### How does the merge work?

Two listings from different portals are the same home when:

- they are within 50 m of each other, or they have the same street and house number, and
- the size is within 5 percent, and
- the number of rooms is the same, and
- the price is within 10 percent.

The cheapest copy becomes the main record.

### About ImmoScout24 (beta)

ImmoScout24 has strong bot protection. It needs a **RESIDENTIAL** proxy. If you use Apify Proxy without picking a group, the Actor uses residential proxies in Germany for all portals. Plain requests to ImmoScout24 are blocked, even with a residential proxy. So the Actor opens ImmoScout24 in a real browser (Firefox), one page at a time, like a normal visitor. The page's own check runs and usually passes in a few seconds. Listing pages are then read from the same browser, so they are fast and cheap. The Actor does not solve captchas. If ImmoScout24 asks for one, ImmoScout24 is skipped, the other portals still work, and you are not charged for the blocked pages.

For cities outside our built-in list, paste an ImmoScout24 search URL instead of a city name.

### FAQ and support

**Is it legal to scrape property portals?**
Our Actors are ethical and do not extract any private user data, such as email addresses, gender, or location. They only extract what the user has chosen to share publicly. We therefore believe that our Actors, when used for ethical purposes by Apify users, are safe. However, you should be aware that your results could contain personal data. Personal data is protected by the GDPR in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you're unsure whether your reason is legitimate, consult your lawyers.

This Actor does not collect phone numbers or contact names.

**A portal shows "blocked" in the summary. What can I do?**
Use Apify Proxy with the RESIDENTIAL group. Lower the number of listings.

**Can I use it from my own code?**
Yes. See the **API** tab.

**Something is wrong or missing?**
Please open an issue in the **Issues** tab. We usually reply fast.

# Actor input Schema

## `searches` (type: `array`):

One entry per search. Use a search URL from a supported portal, for example {"url": "https://www.kleinanzeigen.de/s-wohnung-mieten/berlin/c203l3331"}. Or describe the search and we build the URL for every portal: {"location": "Berlin", "operation": "rent", "propertyType": "apartment", "minPrice": 500, "maxPrice": 1500, "minRooms": 2, "minSize": 50}. operation is "rent" or "sale". propertyType is "apartment" or "house". Prices are in EUR. For rent, price means Kaltmiete (cold rent).

## `portals` (type: `array`):

Which portals to scrape. ImmoScout24 is in beta: it has strong bot protection and needs a RESIDENTIAL proxy. Default: Immowelt and Kleinanzeigen.

## `maxListingsPerPortal` (type: `integer`):

Stop after this many listings from each portal (all searches together). Lower is cheaper and faster.

## `includeDetails` (type: `boolean`):

Open every listing page to get the full description, Nebenkosten, Heizkosten, Kaution, Baujahr, coordinates and all features. Slower and costs one extra event per listing. Off means list page data only.

## `browserForBeta` (type: `boolean`):

ImmoScout24 (beta) blocks plain requests. When this is on, the Actor opens ImmoScout24 in a real browser (Firefox) with a residential proxy, like a normal visitor. It is slower and costs more. It does not solve captchas: if ImmoScout24 asks for one, ImmoScout24 is skipped. Other portals always use fast plain requests.

## `monitor` (type: `boolean`):

Compare with the previous run and mark each listing as new, price_drop, price_rise, back or unchanged. Listings that disappeared are added as removed. Use with a schedule (for example every day).

## `monitorId` (type: `string`):

Name of the saved state for monitoring. Runs with the same ID are compared with each other. Letters, numbers and dashes only. Leave empty to build it from your searches.

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

Apify Proxy is recommended. If you do not pick a proxy group, all portals use RESIDENTIAL proxies in Germany. Datacenter proxies get blocked by these portals.

## Actor input object example

```json
{
  "searches": [
    {
      "location": "Berlin",
      "operation": "rent",
      "propertyType": "apartment",
      "maxPrice": 1500
    }
  ],
  "portals": [
    "immowelt",
    "kleinanzeigen"
  ],
  "maxListingsPerPortal": 20,
  "includeDetails": false,
  "browserForBeta": true,
  "monitor": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset with one item per unique home

## `summary` (type: `string`):

Per-portal status and counts

# 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 = {
    "searches": [
        {
            "location": "Berlin",
            "operation": "rent",
            "propertyType": "apartment",
            "maxPrice": 1500
        }
    ],
    "portals": [
        "immowelt",
        "kleinanzeigen"
    ],
    "maxListingsPerPortal": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("apizilla/germany-property-tracker").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 = {
    "searches": [{
            "location": "Berlin",
            "operation": "rent",
            "propertyType": "apartment",
            "maxPrice": 1500,
        }],
    "portals": [
        "immowelt",
        "kleinanzeigen",
    ],
    "maxListingsPerPortal": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("apizilla/germany-property-tracker").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 '{
  "searches": [
    {
      "location": "Berlin",
      "operation": "rent",
      "propertyType": "apartment",
      "maxPrice": 1500
    }
  ],
  "portals": [
    "immowelt",
    "kleinanzeigen"
  ],
  "maxListingsPerPortal": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call apizilla/germany-property-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,apizilla/germany-property-tracker"
        }
    }
}
```

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/Ex44CzyKW7HlR9pON/builds/onGHzYCqNmomULUZ2/openapi.json
