# Immowelt Scraper — German Rentals & Sales, Price Changes (`northbell/immowelt-scraper`) Actor

Immowelt scraper for German homes to rent or buy: price, rooms, living space, place, features and agency, plus new listings and price changes since your last run. Give it a city or an immowelt.de search link. No login.

- **URL**: https://apify.com/northbell/immowelt-scraper.md
- **Developed by:** [Northbell](https://apify.com/northbell) (community)
- **Categories:** Real estate, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 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

## Immowelt Scraper — German Rentals & Sales, Price Changes

Give it German places — a city for rent or for sale, apartments or houses, or an Immowelt search address — and get one clean row per listing: **price (cold and warm rent, or purchase price), rooms, living space, price per m², city, district, postal code, features, agency, photo and listing dates**. **$1.50 per 1,000 listings**, up to 1,000 listings per search, no login, no API key.

Run it again — or put it on a daily schedule — and every row also tells you **what changed since the last run**: whether the listing is **new**, its **previous price** and the **price change** in euros and percent. Immowelt's pages show only today's price; this keeps yesterday's, so a schedule becomes a price-cut and new-listing alert. Each search also gets one free summary row: results found, listings read, new listings, price drops, the median price and median price per m² of what was read, and Immowelt's own average price per m² for the city.

### What you get

| Field | What it is |
|---|---|
| `type`, `search` | `listing`, `summary` or `error`; the search entry the row belongs to |
| `id`, `url`, `title` | Immowelt's listing ID, the link, and the ad's headline (a phone number or e-mail address typed into it is cut out, with the words next to it; when a private landlord wrote no headline, Immowelt's generic title such as `Wohnung zur Miete` is used) |
| `deal`, `propertyType` | `rent`, `buy`, `auction` or `forced-auction`; `apartment` or `house` |
| `price`, `priceType`, `priceText`, `currency` | the price as a number in euros. `priceType` is `rent-cold` (Kaltmiete, per month), `rent-warm` (only a Warmmiete is given) or `purchase`. `price` is `null` when the ad says "price on request" (`priceText` keeps Immowelt's wording) |
| `warmRent` | rentals: the warm rent (Warmmiete) when the ad gives both |
| `pricePerSqm` | `price` divided by the living area (or by the total area when the ad only gives that): euros per m² per month for rentals, euros per m² for purchases |
| `rooms`, `livingAreaSqm`, `totalAreaSqm`, `plotAreaSqm` | rooms (half rooms such as 2.5 exist), living area, total area (some purchases, such as whole buildings, give only this), plot size of houses |
| `floor`, `floorsInBuilding` | floor (0 is the ground floor) and storeys, when the ad says |
| `availableFrom`, `availableImmediately` | the move-in date, or `true` for "frei ab sofort" |
| `energyClass`, `condition`, `isNewBuild` | energy class (`A+` to `H`), Immowelt's condition label (`first-time-use`, `mint-condition`, `fully-renovated`, `well-kept`, `need-of-renovation`, `projected`…) and whether the listing belongs to a new-build project |
| `city`, `district`, `postalCode` | where it is. Street and house number are not returned |
| `features` | tags read from the ad's text and photo captions and from the photo types Immowelt assigns: `balcony`, `terrace`, `garden`, `fitted-kitchen`, `lift`, `cellar`, `parking`, `floor-heating`, `barrier-free`, `fireplace`. A word after "ohne", "kein" or "nicht" does not count. An empty list means the text does not mention them, not that the home has none |
| `sellerType`, `agency`, `agencyUrl` | `agency` or `private`. For agencies (businesses): the company name and its Immowelt profile page. For private landlords both are `null` and nothing identifies the landlord |
| `imageUrl`, `imageCount`, `hasFloorPlan` | the first photo, how many photos there are, and whether a floor plan is attached |
| `listedAt`, `updatedAt`, `newOnSite` | when the listing was created and last updated on Immowelt, and Immowelt's own "new" badge (on 2026-10-09, 67 of the 69 badged listings in a check of 195 across four cities had been updated within the previous 7 days) |
| `isNew`, `previousPrice`, `priceChange`, `priceChangePct`, `previousSeenAt` | the change since your previous run of the same search (`null` on the first run). `priceChange` is negative when the price dropped |
| `firstSeenAt`, `daysSinceFirstSeen`, `firstSeenPrice`, `priceChangeSinceFirstSeen` | the first run of this Actor that saw the listing, the days since, the price then, and the total change since |
| `observedAt` | when the row was read (ISO time) |

The summary row (`type: "summary"`, free) has `searchUrl` (the Immowelt page your search led to), `place`, `deal`, `propertyType`, `totalResults` (how many listings Immowelt says the search has), `listingsRead`, `listingsOutput`, `pagesRead`, `complete` (`true` when every listing of the search was read), `newSinceLastRun`, `priceDrops`, `priceIncreases`, `medianPrice`, `medianPricePerSqm`, `marketCity`, `marketAsOf`, `marketPricePerSqm`, `marketChange1YearPct`, `firstRun` and a `note` that says in words what the numbers cover. The `market…` fields are Immowelt's own average price per m² for the city and the property type of your search, with its change over one year.

### Run it again and again

- **On a schedule** — save your input as a task and add a **Schedule**: the same searches every morning. Send each run to Google Sheets, Slack or email with an integration on the task.
- **Watch for changes** — Turn on **Only new listings and price changes**.
- **In bulk** — Put every search in **Searches** in one run.

### Use it for

- **Rent and price-cut alerts** — schedule a daily run with *Only new listings and price changes* on, and send the rows to e-mail, Slack or a sheet
- **Investors and landlords** — compare asking rent and purchase price per m² by district and room count, and see which agencies list the most
- **Market research** — Immowelt's city average next to the asking prices, plus `listedAt` and `daysSinceFirstSeen` to see how long listings stay
- **Agents and valuers** — see what competing agencies list and what they have re-priced

### Input

```json
{
  "searches": [
    { "city": "Berlin", "type": "apartment", "deal": "rent" },
    { "city": "München", "type": "house", "deal": "buy" },
    "https://www.immowelt.de/suche/mieten/wohnung/sachsen/leipzig-04103/ad08de10168"
  ],
  "maxListingsPerSearch": 100,
  "onlyChanges": false
}
```

A search is either an object with `city`, `type` (`apartment` or `house`, default `apartment`) and `deal` (`rent` or `buy`, default `rent`), or an Immowelt address that starts with `immowelt.de/suche/`. Up to 50 searches per run. Write the city as you like (`München`, `Frankfurt am Main`); the Actor turns it into Immowelt's spelling. To go narrower than a city, use the address of a district page or a room-count page that Immowelt links from the city's page (for example `immowelt.de/suche/mieten/wohnung/zimmer-2/berlin/berlin-10115/ad08de8634`); those addresses work as searches too.

With **Only new listings and price changes** (`onlyChanges`) the Actor outputs, from the second run of a search on, just the listings that are new or changed in price, and you pay only for those. The first run of a search still outputs everything.

What is remembered is kept per search (its Immowelt page, not the limit) in a named key-value store in your Apify account, so the second run of the same search already shows changes. A listing that has not been seen for 45 days is forgotten. Schedule it (Apify → Schedules) for a daily history.

### Pricing

**$1.50 per 1,000 listings**, plus $0.01 per run. Error rows (a place Immowelt does not know, a page that could not be read) and the summary rows are free. With *Only new listings and price changes* on, listings that did not change are not returned and not charged. The $0.01 run-start fee is the one charge every run pays, even one that returns nothing.

### Notes

- Public data only: what anyone sees on Immowelt's public search pages without signing in. The Actor reads only pages under `immowelt.de/suche/`, which Immowelt's robots.txt does not disallow (checked 2026-10-09); it does not use the `classified-search` filter pages that robots.txt disallows. No field carries a landlord's name, phone number or e-mail address, street and house number are not returned as fields, and the ad's long text is not returned at all. For agencies only the company name is given. The one free-text field is `title`, the seller's own headline: phone numbers and e-mail addresses (with the words next to them) are cut out of it, but a seller can still type a street name into it. In my checks on 2026-10-09, 2 of 1,000 Berlin rental headlines did, and none of the 92 headlines from private landlords in that sample contained a person's name.
- **What one search gives.** Immowelt's public search page shows only the first 30 listings of a search (40 for house searches in some places, such as Berlin on 2026-10-09), always in Immowelt's own order, which is not newest first. There is no second page, no sort order and no price or area filter on the pages that can be read; those exist only on Immowelt's filter pages, which block automated reading. Because of this, **filters written into an address (`?priceMax=…`, `?page=…`) are ignored by Immowelt**, and the summary `note` lists them so you are not misled. Links that start with `immowelt.de/classified-search` are refused with a free error row.
- **How it reaches more than 30.** The page itself links to narrower pages, by room count and by district (30 each). The Actor follows them, room counts first, then districts within a room count, and removes duplicates by listing ID, until it has your limit or has read everything. When I measured it on 2026-10-09, a Berlin rental search needed about 5 pages for 100 listings, 18 for 400 and 46 for 1,000 (roughly two minutes for the last). It reads at most 80 pages per search. Searches that already have a room count read districts only. If a narrower page fails, the listings read so far are kept and a free error row says where it stopped.
- **Medians come from what was read.** Because of Immowelt's order, the listings read are not a random sample: the first 100 Berlin rentals I read on 2026-10-09 had a median of about 23 euros per m², while Immowelt's own city figure (`marketPricePerSqm`, as of 2026-10-01) was 14.63. Use the `market…` fields for the city average and the medians for the listings you got.
- **Reliable new-listing alerts.** A listing counts as new when no earlier run of this search read it. If the search has more listings than your limit, a listing can show up only because the reading window moved. Check `complete` in the summary row (`true` when everything was read) and narrow the search (a district, a room count) until it has fewer listings than your limit; `listedAt` tells you when the ad was really created.
- **Place names.** The city is turned into Immowelt's address by Immowelt itself (`muenchen`, `frankfurt-am-main`). A name that exists in many places (for example `Neustadt`) or that Immowelt spells differently is answered with a free error row; paste an Immowelt address instead.
- Some fields are empty by design: `warmRent` for purchases and for rentals that give only one rent; `plotAreaSqm` for apartments; `totalAreaSqm` unless the ad gives a total area instead of a living area; `floor` and `floorsInBuilding` for most houses and when the ad does not say; `availableFrom` and `availableImmediately` when the ad gives no move-in date; `energyClass` and `condition` when the ad gives none; `agency` and `agencyUrl` for private landlords (and `agencyUrl` for agencies without a profile page); `pricePerSqm` when there is no price or no area; `postalCode` for a few listings in which Immowelt hides it. The change fields (`previousPrice`, `priceChange`, `priceChangePct`, `previousSeenAt`, `priceChangeSinceFirstSeen`) are `null` on the first run of a search and for a listing first seen now; `isNew` is `null` on the first run only. A listing whose rent type changed (cold to warm) between runs is not compared.
- Some sellers type a placeholder price instead of a real one (for example, among 1,000 Berlin rentals on 2026-10-09: a rent of 12.23 euros for 34 m² and one of 59,152 euros for 41 m²). A median is not thrown off by a few of them, but filter them out of any average you compute yourself.
- If Immowelt rate-limits or has a short outage, the Actor waits and retries twice. If its bot protection blocks a page, that search ends with a free error row. A run that could read nothing at all fails.
- Germany only: Immowelt's pages for other countries are not read.

Looking at the UK or the US instead? See [Rightmove Property Scraper](https://apify.com/northbell/rightmove-property-scraper) for the UK and [Redfin Scraper](https://apify.com/northbell/redfin-homes-scraper) for US homes.

### For AI agents

`{"searches": [{"city": "Berlin", "type": "apartment", "deal": "rent"}], "maxListingsPerSearch": 30}` returns up to 30 listing rows with price, rooms, living area, price per m², district, agency and the changes since the last run, plus one summary row. Rows have `type`: `listing`, `summary` or `error`.

# Actor input Schema

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

One search per entry. Either an object {"city": "Berlin", "type": "apartment", "deal": "rent"} (type is apartment or house, deal is rent or buy, city as on immowelt.de with umlauts written as you like), or an Immowelt address that starts with immowelt.de/suche/ (a district or a room-count page works too). Addresses that start with immowelt.de/classified-search are Immowelt's filter pages and cannot be read.

## `maxListingsPerSearch` (type: `integer`):

How many listings to read for each search, from 1 to 1000. Immowelt's public search page shows only the first 30, so the Actor also reads the room-count and neighbourhood pages Immowelt links from your search (30 more each, duplicates removed). 100 listings is about 4 to 5 pages; 1000 can take a few minutes.

## `onlyChanges` (type: `boolean`):

From the second run of a search on, output only listings that are new since the last run or whose price changed, and pay only for those. The first run of a search still outputs every listing, as the starting list.

## Actor input object example

```json
{
  "searches": [
    {
      "city": "Berlin",
      "type": "apartment",
      "deal": "rent"
    },
    "https://www.immowelt.de/suche/mieten/wohnung/sachsen/leipzig-04103/ad08de10168"
  ],
  "maxListingsPerSearch": 100,
  "onlyChanges": false
}
```

# Actor output Schema

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

Price, rooms, living space, place, agency and the change since the last run.

# 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": [
        {
            "city": "Berlin",
            "type": "apartment",
            "deal": "rent"
        },
        "https://www.immowelt.de/suche/mieten/wohnung/sachsen/leipzig-04103/ad08de10168"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("northbell/immowelt-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 = { "searches": [
        {
            "city": "Berlin",
            "type": "apartment",
            "deal": "rent",
        },
        "https://www.immowelt.de/suche/mieten/wohnung/sachsen/leipzig-04103/ad08de10168",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("northbell/immowelt-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 '{
  "searches": [
    {
      "city": "Berlin",
      "type": "apartment",
      "deal": "rent"
    },
    "https://www.immowelt.de/suche/mieten/wohnung/sachsen/leipzig-04103/ad08de10168"
  ]
}' |
apify call northbell/immowelt-scraper --silent --output-dataset

```

## MCP server setup

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