# Immowelt Scraper - German Property Listings & Prices (`s-r/immowelt-scraper`) Actor

Scrape Immowelt property listings for sale or rent by search URL, city or postcode. Export expose ids, addresses, prices, cold and warm rent, floor area, rooms, estate agencies and photos for market research and price monitoring.

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

## Pricing

$0.70 / 1,000 property listings

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

## Immowelt Scraper

Immowelt Scraper collects German property listings for sale and rent from an Immowelt search URL, city, district or postcode. Export structured listing rows with expose ids, addresses, prices, floor area, rooms, estate agencies and photos for market research, price monitoring and lead lists. No Immowelt account or login is needed, and every run is also available as an API endpoint, so it works as an Immowelt API for reading listings from code, spreadsheets or automation tools.

### What the Immowelt Scraper returns

Each row represents one listing in the requested search. The actor keeps the order of the search results and identifies properties by their expose id. New construction projects can contain several units with the same project URL, so use `listing_id` when comparing collections. Recommendations outside your search are excluded. If a listing record is unavailable, the actor skips that record and reports a warning rather than creating an empty property row.

The output includes the displayed address, postcode, city and district. A street appears only when Immowelt publishes it. A withheld postcode is returned as null; the address then contains the published city and district. Photos and agency fields can be absent, and missing values remain null. A property with a price available on request is still a valid listing and has a null numeric price together with its original German price label.

### Input

Provide either `url`, `location` or `location_id`. A supplied URL takes precedence over location and search filters. Only the `/classified-search` URL format supports this collection. Copy that URL from an Immowelt search; older `/liste` and `/suche` links and individual expose URLs are not supported.

A location can be a German city, district or postcode. The actor uses the first matching location returned by Immowelt. A supplied `location_id` takes precedence over the location name. For ambiguous place names, a postcode or the portal's exact location id gives more predictable results.

This input corresponds to the real Munich apartment search used in the verified examples:

```json
{
  "location_id": "AD08DE6345",
  "operation": "buy",
  "property_type": "apartment",
  "max_items": 10
}
```

Choose `operation: "buy"` or `"rent"`. Property types are `apartment`, `house`, `house_or_apartment` and `plot`. The default is apartments for sale. `radius_km` adds a search radius between zero and 100 kilometres when using a location or location id.

Optional price, area and room filters use `price_min`, `price_max`, `area_min`, `area_max`, `rooms_min` and `rooms_max`. Supply whole nonnegative numbers. Minimum values must not exceed their corresponding maximum values. Price filters for rental searches refer to monthly cold rent. Area filters use square metres. Rooms mean the German Zimmer count, which can include the living room; they are not a bedroom count.

`sort` supports `default`, `newest`, `price_asc`, `price_desc`, `area_asc` and `area_desc`. Omit it to preserve an order already present in a supplied search URL. A URL's starting page is also preserved. `max_items` defaults to 100 and accepts one through 10,000. It limits unique delivered listings across the complete search, including any continuation requests.

### Output

The default dataset contains the listing rows. The default key-value store's OUTPUT record contains run metadata, collection status and warnings. You can export dataset rows as JSON, CSV or Excel through Apify and use them in spreadsheets or downstream workflows.

Core fields include `listing_id`, `url`, `listing_type`, `address`, `street`, `city`, `postal_code`, `state`, `country_code`, `price`, `currency`, `property_type`, `living_area`, `living_area_unit`, `lot_area`, `lot_area_unit`, `broker` and `image_url`. Collection context includes `source`, `platform`, `query`, `position` and `fetched_at`.

Additional fields include the listing title and headline, description teaser, total rooms, district, original price wording, photos, first floor plan, agency profile and website, energy class, feature flags and original creation and update timestamps. The description is the search-card teaser and can be shorter than the full expose description. The actor does not request individual property details.

For rental properties, `price` is monthly Kaltmiete and `warm_rent` is Warmmiete when the source provides it. Both amounts are in EUR. This distinction matters when comparing offers: a rental property's total monthly payment can exceed its advertised cold rent. The actor preserves both values instead of mixing them in one price column.

Private sellers have `seller_type: "private"` and null agency fields. Private contact names and telephone numbers are not included. Agency telephone numbers are also outside this version's output.

#### Example Immowelt scraper row: an apartment for sale in Munich

This is the first agency listing from a real run of the Munich input shown above, collected on 5 October 2026. Only the `images` list (all gallery photos) and the long `description` teaser are left out to keep it readable.

```json
{
  "source": "immowelt-scraper",
  "platform": "immowelt",
  "fetched_at": "2026-10-05T17:19:51.150Z",
  "listing_id": "26M58VRNUQZ6",
  "url": "https://www.immowelt.de/expose/5d68d765-59aa-4419-85c3-ffedd9df9f15",
  "listing_type": "for_sale",
  "query": "AD08DE6345",
  "position": 1,
  "address": "81541 München (Obergiesing)",
  "street": null,
  "city": "München",
  "postal_code": "81541",
  "district": "Obergiesing",
  "state": "Bavaria",
  "country_code": "DE",
  "currency": "EUR",
  "price": 539990,
  "living_area": 77,
  "living_area_unit": "m2",
  "lot_area": null,
  "lot_area_unit": "m2",
  "property_type": "APARTMENT",
  "rooms": 2,
  "broker": "ImmoFerreira",
  "image_url": "https://mms.immowelt.de/7/a/8/1/7a81cce3-cc2e-4b1b-862e-ca17afc2e1c4.png",
  "title": "Wohnung zum Kauf",
  "price_text": "539.990 €",
  "price_per_m2_text": "7.013 €/m²",
  "warm_rent": null,
  "headline": "Urban wohnen, entspannt ankommen - Ihr Freiraum in Obergiesing",
  "key_facts": [
    "2 Zimmer",
    "77 m²",
    "4. Geschoss"
  ],
  "floorplan_url": "https://mms.immowelt.de/1/a/2/b/1a2b2653-f208-4874-85d9-9dd21b12fd13.png",
  "seller_type": "agency",
  "broker_url": "https://www.immowelt.de/profil/23228c2060974d849486996550447827",
  "broker_logo_url": "https://mms.immowelt.de/9/6/0/f/960f62c5-5b2d-4ba1-9fd6-5447c5aa1407.jpg",
  "broker_address": "Bürgerplatz 5, 85748 Garching",
  "broker_website": "https://immo-bayern-muenchen.de/",
  "broker_rating": 3.3,
  "broker_reviews": 4,
  "energy_class": "C",
  "is_new_build": false,
  "has_floor_plan": true,
  "has_virtual_tour": true,
  "has_video": false,
  "has_brokerage_fee": false,
  "created_at": "2026-09-19T11:14:38.103Z",
  "updated_at": "2026-09-21T07:20:06.08Z",
  "legacy_id": "5d68d765-59aa-4419-85c3-ffedd9df9f15",
  "geo_ids": [
    "AD08DE6345",
    "AD05DE13",
    "AD06DE225",
    "AD04DE9",
    "NBH2DE91301008",
    "AD02DE1"
  ],
  "page": 1
}
```

### Collection limits and continuation

Apify follows continuation cursors automatically when a search needs several collection requests. The actor keeps delivered ids between requests to avoid duplicate rows and charges. API clients using the response directly can resubmit the same search fields with `cursor` set to `next_cursor`.

Immowelt reports a total result count that can exceed the listings accessible through one search. Its search depth is approximately 10,000 results; rental searches, including houses and mixed house/apartment searches, and apartment and plot searches expose at most 333 complete pages of 30. Sale searches including houses expose 250 pages of 40. Divide wider searches by location, price or area. A warning identifies a depth stop. Results can change while collection runs, so the reported total is not a guaranteed final dataset size.

### Use cases

- **Rent and price monitoring.** Run the same city or postcode search every week and join the datasets on `listing_id` to see new listings, removed listings and asking-price changes.
- **Market research for German cities.** Compare asking price per square metre, cold and warm rent and room counts across districts such as Berlin Mitte or Munich Obergiesing.
- **Investor and buyer alerts.** Filter by price, floor area and rooms, sort by newest and feed the results into a sheet, a chat channel or your own matching logic.
- **Estate agency lead lists.** Every agency listing carries the agency name, profile, address, website and rating, so you can see which agencies are active in which postcodes.
- **Combining German portals.** Use the same row layout next to ImmoScout24 data to cover more of the German property market and deduplicate by address, area and price.

### How to scrape Immowelt without code

1. Open Immowelt, run the search you want (city, buy or rent, property type and filters) and copy the `/classified-search` URL from the address bar, or just type a city or postcode into `location`.
2. Paste it into the `url` field of this actor and set `max_items`.
3. Start the run and download the dataset as JSON, CSV or Excel, or call the run through the Apify API and schedule it.

### Immowelt scraper compared with other options

| option | what you get | limits |
|---|---|---|
| Copying listings by hand | Exactly what you see on the page | Slow; no history, no structured prices or areas |
| Immowelt's own interface | Documented for commercial providers who publish their own listings | Not a public search API for reading the market; its own usage terms apply |
| Writing your own scraper | Full control | You maintain parsing, pagination and blocking yourself |
| This Immowelt Scraper | Structured rows with cold and warm rent, area, rooms, agency and photos; pay per listing | Search-card data only; full expose details and contact phone numbers are not included |

Next to ImmoScout24, Immowelt is one of the large German property portals. Agencies do not always advertise on both, so collecting both portals gives a wider view of the market; deduplicate on address, area and price when you combine them.

### Pricing

The listing event counts each unique property row delivered to the dataset. An empty search or a source error that delivers no rows has no listing charge. There is no separate custom run-start event. Photos, agency fields and rental-price fields included on a delivered row do not trigger additional events. The current Store pricing panel is the authoritative published price; the configured price proposal is $0.70 per 1,000 listings.

Non-paying users are limited to ten delivered listings per run. To collect larger searches, use a paid Apify account and increase `max_items`. That limit controls the requested number of results; it cannot make properties beyond Immowelt's accessible search depth available.

### FAQ

#### Is there an official Immowelt API?

Immowelt documents an interface for commercial providers who publish their own listings. It is not a public search API for reading listings across the market. This actor fills that gap: you can start it through the Apify API and read the dataset as JSON.

#### Can I export Immowelt listings to Excel or CSV?

Yes. Every dataset can be downloaded as JSON, CSV, Excel, XML or HTML from the run page, or read through the Apify API. Nested values such as `key_facts` and `images` become separate columns in CSV and Excel.

#### Can I search by postcode instead of city?

Yes. Set `location` to the postcode, or provide its exact Immowelt `location_id`. For example, the verified 10115 rental fixture resolves to `POCODE216`.

#### Why is an address or photo missing?

Some sellers do not publish the street or postcode, and some search records have no photo gallery. Null means the requested field was not present in the search response. A missing optional field does not remove the listing. An address without a postcode still contains the published city and district.

#### Does the actor return bedrooms and full property descriptions?

It returns the source's total room count as `rooms` and the search-card description. Bedrooms, bathrooms and full expose descriptions are not supplied by this version. Do not interpret Zimmer as bedrooms.

#### Why did my run return fewer listings than requested?

The search may contain fewer listings, some records may be unavailable, or the portal's accessible depth may have been reached. Check collection warnings and the run output. A temporary failure during a continuation can leave the collection incomplete; retry the search or resume with the provided cursor.

#### Can repeated runs track price changes?

Yes. Save successive datasets and join them on `listing_id`, then compare price and timestamp fields. The actor returns current search results for each run; it does not manufacture historical prices or property transactions.

# Actor input Schema

## `url` (type: `string`):

Immowelt classified-search URL; takes precedence over location and filters. Its page and order are preserved unless sort is explicitly supplied.

## `location` (type: `string`):

German city, district or postcode, resolved by Immowelt autocomplete.

## `location_id` (type: `string`):

Immowelt place id, for example AD08DE6345 or POCODE216. Takes precedence over location.

## `operation` (type: `string`):

Buy or rent. Rental price filters apply to monthly cold rent.

## `property_type` (type: `string`):

Property types to search.

## `radius_km` (type: `integer`):

Search radius in kilometres; used with location or location_id.

## `price_min` (type: `integer`):

EUR asking price or monthly cold rent: minimum.

## `price_max` (type: `integer`):

EUR asking price or monthly cold rent: maximum.

## `area_min` (type: `integer`):

Living area in square metres: minimum.

## `area_max` (type: `integer`):

Living area in square metres: maximum.

## `rooms_min` (type: `integer`):

Total rooms (Zimmer), not bedrooms: minimum.

## `rooms_max` (type: `integer`):

Total rooms (Zimmer), not bedrooms: maximum.

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

Result order. Omit to preserve the order in a supplied URL; otherwise source default.

## `max_items` (type: `integer`):

Maximum unique listings across the search, including cursor continuation.

## `cursor` (type: `string`):

Opaque next_cursor from a previous response. Resubmit the same search fields. Apify follows this automatically.

## Actor input object example

```json
{
  "location_id": "AD08DE6345",
  "operation": "buy",
  "property_type": "apartment",
  "radius_km": 0,
  "max_items": 100
}
```

# Actor output Schema

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

One row per unique delivered expose id.

## `output` (type: `string`):

Collection metadata, warnings 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 = {
    "location_id": "AD08DE6345"
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/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 = { "location_id": "AD08DE6345" }

# Run the Actor and wait for it to finish
run = client.actor("s-r/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 '{
  "location_id": "AD08DE6345"
}' |
apify call s-r/immowelt-scraper --silent --output-dataset

```

## MCP server setup

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