# willhaben Scraper: Immobilien, Cars, Jobs & Ads (`automation_craft/willhaben-scraper`) Actor

willhaben.at Marktplatz, Immobilien, Auto & Motor and Jobs in one run by keyword, filter, search link, seller, employer or ad link, no login. Rows hold price, m2, rooms, make, mileage, salary; full records add costs, energy class, car equipment, VAT id. New ad alerts, pay per row, JSON, CSV, API.

- **URL**: https://apify.com/automation_craft/willhaben-scraper.md
- **Developed by:** [Automation Craft](https://apify.com/automation_craft) (community)
- **Categories:** Real estate, E-commerce, Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

### willhaben Scraper: Immobilien, Cars, Jobs & Ads

**willhaben scraper** for willhaben.at, Austria's classifieds site: search all four sections in one run, Marktplatz (19 top and
184 sub categories), Immobilien (all 12 property kinds, rent and buy), Auto & Motor (cars, motorbikes, vans and trucks, caravans
and motorhomes) and Jobs, by keyword, by willhaben's own filters, by a pasted willhaben search link, by seller, by employer page or
by ad and job link. Each ad or job is one row with its price or salary, place, map point, seller type, photos and the section's
facts (living area, rooms, make, mileage, fuel, employment type). Turn on full records for the whole description, every attribute,
the rent and running costs, energy figures, car equipment, the seller's or company's facts and the job's apply link. No login, no
cookies, no captcha. A monitor delivers only what it never delivered before, with price changes and removals if you like, and can
send it to Telegram, Slack, Discord, WhatsApp or your own webhook.

Who uses it: flat hunters and property investors watching new Vienna, Graz or Salzburg listings, car dealers following private car
ads, resellers watching Marktplatz bargains, recruiters and salary analysts reading Austrian job ads, and teams feeding willhaben
data into their own sheets, apps or AI agents.

### Quick start

1. Open the **Input** tab and pick the **Sections**: `marktplatz`, `immobilien`, `auto`, `jobs` (several at once if you like).
2. Narrow them: **Property types** such as `mietwohnungen` or `haus-kaufen`, **Vehicle types**, **Marktplatz categories**
   (`Fahrräder / Radsport`), **Keywords** (`fahrrad`, `3 zimmer balkon`, `golf 7`), **States** (`Wien`, `Steiermark`) and
   **Districts** (`Graz`, `1030` or `Wien 3`).
3. Or paste willhaben links: search pages into **Search links** (`https://www.willhaben.at/iad/gebrauchtwagen/auto/gebrauchtwagenboerse?PRICE_TO=5000`),
   seller numbers into **Sellers**, employer pages into **Employer pages**, ad and job links into **Ad and job links**.
4. Add filters (price, living area, rooms, make, model, year, mileage, fuel, job field, employment type, salary ...), set **Maximum
   rows** and click **Start**. Download the rows as JSON, CSV or Excel, or read them through the Apify API.

The prefilled input reads 20 Vienna rentals: measured, 4 seconds and 2 requests (run IrGNo2zqp4g2Y8oTO). To receive new ads only,
give the run a **Monitor name** and schedule it (daily or more often).

### What you get

One row per ad or job (`type: "listing"`), one coverage row per search (`type: "search"`), free status rows for anything that could
not be delivered (`type: "status"`), a run summary (`type: "summary"`), and in a monitor with **Track changes** a row per ad that
disappeared (`type: "removed"`). The dataset view **Ads and jobs** keeps the most useful columns.

Fill rates below are counted on platform runs of 2026-10-20 (the private test build before the Codex review; the released build's runs of the same day on the real actor show the same fill). Search rows: 600 Marktplatz ads for `fahrrad` (run
yyjFRNX6lIbm1tZI8), 20 Vienna rentals (the prefill, run IrGNo2zqp4g2Y8oTO), 90 houses for rent in Burgenland (run
Dk4xVVKRZd2YO8U57), 10 dealer motorbikes (run hngm2O2Afeh5ql12t) and 270 jobs (run XY8bh00sWvV4Zb4z2). Rows with full records: 60
Vienna flats for sale (run b05yRSWKrccbckTHY), 10 rentals in Vienna 3 (run FzGmMWOJ2sfLfM5Mo), 10 diesel Golfs (run
Jj2jrM94lHNISSI1x: 9 private and a dealer's paid first slot, which the released build leaves out: run TZFqMLlp80aa8dsiV, 10 private), 8 new phones (run
hRKxRCUlb7faPbA9D) and 5 full time jobs in Vienna (run ZtV5UJQcuqleOkIl6).

#### Ads (Marktplatz, Immobilien, Auto & Motor)

| Field | What it is | Filled on (measured) |
|---|---|---|
| `adId`, `adUuid`, `url`, `title`, `section`, `subSection` | willhaben's ad id, the ad's link and title, the section and sub section (`Wohnung mieten`, `Gebrauchtwagen`) | every row |
| `descriptionSnippet`, `descriptionComplete` | the text the search shows (cut at about 257 characters with "..."), and whether it is the whole description | every search row; complete on 0 of the 20 rentals |
| `description` (with `descriptionText`, `descriptionMarkdown` in format `all`) | the whole description, as HTML (the default), text or markdown | every row with a full record (60 of 60, 10 of 10, 8 of 8) |
| `price`, `priceText`, `currency` | price in euros (for rentals the rent) and as willhaben writes it (`€ 596,57`) | 600 of 600 `fahrrad`, 20 of 20 rentals, 58 of 60 flats for sale |
| `oldPrice`, `priceReducedAt` | the earlier price of a reduced ad and the day it was reduced | 45 of 600 `fahrrad` |
| `pricePerSquareMeter` | price per m2 | 20 of 20 rentals, 56 of 60 flats for sale |
| `postcode`, `location`, `district`, `state`, `country` | where the ad is | postcode 600 of 600, district 573 of 600, state 599 of 600 `fahrrad`; every Immobilien row |
| `municipality` | the municipality | with a full record (60 of 60) |
| `latitude`, `longitude`, `coordinatesRounded` | the ad's map point; rounded to 2 decimals (about 1 km) on a private seller's ad with personal data off | 597 of 600 `fahrrad`, every Immobilien row |
| `distanceKm` | the distance from your **Latitude** and **Longitude** | 10 of 10 with a point given (run Ad3n04nDYOD3nQ6b1); empty without one |
| `street` | the ad's street: business sellers' ads, private ones only with **Include personal data** | 146 of the 210 business ads of `fahrrad`, 0 of the 390 private ones |
| `publishedAt`, `isBumped` | the last time the ad was published or bumped, and whether it was bumped | every row; bumped 54 of 60 flats for sale |
| `firstPublishedAt`, `createdAt` | the first publication and the creation time | with a full record (60 of 60) |
| `changedAt`, `endsAt` | last change and the end of the ad's run time | 600 of 600 Marktplatz search rows; with a full record on every section |
| `isTopAd`, `status` | willhaben's top ad and the status (`active`, `reserved`) | every row; reserved 2 of 90 houses |
| `sellerType` | `private` or `business` | every row (210 business of 600 `fahrrad`, 17 of 20 rentals) |
| `sellerId`, `sellerName` | the seller's number (ORGID) and name: business sellers always, private ones with personal data on | 210 of 600 `fahrrad`, 17 of 20 rentals (the business sellers) |
| `sellerMemberSince`, `sellerActiveAds` | since when the seller is on willhaben and how many ads they have | every row with a full record |
| `sellerPhone`, `sellerAddress`, `sellerWebsite`, `sellerProfileUrl`, `sellerLogoUrl`, `sellerOpeningHours`, `sellerEmail` | a business seller's contact data from the full record | phone and address 58 of the 58 business flats for sale, website 56; opening hours 1 of 1 car dealer; e-mail on none of the measured rows |
| `images`, `imageCount`, `mainImageUrl`, `thumbnailUrl` | photo links (never downloaded) | 597 of 600 `fahrrad`, every Immobilien row |
| `floorPlans`, `attachments` | floor plan and attachment links of the full record | floor plans 1 of 60 flats for sale |
| `categoryIds`, `categoryPath`, `teaser` | willhaben's category ids and names, the short facts the search shows | category names 600 of 600 `fahrrad`; Immobilien search rows carry ids only, the names come with a full record |
| `attributes` | every willhaben attribute of the row (personal ones behind the switch) | every row |
| `propertyType`, `livingAreaM2`, `rooms`, `usableAreaM2`, `floor`, `outdoorAreas`, `outdoorAreaM2`, `projectId`, `virtualTourUrl` | Immobilien | rooms 20 of 20 rentals and 90 of 90 houses; living area 20 of 20 and 88 of 90; outdoor areas 15 of 20 and 76 of 90 |
| `rentNetPerMonth`, `additionalCostsNet`, `totalMonthlyCost`, `vatPerMonth`, `commissionText`, `availableFrom`, `rentalTerm`, `ownershipType` | Immobilien money facts of the full record | net rent and total cost 7 of 10 rentals, commission text 8 of 10 rentals and 47 of 60 flats for sale, available from 8 of 10 |
| `energyHwb`, `energyHwbClass`, `energyFgee`, `energyFgeeClass`, `constructionYear`, `buildingCondition`, `heating`, `features` | energy figures and the building, from the full record | HWB 10 of 10 rentals and 50 of 60 flats for sale, construction year 42 of 60, heating 40 of 60, features 57 of 60 |
| `make`, `model`, `modelSpecification`, `mileageKm`, `powerKw`, `fuel`, `bodyType`, `condition` | Auto & Motor | 10 of 10 cars; motorbike search rows 10 of 10 (fuel 9) |
| `firstRegistrationYear`, `firstRegistrationMonth`, `transmission`, `owners`, `seats`, `doors`, `colour`, `warranty`, `drive`, `equipment` | more car facts | 10 of 10 cars with a full record (owners 9); `colour` is willhaben's colour code (`14`); motorbike search rows carry no year and no transmission |
| `tradeInPossible`, `financingPossible`, `conditionReport` | trade in, financing, a condition report | every vehicle row |
| `condition`, `delivery`, `itemAttributes`, `buyerProtection` | Marktplatz: condition (`Neu`), handover, every attribute of the ad page, PayLivery | condition, handover and attributes 8 of 8 with a full record; buyer protection every Marktplatz row |

#### Jobs

| Field | What it is | Filled on (measured) |
|---|---|---|
| `jobId`, `jobUuid`, `url`, `title` | willhaben's job id, the job's link, its title | every row |
| `companyName`, `companyId`, `companyJobsUrl`, `companyLogoUrl` | the employer and its willhaben employer page | 270 of 270 (logo 264) |
| `location`, `locations`, `state`, `country` | where the job is | location 270 of 270; state and country 159 of 270 (a town below district level gets none) |
| `salary`, `salaryPeriod`, `salaryPeriodText`, `overpay` | the single amount willhaben shows, its period (`month`, `year`, `hour`), willhaben's wording, the overpay flag | salary 257 of 270 (225 a month, 21 an hour, 11 a year); overpay true on 124 |
| `employmentModes`, `employmentTime`, `position` | `Vollzeit`, `Teilzeit` ...; from when; the position level | 270, 269 and 270 of 270 |
| `publishedAt`, `lastReorderAt`, `lastModifiedAt`, `createdAt` | first publication, the last reorder and change, creation (Vienna times as UTC) | 270 of 270 |
| `isTopJob`, `isBrandNew`, `internalApplication` | willhaben's paid top job, brand new, apply on willhaben | every row; top jobs 12 of 270 |
| `description` | the job text: on search rows only for top jobs | 12 of 270 search rows; 5 of 5 with a full record |
| `companyVatId`, `companyIndustry`, `companyEmployeesFrom`, `companyEmployeesTo`, `companyFoundingYear`, `companyWebsite`, `companyAddress`, `companyActiveAds`, `companyType` | company facts of the full record | VAT id, website, address 5 of 5; industry, size, founding year 4 of 5 |
| `expiresAt`, `applyUrl`, `externalApplication` | end date and the apply link | 5 of 5 with a full record |
| `contactName`, `contactEmail`, `contactPhone`, `contactSalutation` | the job's contact person | only with **Include personal data** |

#### On every row

`detailsIncluded` (whether the full record was read), `source` (`search`, `link`, `employer`), `searchKey`, `searchLabel`,
`positionInSearch`, `contentHash` (a short hash of title, price and text, to tell a changed row from an unchanged one), `change`
(monitor runs with **Track changes**), `possibleRepostOf` and `isRepost` (with **Mark reposts**), `monitorName`, `deliveryId`,
`actorRunId`, `actorTaskId`, `scrapedAt`. With **Extract contacts from texts**: `emails`, `phoneNumbers`, `websites`,
`socialProfiles`. With **Include raw data**: `raw` (willhaben's own search row and full record).

A search row (shortened; a private seller, personal data off, run IrGNo2zqp4g2Y8oTO):

```json
{
  "type": "listing", "section": "immobilien", "subSection": "Wohnung mieten",
  "adId": "1207135601",
  "url": "https://www.willhaben.at/iad/immobilien/d/mietwohnungen/wien/wien-1150-rudolfsheim-fuenfhaus/nachmieter-gesucht-moderne-1-zimmer-wohnung-nahe-schoenbrunn-32-m-vollmoeblierte-uebernahme-moeglich-1207135601/",
  "title": "Nachmieter gesucht | Moderne 1-Zimmer-Wohnung nahe Schönbrunn | 32 m² | Vollmöblierte Übernahme möglich",
  "price": 596.57, "priceText": "€ 596,57", "pricePerSquareMeter": 18.64,
  "postcode": "1150", "location": "Wien, 15. Bezirk, Rudolfsheim-Fünfhaus", "state": "Wien",
  "latitude": 48.19, "longitude": 16.32, "coordinatesRounded": true,
  "publishedAt": "2026-10-20T20:27:18.321Z", "isBumped": false, "status": "active",
  "sellerType": "private", "sellerId": null, "sellerName": null, "imageCount": 7,
  "propertyType": "Wohnung", "livingAreaM2": 32, "rooms": 1, "floor": "1",
  "detailsIncluded": false, "source": "search", "searchLabel": "in Wohnung mieten"
}
```

A car with its full record (shortened; a private seller, personal data off, run Jj2jrM94lHNISSI1x):

```json
{
  "type": "listing", "section": "auto", "subSection": "Gebrauchtwagen", "adId": "1312174365",
  "title": "VW Golf Variant Comfortline 1.6 TDI BlueMotion DSG Kombi / Family Van",
  "price": 6800, "postcode": "6179", "location": "Ranggen", "district": "Innsbruck-Land", "state": "Tirol",
  "sellerType": "private", "sellerName": null, "sellerMemberSince": "2022-06-07T22:00:00.000Z",
  "firstPublishedAt": "2026-10-06T19:00:14.000Z", "publishedAt": "2026-10-20T19:00:08.000Z",
  "make": "VW", "model": "Golf", "modelSpecification": "Variant Comfortline 1.6 TDI BlueMotion DSG",
  "firstRegistrationYear": 2015, "mileageKm": 24900, "fuel": "Diesel", "transmission": "Automatik", "powerKw": 77,
  "bodyType": "Kombi / Family Van", "condition": "Gebrauchtwagen", "owners": 3, "warranty": false,
  "equipment": ["Regensensor", "Freisprecheinrichtung", "Nichtraucherfahrzeug", "Mittelarmlehne"],
  "detailsIncluded": true
}
```

A job with its full record (shortened, run ZtV5UJQcuqleOkIl6):

```json
{
  "type": "listing", "section": "jobs", "jobId": "13282518",
  "url": "https://www.willhaben.at/jobs/job/kuechenleitung-m-w-d/13282518",
  "title": "Küchenleitung (m/w/d)", "companyName": "Mömax Österreich GmbH", "companyVatId": "ATU63841588",
  "location": "Wien, 21. Bezirk, Floridsdorf", "state": "Wien",
  "salary": 3500, "salaryPeriod": "month", "salaryPeriodText": "monatlich", "overpay": false,
  "employmentModes": ["Vollzeit"], "employmentTime": "ab sofort", "position": "Gruppen-/Teamleitung",
  "publishedAt": "2026-10-20T08:31:04.000Z", "expiresAt": "2027-10-19", "externalApplication": true,
  "contactName": null, "detailsIncluded": true
}
```

The coverage row of each search says which filters were sent (`filtersSent`), how many ads willhaben counts for it
(`adsOnWillhaben`), how many pages and rows were read, how many ads were met and delivered, how many were left out and by which
filter (`filteredOutBy`), how many a monitor already knew (`knownToMonitor`), whether the search was read to its end (`complete`) and
why it stopped (`stoppedBy`). From a real run (houses for rent in Burgenland up to 1,200 euros, the first run of a monitor, run
Dk4xVVKRZd2YO8U57):

```json
{ "type": "search", "searchLabel": "in Haus mieten", "section": "immobilien", "sort": "newest first (last published or bumped)",
  "filtersSent": ["areaId=1", "PRICE_TO=1200"], "adsOnWillhaben": 90, "pagesRead": 1, "rowsRead": 90, "distinctAdsMet": 90,
  "adsBeyondReach": 0, "adsDelivered": 90, "alreadyDelivered": 0, "knownToMonitor": 0, "changesDelivered": 0, "filteredOut": 0,
  "filteredOutBy": {}, "removedOrGone": 0, "detailsFailed": 0, "newAdsMayLieBeyond": false, "complete": true, "stoppedBy": "end" }
```

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

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

| Event | Free plan | Bronze | Silver | Gold |
|---|---|---|---|---|
| Listing (one ad or job row) | $0.99 per 1,000 | $0.99 per 1,000 | $0.98 per 1,000 | $0.845 per 1,000 |
| Listing details (a full record read and merged into a row) | $3.50 per 1,000 | $3.20 per 1,000 | $2.90 per 1,000 | $2.50 per 1,000 |
| Results page (monitor runs only, one search page of up to 200 ads or 90 jobs) | $4.20 per 1,000 | $3.80 per 1,000 | $3.50 per 1,000 | $3.00 per 1,000 |
| Actor start (once per run) | $0.0075 | $0.0075 | $0.0075 | $0.0075 |

Platinum and Diamond plans pay the Gold price. The Actor start is a flat fee per run, with no plan discount.

The Store pricing card shows these prices per 1,000 events: "$0.99 / 1,000" on the Listing row means one ad or job row costs
$0.00099, less than a tenth of a cent.

- **Listing** is charged once per ad or job delivered as a row, from a search, a seller, an employer page or a link. In a monitor
  with **Track changes** it is also charged per row delivered again because it changed or came back, and per removed row; with
  **Deliver unchanged ads too**, per unchanged row.
- **Listing details** is charged per delivered row whose full record was read and merged: with **Include details** on, and on every
  ad and job link row (a link is always read with its full record, so it pays Listing plus Listing details). A removed or unknown
  record is free.
- **Results page** is charged only in a monitor run (**Monitor name** set): one per search page read, whatever it held, so a monitor
  pays for checking even on a day with nothing new. A read that failed or that willhaben did not apply is free. Runs without a
  monitor never pay it.
- Never billed: repeats in the run, rows left out by a filter, ads a monitor already delivered, removed or unknown links, invalid
  input, coverage rows, status rows, the summary and every notification.
- Example on the Bronze plan: 1,000 search rows cost $0.0075 (start) + 1,000 listings ($0.99) = $0.9975. The Free plan pays the
  same; the Silver plan $0.9875, the Gold plan $0.8525.
- Example with full records on the Gold plan: 100 flats cost $0.0075 + 100 listings ($0.0845) + 100 listing details ($0.25) =
  $0.342.
- Example of 50 ad links on the Gold plan: $0.0075 + 50 listings ($0.04225) + 50 listing details ($0.125) = $0.17475.
- Example of a daily monitor run on the Gold plan whose search holds fewer than 200 ads, 20 of them new: start ($0.0075) + 1 results
  page ($0.003) + 20 listings ($0.0169) = $0.0274. A day with nothing new: $0.0105 (measured: the second run of the monitor above,
  run 7jlCBiHLhjunC48z2, charged 1 start and 1 Results page and delivered 0 rows, the 90 known houses free).
- The prefilled run of 20 Vienna rentals costs $0.0075 + 20 listings ($0.0198) = $0.0273 on the Free plan.
- A run without a monitor that delivers nothing (a keyword with no ads, a filter no ad passes) pays only the start: $0.0075.

### Input

| Setting | What it does |
|---|---|
| `sections` | `marktplatz`, `immobilien`, `auto`, `jobs`: each searched on its own, once per keyword. Left empty, the section comes from the section settings you give, else Marktplatz. |
| `keywords` | Search words, each its own search (up to 200, 100 characters each). |
| `propertyTypes` | Immobilien: `all`, `mietwohnungen`, `eigentumswohnung`, `haus-mieten`, `haus-kaufen`, `grundstuecke`, `neubauprojekte`, `gewerbe-mieten`, `gewerbe-kaufen`, `ferien-mieten`, `ferien-kaufen`, `sonstige`, each its own search. |
| `vehicleTypes` | Auto & Motor: `cars`, `motorbikes`, `commercial`, `caravans` (default cars; with sellers all four). |
| `marktplatzCategories` | A Marktplatz category by its willhaben name or id: a top category (`Sport / Sportgeräte`) or one of its sub categories (`Fahrräder / Radsport`). |
| `states`, `districts` | The nine states and `andere Länder`; districts and the towns that are districts (`Graz`, `St. Pölten`, `Graz-Umgebung`), Vienna's districts as `1030`, `Wien 3` or `Landstraße`. Case, umlauts written out and hyphens do not matter. |
| `latitude`, `longitude`, `radiusKm` | A point in Austria and a radius of 1 to 300 km: ads farther away or without a map point are left out free (with no sort chosen, ad searches then read nearest first); job searches send the radius to willhaben. |
| `minPrice`, `maxPrice`, `sellerType` | Price (for rentals the rent) in euros; private or business sellers (at willhaben on Marktplatz and Auto & Motor, checked row by row on Immobilien). |
| `postedWithinDays`, `postedAfter`, `postedBefore` | willhaben's own window (1 to 365 days; jobs 1, 3 or 7), and dates (YYYY-MM-DD, Vienna time) checked on every row. |
| `minLivingArea`, `maxLivingArea`, `minPlotArea`, `maxPlotArea`, `rooms`, `objectTypes`, `outdoorAreas`, `propertyFeatures`, `availableNow`, `newBuildOnly` | Immobilien filters as willhaben names them (`Dachgeschoßwohnung`, `Balkon`, `Einbauküche` ...). |
| `makes`, `models`, `minYear`, `maxYear`, `minMileageKm`, `maxMileageKm`, `minPowerKw`, `maxPowerKw`, `fuelTypes`, `transmissions`, `bodyTypes`, `vehicleConditions` | Auto & Motor filters (`VW`, `Golf`, `Diesel`, `Automatik`, `Kombi / Family Van`, `Jahreswagen` ...). |
| `marktplatzCondition`, `delivery`, `buyerProtectionOnly` | Marktplatz: condition (`Neu`, `Gebraucht` ...), pickup or shipping, PayLivery only. |
| `jobFields`, `employmentTypes`, `positionLevels`, `employerType` | Jobs: fields of work and their sub fields, `Vollzeit`, `Teilzeit` ..., position levels, direct employers or staffing agencies. |
| `minSalary`, `maxSalary`, `salaryPeriod` | Jobs: a salary range checked on every job row, per month (default), year or hour; monthly and yearly pay compared at 14 salaries a year. Jobs without a salary are left out free. |
| `attributeFilters` | Any other filter of the search's own willhaben filter list by its German label or id: `{"Farbe": ["Schwarz"], "Anzahl der Sitze": {"min": 7}}`. |
| `sort` | Left empty: newest first (nearest first with a radius; a pasted link keeps its own order). Or `newest`, `relevance`, `priceLow`, `priceHigh`, `nearest`, `areaLow`, `areaHigh`, `mileageLow`, `mileageHigh`, `yearNew`, `yearOld`; jobs newest or relevance. |
| `includeKeywords`, `requireAllKeywords`, `excludeKeywords`, `titleOnly`, `excludeAdIds` | Whole words the title or description must hold (one or all) or must not hold, in the title only if you like; ad or job ids you already have. Checked on every row. |
| `searchUrls` | willhaben search links of any section (`/iad/...` and `/jobs/suche`), read with all their filters (up to 200). |
| `sellerIds` | A seller's number (ORGID) or a private seller's profile link: the seller's ads in Marktplatz, Immobilien and Auto & Motor (up to 200). |
| `employerUrls` | Employer pages (`https://www.willhaben.at/jobs/firma/<name>/<id>`): all open jobs of each (up to 200). |
| `adUrls` | Ad links, ad ids or job links (up to 5,000), each read with its full record. A removed or unknown one is free and gets a status row, up to the run's budget of free rows (20 plus one per billed event); past it the summary counts them. |
| `maxItems`, `maxItemsPerSearch`, `maxPagesPerSearch`, `maxRowsPerSearch` | Paid rows per run (default 100), rows per search, pages per search, rows read per search (a monitor's window, 200 by default). |
| `includeDetails`, `includePersonalData`, `includeRawData` | Full records (billed as Listing details), the personal data switch, willhaben's own objects. |
| `descriptionFormat`, `descriptionMaxLength`, `compactOutput`, `excludeEmptyFields`, `extractContacts`, `phoneExtractionMode` | Description as `html` (default), `text`, `markdown` or `all`; a length cut; core fields only; empty fields left out; e-mails, phones, websites and social profiles found in the texts, phones `strict` or `lenient`. |
| `monitorName`, `resetMonitor`, `trackChanges`, `emitUnchanged`, `markReposts`, `skipReposts` | The monitor (below). |
| `telegramBotToken`, `telegramChatId`, `slackWebhookUrl`, `discordWebhookUrl`, `whatsappAccessToken`, `whatsappPhoneNumberId`, `whatsappTo`, `webhookUrl`, `webhookSecret`, `webhookHeaders`, `notificationLimit`, `notifyOnlyChanges` | Free notifications of the rows a run delivered (below). |
| `failIfIncomplete`, `maxFailedRequestsPercent`, `proxyConfiguration`, `requestsPerSecond`, `maxConcurrency`, `maxRequestRetries` | Fail the run when a search is incomplete or too many reads failed; your own proxy links; the pace of each of the two exits (0.2 to 2 requests a second, default 1), requests in flight (1 to 4, default 2), retries of a failed answer (0 to 5, default 2). |

A run reads at most 200 searches (sections times their types times keywords, plus search links, sellers and employer pages).

#### Every filter is checked

willhaben ignores a filter it does not know without saying so and answers the whole search. So every filter this Actor sends must
come back in willhaben's own answer; when one does not, the search delivers nothing and a free row names the filter. A filter value
the search's own filter list does not offer (a make, a district, a category) is a free row that lists the values it does offer, and
the search delivers nothing: an unknown value never widens a search. Every range and the seller type sent are checked again on
every row and on the full record. willhaben's paid first slot (a dealer's or agency's ad on top of the first page, which can
ignore the filters sent) is left out free; when that ad does match your search, it is delivered at its own place in the list.

#### Depth and order

Ad searches read pages of up to 200 ads, jobs pages of 90. willhaben serves at most 199,800 ads of one search (999 pages); newest
first, the default, reads past it by willhaben's own date ranges, every ad once. Other orders end at the 199,800 ads, and the
coverage row counts the ads beyond (`adsBeyondReach`). Newest first follows willhaben's publish or bump time, so an older ad that
was bumped today stands among today's ads (`isBumped`).

#### Full records

With **Include details** on, each delivered row's full record is read and merged: the whole description, every attribute, the
seller's member since date and ad count, a business seller's phone, address, website and opening hours, the ad's municipality,
property costs and energy figures, floor plans, car equipment, Marktplatz item attributes, and for jobs the company facts (VAT id,
industry, size, founding year), the end date and the apply link. It is billed as Listing details, per row whose record was read and
merged. Ad and job links are always delivered with their record.

#### Monitor: only new ads, with price changes and alerts

Give the run a **Monitor name** and schedule it. Each run reads the newest 200 rows of every search (**Rows read per search**),
pays a Results page per page read (one page of up to 200 ads for an ad search, up to three pages of 90 for a jobs search), and delivers only the ads
and jobs this monitor never delivered before; each is delivered and billed once per monitor, and the ones it knows are free. With
**Track changes**, a known ad comes again when its price changed (`priceChanged`, with the previous and the first seen price and a
price history), its title, images or place changed (`updated`) or it came back (`reappeared`), and when a run read every search to
its end, each tracked ad it no longer met is a `removed` row (held back when more than half and more than 5 are missing at once).
**Mark reposts** marks a new ad whose title, price and seller match one the monitor delivered (`possibleRepostOf`); **Skip reposts**
leaves them out free. **Reset the monitor** starts it empty. One monitor name belongs to one set of settings: a run with other
settings under the same name delivers nothing and says why. A monitor remembers up to 60,000 ads and jobs.

Notifications are free and list the rows the run delivered, in a monitor run or any other: Telegram through your own bot (one
message per row, up to **Rows per message** rows, default 10, at most 30, then a line naming the rest), one Slack, Discord or
WhatsApp Cloud API message listing up to that many rows, and a webhook that receives up to 1,000 rows per run in JSON batches of
100, signed with HMAC SHA-256 (header `x-signature-sha256`) when you give a secret, with up to 10 headers of your own. **Notify only
new and changed** leaves unchanged rows out. A failed notification never fails the run.

#### Personal data

With **Include personal data** off (the default), a private seller's ad has its seller id, seller name and street null, its map point rounded to about 1 km and the distance in whole kilometres, and those attributes are left out of
`attributes`; the contact person's name of any ad (a business's included) and a job's contact person (name, e-mail, phone) are
null (`contactName`, `contactEmail`, `contactPhone`, `contactSalutation`); contacts found in the texts of private ads and job ads
(**Extract contacts from texts**) are null, and so are the raw copies of a private ad and of a job (`raw`). Turning the switch on adds
all of these, the exact map point and the distance to 10 m. Contacts found in a business ad's own text (an agent's work e-mail or
mobile number) are business contact details and are delivered with the switch off. A seller whose kind is not known is treated
as private. A business seller's data (dealers, agencies, shops: name, phone, address, website, opening hours) is business data and
always delivered. Titles and descriptions are delivered exactly as willhaben serves them, so an ad's own text can name a person or
give a phone number. You are responsible for a lawful use of personal data, GDPR included.

#### How long a run takes

Searches go through Apify's datacenter proxy on one sticky session; willhaben's www pages (full records, job searches, employer
pages) through one Apify residential session in Austria, included in the price. Each of the two exits keeps its pace (**Requests
per second**, default 1, with 2 in flight). Measured on the platform:

- Search rows are fast: 600 Marktplatz rows in 8 seconds (5 requests), 270 jobs in 7 seconds (3 pages of 90), the 20 row prefill in
  4 seconds.
- Full records take about a second each at the default pace: 60 flats for sale with their records in 74 seconds, 10 cars in 22
  seconds, 5 jobs in 9 seconds. So 1,000 rows with full records take about 20 minutes.
- 5 ad links (3 ads, 2 unknown ids) took 13 seconds; a monitor's second run with nothing new took 5 seconds.

When willhaben refuses a request, the Actor pauses and asks once more on the same address; a second refusal in a row stops that
source with a free status row. It never switches to fresh addresses to get past a refusal (only a broken proxy connection draws a
new session, at most twice a run per exit). What was delivered stays delivered, and every search or link not reached is free and
counted (a status row each, up to the run's budget of free rows).

### What this Actor does NOT do

- **No other sites.** Only willhaben.at.
- **No login and no account data.** No saved searches (Suchagent), favourites or messages are read; nothing is posted, no message
  or contact form is sent.
- **No hidden phone numbers.** A business seller's phone comes from the full record when willhaben shows it; private sellers show
  none, and nothing behind a click or a login is read.
- **No photo downloads.** Photo links only.
- **No salary range for jobs.** willhaben shows one amount per job; `salary` is that amount, with no maximum.
- **No radius filter at willhaben for ads.** The radius is checked on each row from the ad's map point, which can be the centre of
  its place rather than its address (in a 3 km search around Stephansplatz all 10 measured rows stood 0.01 km from it), and a
  private seller's point is rounded to about 1 km with personal data off.
- **No view counts.** willhaben shows none in the data this Actor reads.
- **No pasted links sorted by distance.** A willhaben link with its own `sort=2`, `lat` and `lon` is refused with a free row; set
  **Latitude**, **Longitude** and **Radius** instead (with no sort chosen, the search is then read nearest first).
- **No Notion connector input.** Apify's own integrations send a run's dataset to Notion, Google Sheets and others, and the free
  webhook, Slack, Discord, Telegram and WhatsApp alerts cover the same destinations.
- **No unpaid crawling.** A run stops with a status row when its unpaid work (pages whose rows were all filtered out or already
  delivered, removed or unknown links, the reads that check a search's filters) passes a small budget (USD 0.0012 plus a share of
  every billed event): narrow the search or loosen the filters checked on each row.

### API and integrations

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

```bash
curl -X POST "https://api.apify.com/v2/acts/automation_craft~willhaben-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"sections": ["immobilien"], "propertyTypes": ["eigentumswohnung"], "districts": ["Graz"], "minLivingArea": 50, "maxPrice": 400000, "maxItems": 100}'
```

Node.js:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('automation_craft/willhaben-scraper').call({ sections: ['auto'], makes: ['VW'], models: ['Golf'], fuelTypes: ['Diesel'], sellerType: 'private', minYear: 2015, includeDetails: true, maxItems: 50 });
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/willhaben-scraper").call(run_input={"sections": ["jobs"], "keywords": ["koch"], "states": ["Wien"], "monitorName": "koch-wien", "includeDetails": True})
jobs = [x for x in client.dataset(run["defaultDatasetId"]).iterate_items() if x["type"] == "listing"]
```

The Actor also runs from n8n, Make and Zapier through their Apify integrations, from AI agents through Apify's MCP server, and on a
schedule in the Apify Console.

### FAQ

#### How can I scrape willhaben listings?

Pick a section (Marktplatz, Immobilien, Auto & Motor or Jobs), type keywords or paste a willhaben search link, set **Maximum rows**
and start the run: no code, no login and no proxy setup. Each ad or job comes as one row with its price or salary, place, seller
type and the section's own facts, as JSON, CSV or Excel or through the Apify API.

#### Is there a willhaben API?

willhaben documents no public API for its ads and jobs. This Actor works as one: call it through the Apify API with sections,
keywords, filters, search links, sellers, employer pages or ad links and read the rows back as JSON, in one call with
`run-sync-get-dataset-items`.

#### How do I get alerts for new willhaben ads?

Give the run a **Monitor name** and schedule it: each run delivers only the ads and jobs this monitor never delivered before and can
send them to Telegram, Slack, Discord, WhatsApp or your own webhook at no extra charge, with no willhaben account. A monitor run pays
a Results page per search page it checks, so a day with nothing new costs the start fee plus those reads.

#### Can I export willhaben property listings to Excel?

Yes. Download the run's dataset as Excel, CSV, JSON or XML from the Output tab or the API. Rental and sale rows carry price, price
per m2, living area, rooms, postcode and district; with **Include details** also net rent, running costs, commission text and the
energy figures (HWB, fGEE).

#### Do willhaben job rows include the salary?

Yes, when willhaben shows one: the job row carries the single amount willhaben shows (`salary`), its period (month, year or hour)
and the overpay flag; 257 of 270 measured job rows had a salary. **Minimum salary** and **Maximum salary** are checked on every job
row, monthly and yearly pay compared at 14 salaries a year.

#### 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, and a monitor keeps its memory in a named
key-value store it creates itself. Limited permissions keep your other Apify data out of its reach.

### Changelog

- 0.1 (2026-10-21): first release: Marktplatz, Immobilien, Auto & Motor and Jobs in one run; keywords, the site's filters checked
  against its own answer, search links, sellers, employer pages, ad and job links; every range and the seller type checked on each
  row; newest first past 199,800 ads; full records; a monitor with change tracking, removals, reposts and free notifications;
  private sellers' data behind the personal data switch.

### More data tools by Automation Craft

- [Kleinanzeigen Scraper & API: Ads, Prices, Places](https://apify.com/automation_craft/kleinanzeigen-scraper)
- [Marktplaats Scraper: Ads, Cars, Sellers & Alerts](https://apify.com/automation_craft/marktplaats-scraper)
- [Leboncoin Scraper: Ads, Prices, Real Estate & Cars](https://apify.com/automation_craft/leboncoin-scraper)
- [Immowelt Scraper: Rents, Prices and Energy Data](https://apify.com/automation_craft/immowelt-scraper)
- [Kijiji Scraper: Listings, Cars, Rentals & Prices](https://apify.com/automation_craft/kijiji-scraper)
- [Craigslist Scraper: Listings, Prices & Alerts](https://apify.com/automation_craft/craigslist-scraper)
- [OfferUp Scraper: Local Listings, Prices, Sellers](https://apify.com/automation_craft/offerup-scraper)
- [Mercari US Scraper: Listings, Sold Prices](https://apify.com/automation_craft/mercari-us-scraper)
- [AutoScout24 Scraper: EU Car Listings & Prices](https://apify.com/automation_craft/autoscout24-scraper)
- [Mobile.de Scraper: Cars, Prices, Dealers & Ratings](https://apify.com/automation_craft/mobile-de-scraper)

# Changelog

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

# Actor input Schema

## `sections` (type: `array`):

The willhaben sections to search: marktplatz (general goods), immobilien (real estate), auto (cars, motorbikes, commercial vehicles, caravans), jobs. Each section is searched on its own, once per keyword. Left empty, the section is taken from the section settings you give (property types, vehicle types, a category), else Marktplatz.

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

Search words, each one its own search (for example "fahrrad", "3 zimmer balkon", "golf 7"). Left empty, the section is searched without words. Up to 100 characters each.

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

Immobilien only: which property searches to read, each its own search. "all" is every property ad. Rooms, living area and some other filters exist only for a property type, not for "all".

## `vehicleTypes` (type: `array`):

Auto & Motor only: which vehicle searches to read, each its own search. Left empty: cars (Gebrauchtwagen); with sellerIds: all four.

## `marktplatzCategories` (type: `array`):

Marktplatz only: a category by its willhaben name or id, each its own search: a top category ("Sport / Sportgeräte") or one of its sub categories ("Fahrräder / Radsport"). A name several categories share is refused with the candidates.

## `states` (type: `array`):

The Austrian states to search in ("Wien", "Niederösterreich" or "Niederoesterreich", "Steiermark", ...), or "andere Länder" for ads abroad. Works in every section.

## `districts` (type: `array`):

Districts (Bezirke), the towns that are districts included: "Graz", "St. Pölten", "Salzburg", "Graz-Umgebung", and Vienna's districts as "1030", "Wien 3" or "Landstraße". Case, umlauts written out and hyphens do not matter. For a smaller town use a radius around its coordinates. Every section.

## `latitude` (type: `number`):

With "longitude" and "radiusKm": the point a radius is measured from, in Austria (for example 48.2082 for Vienna centre). Ads farther away, or without coordinates, are left out free; job searches send the radius to willhaben.

## `longitude` (type: `number`):

With "latitude" and "radiusKm" (for example 16.3738 for Vienna centre).

## `radiusKm` (type: `integer`):

The distance from latitude and longitude, in kilometres (1 to 300). The sort "nearest" ends a search at the radius.

## `minPrice` (type: `number`):

Marktplatz, Immobilien and Auto & Motor: the lowest price (for rentals the rent) in euros, sent to willhaben.

## `maxPrice` (type: `number`):

The highest price (for rentals the rent) in euros, sent to willhaben.

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

Private sellers or businesses (dealers, agencies, shops). Marktplatz and Auto & Motor filter at willhaben; Immobilien rows are checked one by one.

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

Only ads published or bumped within this many days (1 to 365, willhaben's own window). On jobs: 1, 3 or 7.

## `postedAfter` (type: `string`):

A date (YYYY-MM-DD, Vienna time): only rows published or bumped (jobs: published or reordered) on or after it. Checked on every row.

## `postedBefore` (type: `string`):

A date (YYYY-MM-DD, Vienna time): only rows published or bumped on or before it. Checked on every row.

## `minLivingArea` (type: `number`):

Immobilien, a property type: the smallest living area in square metres.

## `maxLivingArea` (type: `number`):

The largest living area in square metres.

## `minPlotArea` (type: `number`):

Houses and plots: the smallest plot (Grundfläche) in square metres.

## `maxPlotArea` (type: `number`):

The largest plot in square metres.

## `rooms` (type: `array`):

Number of rooms as willhaben groups them: "1", "2", "3", "4", "5", "6-9", "ab 10".

## `objectTypes` (type: `array`):

Object types as willhaben names them ("Dachgeschoßwohnung", "Erdgeschoßwohnung", "Einfamilienhaus", "Reihenhaus", "Baugrundstück", ...).

## `outdoorAreas` (type: `array`):

Balkon, Dachterrasse, Garten, Loggia, Terrasse, Wintergarten (any of them).

## `propertyFeatures` (type: `array`):

Ausstattung as willhaben names it: Abstellraum, Barrierefrei, Carport, Einbauküche, Fahrstuhl, Garage, Keller, Parkplatz, "Teilmöbliert / Möbliert".

## `availableNow` (type: `boolean`):

Only properties available at once ("ab sofort").

## `newBuildOnly` (type: `boolean`):

Only new build projects (Neubauprojekte) within the property type.

## `makes` (type: `array`):

Auto & Motor: makes as willhaben names them ("VW", "BMW", "Mercedes-Benz", "KTM" for motorbikes).

## `models` (type: `array`):

Models of the makes given ("Golf", "Polo", "3er-Reihe"); needs "makes". A model name two of your makes share is refused with the candidates.

## `minYear` (type: `integer`):

The earliest year of first registration.

## `maxYear` (type: `integer`):

The latest year of first registration.

## `minMileageKm` (type: `integer`):

The lowest mileage in kilometres.

## `maxMileageKm` (type: `integer`):

The highest mileage in kilometres.

## `minPowerKw` (type: `integer`):

The lowest engine power in kW.

## `maxPowerKw` (type: `integer`):

The highest engine power in kW.

## `fuelTypes` (type: `array`):

Benzin, Diesel, Elektro, Gas, "Hybrid Elektro/Benzin", "Hybrid Elektro/Diesel", Wasserstoff.

## `transmissions` (type: `array`):

Automatik or Schaltgetriebe (commercial vehicles also Halbautomatik, Direkt).

## `bodyTypes` (type: `array`):

Cars: "Klein-/ Kompaktwagen", Limousine, "Kombi / Family Van", "SUV / Geländewagen", "Cabrio / Roadster", "Sportwagen / Coupé", Kleinbus, Mopedauto. Motorbikes and others have their own types.

## `vehicleConditions` (type: `array`):

Gebrauchtwagen, Jahreswagen, Neuwagen, Oldtimer, Tageszulassung, Unfallwagen, Vorführwagen.

## `marktplatzCondition` (type: `array`):

Marktplatz only: Neu, Neuwertig, Generalüberholt, Gebraucht, Defekt, Ausstellungsstück.

## `delivery` (type: `string`):

Marktplatz only: pickup (Selbstabholung) or shipping (Versand).

## `buyerProtectionOnly` (type: `boolean`):

Marktplatz only: ads with willhaben's PayLivery (payment and shipping with buyer protection).

## `jobFields` (type: `array`):

Jobs only: fields of work or their sub fields as willhaben names them ("IT / EDV", "Vertrieb / Handel", "Softwareentwicklung", ...).

## `employmentTypes` (type: `array`):

Vollzeit, Teilzeit, Freiberuflich, Geringfügig.

## `positionLevels` (type: `array`):

"Gruppen-/Teamleitung", Lehre, "Leitung/Management", "Mitarbeiter:in", Praktikum, Projektmanagement, Traineeship.

## `employerType` (type: `string`):

Jobs only: direct employers (Firmen) or staffing agencies (Personaldienstleister).

## `minSalary` (type: `number`):

Jobs only, checked on every job row against the salary willhaben shows, in the period below. Jobs without a salary are left out free.

## `maxSalary` (type: `number`):

Jobs only: the highest salary shown.

## `salaryPeriod` (type: `string`):

The period of the salary range: per month (the default), per year or per hour. A monthly and a yearly salary are compared at 14 salaries a year, as Austrian pay is counted; an hourly salary only with an hourly range.

## `attributeFilters` (type: `object`):

Any other filter of the search's own willhaben filter list, by its German label or id, with values by label or id, or {"min": n, "max": n} for a range. For example {"Farbe": \["Schwarz"], "Anzahl der Sitze": {"min": 7}, "Garantie": \["Garantie"]}. A filter the search does not offer is refused with the filters it does offer.

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

The order willhaben answers in. Left empty: newest first (published or bumped; the only order read past 199,800 rows), nearest first when a radius is set, and a pasted link keeps its own order. Or choose: newest, relevance (needs keywords), price, nearest (needs latitude, longitude and radiusKm), living area (Immobilien), mileage or first registration (Auto & Motor). Jobs: newest or relevance.

## `includeKeywords` (type: `array`):

Only rows whose title or description holds one of these words (whole words, any case). Checked on every row; with details on, on the full description.

## `requireAllKeywords` (type: `boolean`):

Rows must hold every word of "includeKeywords", not just one.

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

Leave out rows whose title or description holds one of these words (the same whole word rule).

## `titleOnly` (type: `boolean`):

Read "includeKeywords" and "excludeKeywords" in the title alone.

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

Ad or job ids or links that are never delivered (for example ones you already have).

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

Search pages copied from willhaben.at (Marktplatz, Immobilien, Auto & Motor under /iad/, or jobs under /jobs/suche), read with all their filters. A link parameter its search does not know is refused, since willhaben would ignore it without saying so.

## `sellerIds` (type: `array`):

A seller's ads in Marktplatz, Immobilien and Auto & Motor: the seller number (the ORGID of their ads, delivered in sellerId for business sellers) or a private seller's profile link. Combined with the sections and filters you give. A search link with "orgId=" goes into Search links, which keeps its section and filters.

## `employerUrls` (type: `array`):

All open jobs of an employer: its willhaben employer page link (https://www.willhaben.at/jobs/firma/<name>/<id>).

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

Ad links (https://www.willhaben.at/iad/.../d/...), ad ids, or job links (https://www.willhaben.at/jobs/job/...): each read with its full record and billed as a Listing with its Listing details. A removed or unknown one is a free row.

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

The most paid rows the run delivers (ads, jobs, change rows, removed rows), across all searches and links.

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

The most rows one search delivers.

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

The most result pages one search reads (up to 200 ads or 90 jobs a page).

## `maxRowsPerSearch` (type: `integer`):

How many rows of each search are read (a monitor's window, 200 by default for a monitor).

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

Read each ad's or job's full record: the full description, every attribute, seller or company facts, car equipment, property costs and energy data, the apply link. Billed as Listing details per row with details.

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

Deliver the fields that name or reach a private person: a private seller's name and id, the street and exact map point of a private ad, contact persons' names on ads and job ads, and the contacts found in such texts. Off by default; you are responsible for using such data lawfully (GDPR). Business data is always delivered.

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

Add willhaben's own search row and full record as served (for a private seller's ad only with "includePersonalData").

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

html: as willhaben serves it (the default); text: plain text; markdown; all: html plus text and markdown fields.

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

Cut descriptions after this many characters (20 to 100,000).

## `compactOutput` (type: `boolean`):

Deliver only the core fields of each row.

## `excludeEmptyFields` (type: `boolean`):

Drop fields that are null, empty texts or empty lists.

## `extractContacts` (type: `boolean`):

Add the e-mail addresses, phone numbers, websites and social profiles written in the title and description (for private sellers' ads and job ads only with "includePersonalData"). Texts themselves are never changed.

## `phoneExtractionMode` (type: `string`):

strict: numbers after a contact word or written with a country code; lenient: also bare numbers that start with 0. Needs "extractContacts".

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

Name a monitor to deliver only ads new to it on every scheduled run (each ad billed once). The memory belongs to this Actor; up to 60 characters of letters, digits, spaces and . \_ , - : # ( ).

## `resetMonitor` (type: `boolean`):

Start the named monitor empty on this run.

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

With a monitor: deliver a known ad again when its price changed (priceChanged, with the previous price and a price history), its title, images or place changed (updated) or it came back (reappeared), and report ads no longer found when every search was read to its end (removed rows). Each such row is billed as a Listing.

## `emitUnchanged` (type: `boolean`):

With a monitor: deliver known ads that did not change as "unchanged" (billed as a Listing).

## `markReposts` (type: `boolean`):

With a monitor: mark a new ad whose title, price and seller match an ad the monitor delivered (possibleRepostOf).

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

With markReposts: leave reposts out free.

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

Send the rows of the run to Telegram through your own bot (the token BotFather gave you).

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

Your chat id (a number) or a channel name starting with @.

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

A Slack incoming webhook link (hooks.slack.com).

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

A Discord webhook link.

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

Your WhatsApp Cloud API token (with the two settings below).

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

Your WhatsApp Cloud API phone number id (digits).

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

The number to send to, in international form without + (for example 436641234567).

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

A link of your own service: the rows are sent as JSON in batches of 100 (signed with HMAC SHA-256 when a secret is given).

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

16 to 200 characters; signs each webhook call (header x-signature-sha256).

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

Extra headers for the webhook (at most 10).

## `notificationLimit` (type: `integer`):

How many rows a Telegram, Slack, Discord or WhatsApp message lists (1 to 30).

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

Leave unchanged rows out of the notifications.

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

Mark the run failed when a search or link was not read completely (the rows stay delivered).

## `maxFailedRequestsPercent` (type: `integer`):

Mark the run failed when more than this percent of its reads failed.

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

Leave as is: the search door is read through Apify's datacenter proxy and willhaben's pages through an Austrian residential session. Your own proxy links replace both.

## `requestsPerSecond` (type: `number`):

The pace of each of the two exits (0.2 to 2).

## `maxConcurrency` (type: `integer`):

Requests in flight at once on each exit (1 to 4), inside the pace above: more helps only when willhaben answers slowly.

## `maxRequestRetries` (type: `integer`):

How often a failed or empty answer is asked again (0 to 5). A refusal by willhaben is never a reason to draw a new address.

## Actor input object example

```json
{
  "sections": [
    "immobilien"
  ],
  "propertyTypes": [
    "mietwohnungen"
  ],
  "states": [
    "Wien"
  ],
  "sellerType": "any",
  "availableNow": false,
  "newBuildOnly": false,
  "delivery": "any",
  "buyerProtectionOnly": false,
  "employerType": "any",
  "requireAllKeywords": false,
  "titleOnly": false,
  "maxItems": 20,
  "includeDetails": false,
  "includePersonalData": false,
  "includeRawData": false,
  "descriptionFormat": "html",
  "compactOutput": false,
  "excludeEmptyFields": false,
  "extractContacts": false,
  "resetMonitor": false,
  "trackChanges": false,
  "emitUnchanged": false,
  "markReposts": false,
  "skipReposts": false,
  "notifyOnlyChanges": false,
  "failIfIncomplete": false,
  "requestsPerSecond": 1,
  "maxConcurrency": 2,
  "maxRequestRetries": 2
}
```

# Actor output Schema

## `items` (type: `string`):

No description

## `report` (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 = {
    "sections": [
        "immobilien"
    ],
    "propertyTypes": [
        "mietwohnungen"
    ],
    "states": [
        "Wien"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/willhaben-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 = {
    "sections": ["immobilien"],
    "propertyTypes": ["mietwohnungen"],
    "states": ["Wien"],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation_craft/willhaben-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 '{
  "sections": [
    "immobilien"
  ],
  "propertyTypes": [
    "mietwohnungen"
  ],
  "states": [
    "Wien"
  ],
  "maxItems": 20
}' |
apify call automation_craft/willhaben-scraper --silent --output-dataset

```

## MCP server setup

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