# Immowelt Scraper: Rents, Prices and Energy Data (`automation_craft/immowelt-scraper`) Actor

Scrape Immowelt property ads in Germany and Austria by town, postal code, radius or filters, or paste search links, no login. Rows carry cold and warm rent or price, price per m2, living space, rooms, floor, energy class, place, photos and the agency's phones. Monitor new ads and price changes.

- **URL**: https://apify.com/automation_craft/immowelt-scraper.md
- **Developed by:** [Automation Craft](https://apify.com/automation_craft) (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 $0.66 / 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: Rents, Prices and Energy Data

**Immowelt scraper** for Germany's and Austria's property portal (immowelt.de and immowelt.at): search flats, houses, plots, parking,
offices and shops for rent or sale by town, district, postal code, radius or a drawn area with Immowelt's own filters, or paste Immowelt
search links, and get one row per ad with its cold rent and warm rent (or purchase price), price per m2, living space, rooms, floor,
energy class, place, agency name and phones, photos and dates. Read single ads, new build projects, an agency's ads or every agency of a
town. No login, no cookies, no captcha. Every search is read to its end, past Immowelt's 10,000 ads per order.

Who uses it: tenants, buyers and estate agents watching new listings, investors and analysts comparing rents and prices per m2 by
district, relocation and proptech services, and teams building lead lists of German and Austrian estate agencies.

### Quick start

1. Open the **Input** tab and type a place in **Places**, for example `Berlin`, `München`, `Prenzlauer Berg`, `10115` or
   `Landkreis München`.
2. Choose rent or buy and the property types, then add filters if you like: price or rent, living space, rooms, floor, energy class,
   features, seller type, keywords.
3. Or paste one or more Immowelt search links (`https://www.immowelt.de/classified-search?...` or `https://www.immowelt.de/suche/...`)
   into **Immowelt search links**.
4. Set **Maximum ads** and click **Start**. Download the rows as JSON, CSV or Excel, or read them through the Apify API.

For Austria set **Country** to `at` and type Austrian places (`Wien`, `Linz`, `1010`). To watch a search for new ads, give the run a
**Monitor name** and schedule it: each run delivers only the ads it never delivered before, and with **Track changes and removals** also
the ads whose price changed and the ads taken offline.

### What you get

One row per ad (`type: "listing"`), one row per agency (`type: "agency"`) for agency links and the directory, one coverage row per
search (`type: "search"`), free status rows for anything that could not be delivered, and a run summary (`type: "summary"`).

| Field | What it is | Filled on (measured on the 763 ads of the platform matrix of 2026-10-19, build 0.1.9; the fields are read the same way since) |
|---|---|---|
| `id`, `legacyId`, `url`, `market` | Immowelt's 12 character id, the older id (a UUID), the ad's link, de or at | every ad |
| `transaction`, `propertyType`, `propertySubType`, `isSwapApartment` | rent or buy (auction, foreclosure), Immowelt's property type and sub type, a flat offered for swap (Tauschwohnung) | every ad |
| `price`, `coldRent`, `warmRent`, `warmRentEstimated`, `priceLabel`, `pricePerSqm`, `currency` | the price shown first (the cold rent, Kaltmiete, for rentals), the cold and warm rent apart, whether Immowelt marks the warm rent as approximate, its label, the price per m2 | price 100, cold rent 97.2 and warm rent 35.8 percent of rentals |
| `livingSpace`, `plotSpace`, `rooms`, `bedrooms` | living space and plot in m2, rooms (Zimmer), bedrooms when the ad states them | living space 99.6, rooms 99.7 percent of flats and houses; bedrooms on few ads |
| `floor`, `floorsInBuilding`, `availableFrom`, `availableNow` | floor, floors of the building, the date the flat is free or "now" | floor 81 percent of flats |
| `energyClass` | the energy class letter (A+ to H) on the card | 28.3 percent of flats and houses |
| `city`, `district`, `postalCode`, `street`, `placeIds` | where the property is (the postal code as digits; empty on the rare card where Immowelt writes the town into it), the street when published, Immowelt's place codes | city every ad, postal code every ad of the 763, district 97.2 percent, street 23.9 percent |
| `title`, `headline`, `description`, `keyFacts`, `titleAdditions` | the ad's title, headline, the card's description (the whole text with Listing details), key facts and badges | every ad |
| `photos`, `photoCount`, `photoScenes`, `floorPlans`, `hasVideo`, `hasVirtualTour` | every photo link, how many, Immowelt's scene label per photo (kitchen, bathroom ...), floor plans | photos 99.7 percent |
| `createdAt`, `updatedAt`, `daysOnMarket`, `isNew` | first published, last update, whole days on the market | every ad |
| `sellerType`, `agencyName`, `agencyProfileId`, `agencyProfileUrl`, `agencyPhones`, `agencyAddress`, `agencyWebsite`, `agencyImprintUrl` | professional or private, and the agency's public contact data | of professional ads: agency name 100, phones 62.3, website 97 percent |
| `agencyRating`, `agencyReviewsCount`, `agencyBadge` | the agency's rating, number of reviews and Immowelt badge | 66.1 percent of professional ads |
| `commissionFree`, `requiresWbs`, `propertyFingerprint` | no buyer's commission, a WBS (housing entitlement) needed, a key of the property's own facts (for reposts) | every ad |
| `searchKind`, `searchLabel`, `searchPage`, `rank` | which search, page and rank delivered the row | every row |
| `listingDetails` | with **Listing details** on: the ad's full record (below) | every ad read |
| `agencyProfile` | with **Agency profiles** on: the agency's public page (below) | agencies whose page could be read |
| `monitorBaseline` | with change tracking: the monitor's own record of the version delivered (hashes), read back if the platform restarts the run | change tracking runs |
| `change`, `repost` | on a monitor's change row: what changed, the previous price; on a repost: the removed ad it repeats | change rows |
| `contactName`, `privateSellerName`, `privateSellerPhones`, `descriptionPhones`, `descriptionEmails` | an agent's own name on an agency's ad, a private seller's shown name and phones, the numbers and addresses written into the description: only with **Include personal data** on | with the switch on |

A sample row (shortened):

```json
{
  "type": "listing",
  "id": "26L7X6JMV49Q",
  "url": "https://www.immowelt.de/expose/48bcd04a-7f31-486c-aca9-d7dbd0b07e21",
  "market": "de",
  "transaction": "rent",
  "propertyType": "APARTMENT",
  "price": 640,
  "coldRent": 640,
  "warmRent": 790,
  "pricePerSqm": 25.6,
  "livingSpace": 25,
  "rooms": 1,
  "floor": null,
  "energyClass": "C",
  "city": "Frankfurt am Main",
  "district": "Schwanheim",
  "postalCode": "60529",
  "sellerType": "professional",
  "agencyName": "Wohnraum Entwicklungs GmbH",
  "agencyPhones": ["+49 69 830 48 120"],
  "photoCount": 7,
  "createdAt": "2026-10-17T13:22:52.439Z",
  "updatedAt": "2026-10-17T13:22:56.618Z",
  "daysOnMarket": 1
}
```

The coverage row of each search says how many ads Immowelt holds for it (`adsOnImmowelt`), how many results pages were read, how many
ads were delivered, left out by a filter (and by which) or already delivered, whether the search was read to its end (`complete`) and
why it stopped (`stoppedBy`).

### How much does it cost to scrape Immowelt?

You pay per event, with Apify's store discounts by plan:

| Event | Free plan | Bronze | Silver | Gold |
|---|---|---|---|---|
| Listing (one ad row) | $0.92 per 1,000 | $0.84 per 1,000 | $0.76 per 1,000 | $0.66 per 1,000 |
| Listing details (add-on, per ad row that carries the full record) | $0.92 per 1,000 | $0.84 per 1,000 | $0.76 per 1,000 | $0.66 per 1,000 |
| Agency profile (per agency row; with the add-on once per agency per run) | $0.005 | $0.0045 | $0.0042 | $0.004 |
| Results page (monitor runs; a pasted search link's first page; pages of ads to leave out) | $0.005 | $0.005 | $0.005 | $0.005 |
| Actor start (once per run) | $0.0095 | $0.0085 | $0.0075 | $0.0065 |

Platinum and Diamond plans pay the Gold price.

The Store pricing card shows these prices per 1,000 events: "$0.66 / 1,000" on the Listing row means one ad row costs $0.00066 on the
Gold plan; "$5 / 1,000" on the Results page row means one results page costs $0.005; "$4 / 1,000" on the Agency profile row means one
agency costs $0.004 on the Gold plan.

- Never billed: ads left out by your filters, ads already delivered in the run, ads a monitor already knows, reposts you skip, ads no
  longer online, invalid inputs, coverage rows, status rows and the summary.
- Results pages are billed in monitor runs: a monitor reads the newest ads since its last run each time (with change tracking, the
  whole search), so every results page it reads (up to 100 ads, each read to learn whether it is new) is billed, whatever it held. A pasted search link's first page is one
  Results page in any run (it is read to learn the search exactly as Immowelt applies it; its ads are delivered too). Searches by place,
  radius or area outside a monitor never pay it. A page that brings no ad the walk had not met yet (past 10,000 ads a search is read
  again in price and living space windows) is free.
- Excluded features and words (`excludeFeatures`, `excludeKeywords`) are read as their own searches, whose ads are left out: each of
  their results pages (up to 200 ads to leave out) is billed as a Results page, in every run (leaving out the 2,531 Berlin
  rentals with a balcony or terrace reads 13 pages: $0.065 a run, measured).
- Example on the Free plan: 1,000 ads from one search cost $0.0095 (start) + 1,000 listings ($0.92) = about $0.93. On the Gold plan the same run costs about $0.67.
- Example of a daily monitor run (Gold) that reads 2 results pages and finds 40 new ads: start ($0.0065) + 2 results pages ($0.010) + 40 listings ($0.026) = about $0.04.

### Input

| Setting | What it does |
|---|---|
| `country` | `de` (immowelt.de, default) or `at` (immowelt.at). Places, links and ad links of the other site are refused. |
| `places` | One search per place: towns (`Berlin`), districts (`Prenzlauer Berg`), counties (`Landkreis München`), states, postal codes (`10115`, Austria `1010`), resolved through Immowelt's own place lookup. Immowelt has no postal code places in Austria: a Vienna code (`1010` to `1230`) is searched as its district, any other Austrian code as the towns that list it, and every ad is checked on its own postal code (the other ads are left out free). A place that matches several is a free status row naming the candidates; add the postal code (`Neustadt 67433`). |
| `radiusKm` | A circle of that radius around each place instead of the place's own outline. |
| `searchAreas` | Circles (`{ "lat": 52.52, "lng": 13.40, "radiusKm": 3 }`) or polygons (`{ "polygon": [[52.5, 13.3], ...] }`), one search each. |
| `transaction`, `propertyTypes`, `propertySubTypes`, `projectTypes` | Rent (default), buy, auction or foreclosure; apartment, house, plot, parking, office, retail, hotel and restaurant, warehouse, agricultural, building, senior, other; Immowelt's 116 sub types; rentals: standard, new build, flatshare, swap, temporary; sales: resale, new build, investment, projected, life annuity. |
| `minPrice`, `maxPrice`, `rentPriceType` | Price or monthly rent range; for rentals the cold rent (default) or the warm rent. |
| `minLivingSpace` ... `maxYearBuilt`, `minBedrooms`, `maxBedrooms` | Living space, plot, rooms, year built and bedrooms ranges, sent to Immowelt and checked on every row (year built by Immowelt alone). Few ads state a bedroom count, so a bedroom bound keeps only those. |
| `minPricePerSqm`, `maxPricePerSqm`, `minFloor`, `maxFloor` | Price per m2 and floor ranges (checked on every row). |
| `energyClasses`, `energySources`, `features`, `excludeFeatures`, `heatingTypes`, `floors`, `excludeFloors`, `furnished`, `availableBefore`, `sellerType`, `conditions`, `wbs`, `saleWithGoodwill` | Immowelt's own filters (energy class letters or "none" for ads that show no class, balcony or terrace, garden, cellar, lift, fitted kitchen, parking, step free, pets, commission free ...), and the ads to leave out by feature or floor. Energy sources (gas, oil, district heating, heat pump, wind ...) are read from the record and need Listing details. |
| `detailFeatures`, `excludeDetailFeatures` | Features only the ad's full record shows, which Immowelt's search cannot filter: rented out (vermietet), listed building tax relief (Denkmalschutz-AfA), suitable for a flat share (WG-geeignet), a plot fully, partly or not developed (erschlossen). Need Listing details; an ad left out is free. |
| `keywords`, `keywordsMatch`, `excludeKeywords` | Words the ad must hold (all of them, or any one), and words it must not hold. |
| `postedWithinDays` or `postedWithinHours`, `minDaysOnMarket` | Only ads first published in the last N days or hours; only ads on the market for at least N days. |
| `withPhoneOnly`, `withPhotosOnly`, `excludeAdIds`, `excludeDatasetIds` | Ads with a phone number, ads with photos; ads to leave out by id or link, or every ad of your earlier datasets. |
| `sort` | Newest first (default), oldest first, relevance, price, price per m2, living space or rooms, each way. |
| `searchUrls` | Immowelt search links, read as Immowelt applies them (the search filters above do not apply to them; the row filters do). |
| `adUrls` | Single ads: links or ids (also the older UUIDs), and new build project links (`/projekte/expose/...`: every unit of the project). |
| `agencyUrls`, `includeAgencyListings` | Agency pages (`/profil/...`): one agency row each and, by default, the ads its page lists. |
| `agencyDirectory` | Towns (`Frankfurt am Main`) or directory links: one agency row for every agency Immowelt lists in the town. |
| `maxItems`, `maxItemsPerSearch`, `maxPagesPerSearch` | Caps per run, per search, per search in results pages. |
| `includeDetails`, `includeAgencyProfiles`, `includePersonalData`, `includeRawData` | The add-ons, the personal data switch (below) and Immowelt's own objects. |
| `descriptionMaxLength`, `descriptionFormat`, `omitEmptyFields`, `flattenOutput`, `outputFields` | Shorter descriptions, text, HTML or Markdown, no empty fields, dotted flat keys for spreadsheets, only the fields you list. |
| `monitorName`, `trackChanges`, `changeFields`, `includeUnchanged`, `flagReposts`, `skipReposts` | The monitor (below). |
| `webhookUrl`, `webhookSecret`, `webhookHeaders`, `telegramBotToken`, `telegramChatId`, `slackWebhookUrl`, `discordWebhookUrl`, `whatsappPhoneNumberId`, `whatsappAccessToken`, `whatsappTo`, `notifyOnlyChanges`, `notificationMaxRows` | Free notifications of the run's new rows. |
| `failIfIncomplete`, `proxyConfiguration` | Fail the run when a search could not be read to its end; Apify residential proxies (default, included in the price) or your own proxy links. |

Every filter whose field the row carries (transaction, property type, sub type, place, price or rent, price per m2, living space,
plot, rooms, bedrooms, floor, energy class, seller type, commission free, posted date, days on market, phone, photos) is checked
again on each ad before it is billed; an ad that does not match is left out free and counted on the coverage row. Year built, project
types, features, heating, positions in the building, furnished, availability, condition, WBS, goodwill and keywords are applied by
Immowelt alone, since no field of the result card shows them; a radius or drawn area is Immowelt's to apply too.

Swap flats (Tauschwohnungen) are part of Immowelt's own rental results: they are delivered and flagged `isSwapApartment`; to leave
them out, pick the other `projectTypes`.

#### Every search to its end

Immowelt serves the first 10,000 ads of one search in one order. This Actor reads them, then goes on in price order in windows that start
at the last price read, then in living space order for the ads without a price, so a search of any size is read to its end. Measured
on 2026-10-18: all 10,093 of 10,093 rentals in Bavaria in one walk, and a change tracking run of this Actor read all 10,790 Bavarian
rental flats and houses to the end in 12 minutes. The coverage row says when ads past the first 10,000 came in price
order. Immowelt has no oldest first order of its own: this Actor reads the newest first pages from the last one back for searches of up
to 10,000 ads.

#### Listing details (add-on)

With **Listing details** on, each ad row carries `listingDetails`, read from the ad's record: the whole description and its sections,
the area description, the position (latitude, longitude, geohash, and the distance from the centre of a radius search), the cost lines
(cold rent, warm rent, service charges, heating costs, deposit, purchase price, buyer's commission, parking, every price line Immowelt
prints), the energy certificate (type, validity, value, primary energy source, heating), condition, last modernisation, year built,
house type, features, floor plans, videos and virtual tours. Billed as Listing details per row; an ad taken offline meanwhile is a free
row.

#### Agency profiles (add-on) and agency rows

An agency row, and with **Agency profiles** on the `agencyProfile` of each ad row, carries the agency's public Immowelt page: phones,
fax, e-mails, website, address, logo, rating and reviews count, how many ads it lists, its description and its legal record (legal
name, register number and court, VAT number, supervisory authority, legal address). Billed once per agency per run, with the first row
that carries it.

#### Monitor: new ads, changes and removals

Give the run a **Monitor name** and schedule it. Each run reads the ads first published since its last complete run (less 24 hours of
margin; a first run reads the last 24 hours), newest first until the ads grow older than that, and delivers only the ads it never
delivered before. With **Track changes and removals**, each run reads the whole search (a change to an older ad can sit on any page of
Immowelt's list, so every page is read and billed), and an ad it delivered before comes back as a change row when its price changed (or
another field you watch: warm rent, living space, rooms, energy class, floor, description, photos), with the previous price; an ad
Immowelt no longer serves comes as a `removed` row and one that comes back as `reappeared`. With **Flag reposts** a new ad of a property
the monitor saw removed carries `repost`; with **Skip reposts** it is left out free. Change and removed rows are billed as Listing; with
**Also deliver unchanged ads** the others come free. Every results page a monitor run reads is billed. One monitor name belongs to one set
of settings; its memory never forgets an ad, so an ad is never billed twice by one monitor.

#### Personal data

Agencies' data (name, phones, e-mail, address, register and VAT numbers) is business data and always delivered. A private seller's shown
name and phone numbers, an agent's own name on an agency's ad, the numbers and addresses written into a description and an agency's
legal representatives are delivered only when you turn on **Include personal data** (`includePersonalData`). With it off those
fields are empty, and so are their copies in `rawData`. Titles, headlines and descriptions (of
the ad, its record and an agency's page) and the record's price lines are delivered as readable plain text: typographic dashes and the
minus sign become "-", the curly apostrophe and single quotes (’ ‘) become "'", special spaces become ordinary spaces, soft hyphens and
zero width spaces and joiners are removed, HTML tags and the usual HTML entities (`&amp;`, `&nbsp;`, `&lt;`, `&gt;`, `&quot;`, umlauts,
ß, €, numeric ones and a few more) become text, and runs of ordinary spaces and tabs are collapsed to one. Every other short text (the price
as shown, features, the agency's address on an ad row, reviews with their authors) gets the same character changes without the HTML
reading; a person's name field is read like a title; the address, legal address and opening hours of an agency's page come as Immowelt
writes them. Long texts are kept up to generous lengths (a title 300 characters, a description 30,000, an agency's description 5,000, a
review 3,000). No name, number or address is removed from any text, whether the switch is on or off, and `rawData` holds Immowelt's own
objects as served. You are responsible for a lawful use of personal data, GDPR included.

### What this Actor does NOT do

- **No sort by distance.** Immowelt's search has no distance order; a radius search's rows carry `listingDetails.distanceKm` (with
  Listing details) for your own sort.
- **No price history before your first monitor run.** Immowelt keeps no history and answers a removed ad with "gone"; a monitor with
  change tracking records prices and removals from its first run on.
- **No other portals.** Only immowelt.de and immowelt.at; no ImmoScout24 or Kleinanzeigen, no duplicate groups across portals, no
  prices in other currencies. Immonet now lives on Immowelt (every immonet.de address forwards there): Immonet links are refused with a
  free row saying so; search Immowelt instead.
- **No e-mails searched outside Immowelt.** Agency e-mails are the ones the agency publishes on its Immowelt page.
- **No export into connected apps (Notion and the like).** The rows go to Apify's own integrations, the API, webhooks and the chat
  notifications.
- **No prime location filter and no restructuring depreciation filter.** Immowelt's search refuses both and none of 110 ad records
  read shows either; the listed building tax relief is a filter (`detailFeatures`).
- **No street or address search.** Immowelt's place search serves towns, districts, counties, states and postal codes, no streets;
  draw a circle around an address (`searchAreas`) instead.
- **No search link Immowelt's page does not apply as written.** A pasted link whose parameter the page ignores or changes (a value in
  another case, a kind the page does not apply) is refused with a free row, never read as a wider search.
- **No login and no account data**: saved searches, alerts, messages and phone numbers behind a login are never read.
- One request at a time: 1,000 ads take about 75 seconds (measured), so the default run timeout of one hour reads about 40,000 ads;
  raise the timeout in the run options for more. `maxItems` defaults to 100.
- An agency page lists its ads in Immowelt's default list (for example 85 of 138); search the agency's town with its name as a keyword
  for the rest.

### API examples

Run it and get the rows in one call (replace `YOUR_TOKEN`):

```bash
curl -X POST "https://api.apify.com/v2/acts/automation_craft~immowelt-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"places": ["Hamburg"], "propertyTypes": ["apartment"], "maxPrice": 1500, "minRooms": 2, "maxItems": 200}'
```

Node.js:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('automation_craft/immowelt-scraper').call({ places: ['München'], transaction: 'buy', maxItems: 100 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((x) => x.type === 'listing'));
```

Python:

```python
from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("automation_craft/immowelt-scraper").call(run_input={"country": "at", "places": ["Wien"], "maxItems": 50})
ads = [x for x in client.dataset(run["defaultDatasetId"]).iterate_items() if x["type"] == "listing"]
```

### FAQ

#### How can I scrape Immowelt listings?

Type a place or paste an Immowelt search link, set a maximum, and start the run: no code, no login and no proxy setup. The rows come out
as JSON, CSV or Excel, or through the Apify API.

#### Is there an Immowelt API for property listings?

Immowelt's own interface is for its advertisers' own listings, not for reading the portal's ads. This Actor works as one: call it
through the Apify API with places, filters or search links and read the rows back as JSON.

#### How many listings can I export from one Immowelt search?

All of them, up to your `maxItems` (100 by default) and the run's timeout (one hour reads about 40,000 ads). Immowelt serves 10,000 ads of
one search in one order; this Actor goes on in price windows, so a search is read to its end, and its coverage row says how many ads
Immowelt holds and how many were delivered.

#### Does it cover Austria (immowelt.at)?

Yes. Set `country` to `at`: the same searches, filters, links, agencies and monitor on immowelt.at, at the same prices.

#### Is it legal to scrape Immowelt?

This Actor reads only what Immowelt shows any visitor, without logging in or solving any captcha. Immowelt's listings form a database
protected in Germany (section 87b UrhG: a substantial part of it, or repeated and systematic extraction of small parts, is reserved to
its maker), so you are responsible for a lawful use: the parts you need, no rebuilding of the portal, Immowelt's terms, and the GDPR
for any personal data.

#### Why does this Actor run with limited permissions?

It needs nothing more: it writes only to its own run's dataset and key-value store, a monitor keeps its memory in a named key-value
store it creates itself, and it reads only the datasets you pick in **Datasets of ads to leave out**. Limited permissions keep your
other Apify data out of its reach.

### Changelog

- 0.1 (2026-10-19): first release: searches by place, radius, area and filters in Germany and Austria, search links, ad and project
  links, agency pages and the agency directory; every search read to its end; oldest first; monitor with change tracking, removals,
  reappearances and reposts; Listing details and Agency profiles add-ons; notifications by webhook, Telegram, Slack, Discord and
  WhatsApp; private persons' data behind the personal data switch, texts unmasked (formatted as the Personal data section says); Austrian postal codes; filters
  on the features only the full record shows (rented out, listed building tax relief, plot development).

### More data tools by Automation Craft

- [SeLoger Scraper: Listings, Prices and Agencies](https://apify.com/automation_craft/seloger-scraper)
- [Kleinanzeigen Scraper & API: Ads, Prices, Places](https://apify.com/automation_craft/kleinanzeigen-scraper)
- [Leboncoin Scraper: Ads, Prices, Real Estate & Cars](https://apify.com/automation_craft/leboncoin-scraper)
- [Airbnb Scraper: Prices, Calendar, Reviews](https://apify.com/automation_craft/airbnb-scraper)
- [Mobile.de Scraper: Cars, Prices, Dealers & Ratings](https://apify.com/automation_craft/mobile-de-scraper)
- [AutoScout24 Scraper: EU Car Listings & Prices](https://apify.com/automation_craft/autoscout24-scraper)

# Changelog

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

# Actor input Schema

## `country` (type: `string`):

Which Immowelt site to read: Germany (immowelt.de, the default) or Austria (immowelt.at). Places, links and ad links of the other country are refused with a free row.

## `places` (type: `array`):

Cities, towns, districts, quarters, counties, states or postal codes, as you would type them on Immowelt: Berlin, Hamburg, Prenzlauer Berg, Landkreis München, Bayern, 10115 (Austria: Wien, Linz, 1010). A name must match exactly one Immowelt place (add the postal code when several share it: Neustadt 67433); a name that matches none or several gives a free row with the candidates. An Austrian postal code is searched as the towns that list it, each ad checked on its own postal code. Immowelt place codes (AD08DE8634) are accepted too.

## `radiusKm` (type: `number`):

Search a circle of this many km around the centre of each place instead of the place itself (Immowelt's own radius search, 1 to 100 km in steps of 0.5). A place given by its code is refused with this option (its centre is not known); give it by name.

## `searchAreas` (type: `array`):

Your own areas, one search each: a circle {"lat": 52.52, "lng": 13.41, "radiusKm": 3} or a polygon {"polygon": \[\[52.50, 13.30], \[52.50, 13.50], \[52.60, 13.40]]} (3 to 200 \[lat, lng] corners). Up to 20 areas, inside Germany or Austria.

## `transaction` (type: `string`):

Ads for rent, for sale, foreclosure auctions or other auctions. Default rent.

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

Default apartment and house.

## `propertySubTypes` (type: `array`):

Only these sub types, as Immowelt names them (penthouse, loft, maisonette types, single_family_house, terrace_house, villa, studio, flatsharing_room ...). Checked again on every row that shows its sub type.

## `projectTypes` (type: `array`):

Immowelt's own kinds of offer. Rent: standard, new_build, flatshare, swap, temporary. Buy: resale, new_build, investment, projected, life_annuity. Default every kind (Immowelt's own default rental search includes apartment swap ads, Tauschwohnung, which are many in some cities; leave out swap to skip them).

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

The lowest purchase price, or the lowest monthly cold rent (Kaltmiete) of a rental (the warm rent with the next setting).

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

The highest purchase price, or the highest monthly cold rent (Kaltmiete) of a rental (the warm rent with the next setting).

## `rentPriceType` (type: `string`):

Rentals with a minimum or maximum price: which rent the range applies to.

## `minPricePerSqm` (type: `integer`):

The lowest price per square metre, checked on every row (Immowelt's own figure on a sale, else the price divided by the living space). An ad without one is left out, free.

## `maxPricePerSqm` (type: `integer`):

The highest price per square metre, checked on every row. An ad without one is left out, free.

## `minLivingSpace` (type: `integer`):

The smallest living space (Wohnfläche) in square metres.

## `maxLivingSpace` (type: `integer`):

The largest living space in square metres.

## `minPlotSpace` (type: `integer`):

The smallest plot (Grundstück) in square metres.

## `maxPlotSpace` (type: `integer`):

The largest plot in square metres.

## `minRooms` (type: `number`):

The fewest rooms (halves allowed: 2.5).

## `maxRooms` (type: `number`):

The most rooms (halves allowed: 3.5).

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

The fewest bedrooms. Few Immowelt ads state a bedroom count (new build units mostly), and Immowelt leaves out every ad without one: this narrows a search to a small part of its ads.

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

The most bedrooms. Only ads that state a bedroom count are kept (see the minimum).

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

The earliest year of construction (Immowelt applies it; the year shows on rows with Listing details).

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

The latest year of construction.

## `minFloor` (type: `integer`):

Only apartments on this floor or higher (0 = ground floor), checked on every row; an ad that shows no floor is left out, free.

## `maxFloor` (type: `integer`):

Only apartments on this floor or lower, checked on every row; an ad that shows no floor is left out, free.

## `energyClasses` (type: `array`):

Only ads with one of these energy efficiency classes (checked on every row). With "none" (ads that show no class, which Immowelt cannot search) the classes are checked in code only, so more results pages are read.

## `energySources` (type: `array`):

Only ads whose heating runs on one of these energy sources (Energieträger). The source shows only in the ad's full record, so this needs Listing details: the record of each candidate is read and an ad whose record names another source, or none, is left out free (its details read is not billed).

## `detailFeatures` (type: `array`):

Only ads whose full record shows every one of these features. Immowelt's search has no filter for them, so this needs Listing details: the record of each candidate is read and an ad whose record does not show the feature is left out free (its details read is not billed).

## `excludeDetailFeatures` (type: `array`):

Leave out ads whose full record shows any of these features (no rented out flats, for instance). Needs Listing details; an ad left out is free (its details read is not billed), an ad whose record does not show the feature is kept.

## `features` (type: `array`):

Only ads with every one of these features.

## `excludeFeatures` (type: `array`):

Leave out ads with any of these features (no swimming pool ads, no assisted living). The search narrowed to each feature is listed first and its ads are left out (not billed as rows); each results page of that list (up to 200 ads) is billed as a Results page. A narrowed search above 10,000 ads refuses that search with a free row.

## `heatingTypes` (type: `array`):

Only ads with one of these heating types.

## `floors` (type: `array`):

Only apartments on these positions in the building.

## `excludeFloors` (type: `array`):

Leave out apartments on these positions (no ground floor flats).

## `furnished` (type: `string`):

Rentals only.

## `availableBefore` (type: `string`):

Rentals available on or before this date (YYYY-MM-DD).

## `availableBeforeIncludeUndated` (type: `boolean`):

With Available before: also rentals that state no availability date (Immowelt's own loose mode).

## `sellerType` (type: `string`):

Private sellers or professionals only (checked on every row).

## `conditions` (type: `array`):

Only ads in one of these conditions, as Immowelt records them.

## `wbs` (type: `string`):

Rentals that need a housing entitlement certificate (WBS), or those that do not.

## `saleWithGoodwill` (type: `string`):

Sales of commercial property: only those Immowelt marks as a business sale with goodwill, or none of them.

## `keywords` (type: `array`):

Words Immowelt looks for in the ad text (Balkon, Altbau, Garten). Up to 10, each up to 60 characters.

## `keywordsMatch` (type: `string`):

With several keywords: all of them (default), or any of them (each place or area is then searched once per keyword, an ad found twice delivered once).

## `excludeKeywords` (type: `array`):

Leave out ads whose text holds any of these words, judged by Immowelt's own text search (the same as Keywords): the search narrowed to each word is listed first and its ads are left out (not billed as rows); each results page of that list (up to 200 ads) is billed as a Results page. Up to 10.

## `postedWithinDays` (type: `integer`):

Only ads first published in the last N days. The search is read newest first and stops once a page's newest ad is well older than that.

## `postedWithinHours` (type: `integer`):

Only ads first published in the last N hours (instead of days). Not with a monitor, which reads every ad updated since its last run.

## `minDaysOnMarket` (type: `integer`):

Only ads first published at least N days ago. Each row also carries daysOnMarket.

## `withPhoneOnly` (type: `boolean`):

Only ads whose card shows a phone number.

## `withPhotosOnly` (type: `boolean`):

Only ads that show at least one photo.

## `excludeAdIds` (type: `array`):

Immowelt ad ids, older ids (UUIDs) or links you already have; they are skipped, free. Up to 10,000.

## `excludeDatasetIds` (type: `array`):

Ids of your Apify datasets (an earlier run of this Actor, or any dataset whose rows carry an Immowelt ad id, older id or link in "id", "legacyId" or "url"): their ads are skipped, free. Up to 10 datasets and 200,000 ads; a dataset that cannot be read stops the run before any paid work. Only the datasets picked here are readable to the run (limited permissions).

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

The order of a search. Newest first sorts on the ad's last update; oldest first reads a search of up to 10,000 ads from its last page back (Immowelt has no such order of its own), a larger one in price order. Past the first 10,000 ads of one order the search goes on in price order. A pasted link keeps its own order unless you set one here. Default newest first.

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

The most paid rows the run delivers (ad rows, price change and removed rows, agency rows together). Default 100.

## `maxItemsPerSearch` (type: `integer`):

The most rows one search, link or agency delivers.

## `maxPagesPerSearch` (type: `integer`):

The most results pages (up to 200 ads each) one search reads, the pages that list the ads to leave out included.

## `searchUrls` (type: `array`):

Search links copied from Immowelt: a filtered search (https://www.immowelt.de/classified-search?...) or a search page without filters (https://www.immowelt.de/suche/mieten/wohnung/...). Every page is read. A link that carries a parameter Immowelt's search does not know, a page above 1, or filters in a /suche/ address (Immowelt ignores them there) is refused with a free row. Immonet links are refused: Immonet was folded into Immowelt on 2026-01-23.

## `adUrls` (type: `array`):

Ads to read directly: Immowelt ad links (expose, wl-cdp), 12 character ids, older ids (UUIDs), or new build project links (https://www.immowelt.de/projekte/expose/...), whose units are delivered as ads. An ad Immowelt no longer has is a free row. Up to 5,000.

## `agencyUrls` (type: `array`):

Agency pages on Immowelt (https://www.immowelt.de/profil/<id>) or their ids. Each gives one agency row and, with the switch below, the agency's ads.

## `includeAgencyListings` (type: `boolean`):

For each agency link, also deliver the ads its page lists (its default list, usually its sale ads; the coverage row gives the agency's counts). Default on.

## `agencyDirectory` (type: `array`):

Towns whose real estate agencies you want (Berlin, Frankfurt am Main) or Immowelt directory links (https://www.immowelt.de/makler/berlin). Every agency of the town's directory page gives one agency row (Agency profile), in the page's order.

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

Read each delivered ad's full record: the whole description in its sections and the area description, GPS position (of the address when published, else the centre of the area Immowelt shows), deposit, service charges, heating and operating costs, purchase price, buyer commission and purchase costs, the energy certificate (type, validity, class and value), heating and energy source, year built, condition, last modernisation, every feature, floor plans and videos. Billed as Listing details per row.

## `includeAgencyProfiles` (type: `boolean`):

Add the agency's public profile to its ad rows: e-mail, phones, fax, website, register number and court, VAT number, description, listing counts, reviews. Read and billed once per agency per run, with its first row.

## `includePersonalData` (type: `boolean`):

Off by default. On: a private seller's shown name and phone numbers (privateSellerName, privateSellerPhones), an agent's own name on agency ads (contactName), the phone numbers and e-mail addresses found in the description (descriptionPhones, descriptionEmails) and an agency's legal representatives (legalRepresentative) are delivered as Immowelt shows them logged out. Off: those fields are empty, and so are their copies in rawData (the card's contact title when it names a person, a private seller's phone numbers, the record's contact block). With the switch on or off, titles, headlines, descriptions and the record's price lines are delivered as readable plain text (typographic dashes, the minus sign, the curly apostrophe and single quotes (’ ‘) and special spaces turned into plain ASCII, soft hyphens and zero width spaces and joiners removed, HTML tags and the usual HTML entities turned into text, runs of ordinary spaces and tabs collapsed to one), up to generous lengths; every other short text (the price as shown, features, an ad row's agency address, reviews) gets the same character changes without the HTML reading; an agency page's address, legal address and opening hours come as Immowelt writes them; nothing is masked; rawData keeps Immowelt's objects as served. The README's Personal data section has the details. You are responsible for using personal data lawfully (GDPR included).

## `includeRawData` (type: `boolean`):

Add Immowelt's own card object (and the ad's record with Listing details) to each ad row as rawData. With personal data off, the person fields in it are left out (see includePersonalData). Larger rows; no extra charge.

## `descriptionMaxLength` (type: `integer`):

Cut the description texts of each row at this many characters (at a word, marked ...). Default the whole text.

## `descriptionFormat` (type: `string`):

The description is always plain text; html or markdown adds descriptionHtml or descriptionMarkdown.

## `omitEmptyFields` (type: `boolean`):

Leave out the fields of an ad row that are empty (null, an empty text or list). The fields that identify the row are always kept.

## `flattenOutput` (type: `boolean`):

Write nested objects as dotted keys (listingDetails.energy.energyClass), handy for spreadsheets.

## `outputFields` (type: `array`):

Keep only these top level fields of an ad row (price, coldRent, livingSpace, url ...); the fields that identify the row and the add-on data you pay for (listingDetails, agencyProfile) are always kept.

## `monitorName` (type: `string`):

A short name of your choice (letters, digits, spaces and . \_ , - : # ( ), up to 60 characters). One name belongs to one set of settings.

## `trackChanges` (type: `boolean`):

With a monitor: an ad it delivered before whose price (or another watched field, below) changed is delivered again with what changed, an ad Immowelt no longer serves is reported as removed and a removed ad that comes back as reappeared (all billed as Listing). Removals need every id of the searches listed each run, every results page billed.

## `changeFields` (type: `array`):

With trackChanges: which changes of a known ad deliver it again (billed as Listing). A price change row carries the previous price, the change in euros and percent and the price history. Default price only.

## `includeUnchanged` (type: `boolean`):

With trackChanges: known ads met again without a watched change are delivered too, free, marked unchanged (they never count toward the maximum). Not with Energy sources: an unchanged ad is read from its card alone, so its energy source cannot be checked.

## `flagReposts` (type: `boolean`):

With trackChanges: a new ad that describes the same property as an ad this monitor reported removed (same kind, transaction, postal code, living space to the m2, rooms and published street) carries repost.repostOf with the removed ad's id and last price. Billed as a new ad.

## `skipReposts` (type: `boolean`):

With trackChanges: such a repost is left out, free (counted in the coverage row), instead of flagged.

## `webhookUrl` (type: `string`):

An https address the run posts its new rows to (JSON, up to 100 rows a call, at most 1,000 a run, with a plain text summary in the field "text").

## `webhookSecret` (type: `string`):

Optional: each call then carries an x-signature-sha256 header, the HMAC SHA-256 of the body with this secret (16 to 200 letters, digits and - \_ . ~, with at least one letter and one digit).

## `webhookHeaders` (type: `object`):

Optional: up to 5 headers sent with each webhook call ({"Authorization": "Bearer ..."}), each value 8 to 500 characters; treated as secrets.

## `slackWebhookUrl` (type: `string`):

A Slack incoming webhook address (https://hooks.slack.com/...): the run posts its first new rows as messages, one line per ad (at most 30 lines).

## `discordWebhookUrl` (type: `string`):

A Discord channel webhook address (https://discord.com/api/webhooks/...): the run posts its first new rows as messages, one line per ad (at most 30 lines).

## `whatsappPhoneNumberId` (type: `string`):

The phone number id of your WhatsApp Business app (WhatsApp Cloud API); with the token and the recipient below, the run sends one message per new row (at most 30). WhatsApp delivers free text only inside a conversation the recipient opened in the last 24 hours.

## `whatsappAccessToken` (type: `string`):

The access token of your WhatsApp Business app.

## `whatsappTo` (type: `string`):

The phone number the messages go to, in international form (+491701234567).

## `telegramBotToken` (type: `string`):

Your own bot's token from BotFather; the run sends one message per new row (at most 30).

## `telegramChatId` (type: `string`):

The chat id (a number) or channel name (@name) your bot posts to.

## `notifyOnlyChanges` (type: `boolean`):

With trackChanges: the notifications carry only change and removed rows, not new ads.

## `notificationMaxRows` (type: `integer`):

The most rows each notification channel sends (default: 1,000 for the webhook, 30 for the chat channels).

## `failIfIncomplete` (type: `boolean`):

Mark the run failed when a search, link or agency could not be read to its end for a reason other than your own limits. The rows delivered stay.

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

Apify residential proxies are used by default and included in the price (Germany unless you choose a country). Immowelt refuses datacenter addresses, so other Apify groups are refused; your own proxy links (http://user:password@host:port, the password 12 characters or more with letters and digits, no space) can be given instead.

## Actor input object example

```json
{
  "places": [
    "Berlin"
  ],
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `items` (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 = {
    "places": [
        "Berlin"
    ],
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/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 = {
    "places": ["Berlin"],
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("automation_craft/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 '{
  "places": [
    "Berlin"
  ],
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call automation_craft/immowelt-scraper --silent --output-dataset

```

## MCP server setup

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