# Kleinanzeigen Scraper - New Ads & Price Drop Alerts (`scrapewise/kleinanzeigen-scraper`) Actor

Scrape Kleinanzeigen.de (ex eBay Kleinanzeigen) without login: any search link or keyword, location, radius, price, offer and seller type. Schedule it with only-new ads and price-drop tracking. Full ad page optional. No seller data. US$ 1.00 per 1,000 ads.

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

## Pricing

Pay per event

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

## Kleinanzeigen Scraper: new ads and price drops, every day

Scrape [Kleinanzeigen.de](https://www.kleinanzeigen.de) (formerly eBay Kleinanzeigen, Germany's largest
classifieds site) **without an account, cookies or a browser**. Paste any Kleinanzeigen search link with
your filters, or search by keyword, category, city or zip code, radius, price range, offers or wanted ads
and private or commercial sellers. Works for every category: cars, apartments, phones, bikes, furniture,
free stuff.

Built to be **scheduled**: turn on `onlyNew` and each run returns only the ads you have not seen yet, turn
on `trackPriceChanges` and every row tells you the previous price and whether it dropped. A quiet day costs
nothing.

### At a glance

- **Price per 1,000 ads:** US$ 1.00
- **Full ad page (description, attributes, all images):** optional, US$ 1.00 per 1,000 ads more
- **Fee per run start:** None
- **Search by Kleinanzeigen link, keyword, category, location and radius:** Yes
- **Filters:** price range, offers or wanted, private or commercial, sort order
- **Only new ads since the last run:** Yes (`onlyNew`)
- **Price change and price drop per ad:** Yes (`trackPriceChanges`, `onlyChanged`)
- **Seller data:** Never (see below)
- **Error rows (bad link, deleted ad, no results):** Free, with an `errorCode`

### One real row

From a test run on 2026-10-06 (keyword "iphone 15", Berlin, 10 km, private sellers, full ad page and price
tracking on; image list and description shortened):

```json
{
  "type": "ad",
  "id": "3533112573",
  "url": "https://www.kleinanzeigen.de/s-anzeige/iphone-15-pro-128-gb-blau/3533112573-173-3520",
  "title": "iPhone 15 pro 128 GB Blau",
  "price": 450,
  "currency": "EUR",
  "priceType": "fixed",
  "priceText": "450 €",
  "originalPrice": null,
  "adType": "offer",
  "categoryId": "173",
  "sellerType": "private",
  "isTopAd": false,
  "buyNowAvailable": false,
  "shippingAvailable": false,
  "zipCode": "10117",
  "locality": "Mitte",
  "region": "Berlin - Mitte",
  "postedAt": "2026-10-06T16:28:00+02:00",
  "imageUrl": "https://img.kleinanzeigen.de/api/v1/prod-ads/images/8a/8a393486-aebd-4ab1-8751-aa7a6bdebf7b?rule=$_59.AUTO",
  "imageCount": 11,
  "isNewAd": true,
  "previousPrice": null,
  "priceChange": null,
  "priceChangePct": null,
  "isPriceDrop": false,
  "searchTerm": "iphone 15",
  "page": 1,
  "description": "iPhone 15 Pro mit 128 GB in Blaufarbe funktioniert einwandfrei. [...] Keine Versand\nKeine PayPal\n\nNur Abholung!!",
  "attributes": { "Art": "Apple", "Modell": "iPhone 15 Pro", "Farbe": "Blau", "Zustand": "Sehr Gut" },
  "images": ["https://img.kleinanzeigen.de/api/v1/prod-ads/images/8a/8a393486-aebd-4ab1-8751-aa7a6bdebf7b?rule=$_59.JPG"],
  "createdOn": "2026-10-06",
  "categoryPath": ["Elektronik", "Smartphones"],
  "location": "10117 Berlin - Mitte",
  "shippingText": "Nur Abholung",
  "detailsScraped": true,
  "scrapedAt": "2026-10-06T14:45:15Z",
  "errorCode": null,
  "error": null
}
```

The next day, if the seller lowers the price to 420 €, the same ad comes back with `previousPrice: 450`,
`priceChange: -30`, `priceChangePct: -6.7` and `isPriceDrop: true`.

### What you can do with it

- **Deal alerts:** a daily (or hourly) Task with `onlyNew` on "ps5", "rtx 4090" or "Rennrad" under your
  price ceiling, sent to Slack, e-mail or Google Sheets. You read only what was posted since the last check.
- **Price research:** asking prices for a model across Germany or in one city, with negotiable (`VB`) and
  fixed prices told apart, to price your own ad or check a deal.
- **Resellers and flippers:** `onlyChanged` on your sourcing searches shows new ads plus every price cut, so
  you catch a seller who drops 20% before anyone else.
- **Car hunting:** category 216 (cars) with a radius around your city; rows carry `mileageKm` and
  `firstRegistration`, and the full ad page adds make, model, power, fuel and inspection date.
- **Apartment hunting:** paste your search for rentals in Berlin, Munich or Hamburg; rows carry
  `livingAreaM2`, `rooms` and the cold rent, the full ad page adds warm rent, extra costs, deposit and floor,
  and `onlyNew` turns it into a new-apartment alert.
- **Market datasets:** supply and price levels by category, region and seller type.

### Input

| Field | Type | What it does |
|---|---|---|
| `searchUrls` (also `startUrls`, `urls`) | list | Kleinanzeigen search or category links with your filters. Ad links (`/s-anzeige/...`) or ad ids return the full ad page. |
| `keywords` | list | Search terms, one search each, combined with the filters below. |
| `categoryId` | text | Number after `c` in a site link: 216 cars, 203 apartments for rent, 196 apartments for sale, 173 phones, 161 electronics, 217 bicycles, 192 free stuff. |
| `location` | text | German city, district or 5-digit zip code (Berlin, München, 10115). |
| `radiusKm` | integer, default 0 | 0, 5, 10, 20, 30, 50, 100, 150 or 200 km around the location. |
| `minPrice`, `maxPrice` | integer | Price range in euros. |
| `adType` | `all`, `offers`, `wanted` | Ads selling something, ads looking for something, or both. |
| `sellerType` | `all`, `private`, `commercial` | Kleinanzeigen's own private or commercial flag. |
| `sortBy` | `newest`, `recommended`, `priceLowToHigh`, `priceHighToLow` | Order for keyword searches; links keep their own. |
| `maxItemsPerSearch` | integer, default 50 | Ads per search (25 per page). `0` = everything the site shows, up to 1,250. |
| `includeTopAds` | boolean | Keep the paid TOP ads pinned above the results (`isTopAd: true`). Default on, and off with `onlyNew`/`onlyChanged` |
| `onlyNew` | boolean, default false | Only ads this search never returned before. |
| `trackPriceChanges` | boolean, default false | Previous price, change and price drop on every row. |
| `onlyChanged` | boolean, default false | Only new ads and ads whose price changed. |
| `includeDetails` | boolean, default false | Full ad page for each delivered ad (separate event). |
| `proxyConfiguration` | proxy | Apify datacenter proxy by default. |

`location`, `radiusKm`, `categoryId`, prices, `adType` and `sellerType` apply to `keywords`. A link already
carries its own filters. With no keyword, a category or location alone lists every ad there.

```json
{
  "keywords": ["iphone 15"],
  "location": "Berlin",
  "radiusKm": 10,
  "maxPrice": 600,
  "sellerType": "private",
  "maxItemsPerSearch": 50
}
```

### Schedule it daily: new ads and price drops

Save your input as a Task and attach a Schedule (every morning, every hour). Then pick one:

- **`onlyNew: true`**: each run delivers, and charges, only ads this search has not returned before.
  Already delivered ads are skipped for free and do not count toward `maxItemsPerSearch`. The run stops
  reading at the first page with nothing new, so a quiet hour costs a request or two and US$ 0.
- **`onlyChanged: true`**: new ads **plus** ads whose price changed since they were last seen, each with
  `previousPrice`, `priceChange`, `priceChangePct` and `isPriceDrop`. Unchanged ads are checked but not
  delivered and not charged; `maxItemsPerSearch` is how many ads each run checks.
- **`trackPriceChanges: true`** alone: every ad is delivered, each with the price comparison fields.

```json
{
  "searchUrls": ["https://www.kleinanzeigen.de/s-wohnung-mieten/berlin/c203l3331"],
  "onlyNew": true,
  "includeTopAds": false,
  "maxItemsPerSearch": 100
}
```

- The memory lives in a Key-Value Store in **your own account** (`kleinanzeigen-scraper-memory`), one record
  per search and filter set, with the last price and the first and last time each ad was seen. Inspect it
  or delete it to start over. `onlyNew`, `trackPriceChanges` and `onlyChanged` share the same record.
- The first run delivers up to `maxItemsPerSearch` and remembers every ad on the pages it read.
- `onlyNew` and `onlyChanged` read the search in the newest order. A seller who bumps an old ad you never
  received makes it show up as new; an ad you already received never comes back under `onlyNew`.
- TOP ads are paid placements that rotate above the results and are often weeks old, so `onlyNew` and
  `onlyChanged` leave them out unless you set `includeTopAds: true`.

### Output fields

| Field | Meaning |
|---|---|
| `id`, `url` | Kleinanzeigen ad id and link |
| `title` | Ad title |
| `price`, `currency` | Price in EUR (number). `0` for free items, `null` when the ad shows only `VB` |
| `priceType` | `fixed`, `negotiable` (VB, Verhandlungsbasis), `free` (zu verschenken) |
| `priceText`, `originalPrice` | Price as shown, and the crossed-out earlier price when the seller shows one |
| `adType` | `offer` or `wanted` (Gesuch) |
| `categoryId` | Kleinanzeigen category of the ad |
| `sellerType` | `private` or `commercial` when the site shows it (always with the full ad page or a seller filter), else `null` |
| `isTopAd`, `buyNowAvailable` | Paid TOP placement; "Direkt kaufen" (buy with payment protection) available |
| `shippingAvailable` | `true` when the ad offers shipping ("Versand möglich"); the full ad page also gives `false` and `shippingText` |
| `zipCode`, `locality`, `region` | Zip code and town or district shown on the ad, and the region line ("Berlin - Mitte"). Never a street |
| `postedAt`, `postedAtText` | When the ad was posted or last bumped, Berlin time (date only for older ads, empty on TOP ads) |
| `descriptionSnippet` | First lines of the description, as on the results page |
| `imageUrl`, `imageCount` | Main photo (large) and number of photos |
| `listingDetails`, `livingAreaM2`, `rooms` | Key facts on the card (apartments: "69 m²", "2 Zi.") |
| `mileageKm`, `firstRegistration` | Cars: mileage and first registration (MM/YYYY) |
| `tags` | Badges as shown (Versand möglich, Direkt kaufen, Gesuch, km, EZ) |
| `searchUrl`, `searchTerm`, `page` | The search that found the ad and its results page |
| `isNewAd`, `previousPrice`, `priceChange`, `priceChangePct`, `isPriceDrop`, `firstSeenAt`, `previousSeenAt` | Price tracking (with `trackPriceChanges` or `onlyChanged`) |
| `description`, `attributes`, `features`, `images`, `createdOn`, `category`, `categoryPath`, `location`, `shippingText`, `isActive`, `detailsScraped` | Full ad page (with `includeDetails` or an ad link) |
| `scrapedAt`, `errorCode`, `error` | Collection time; error fields are `null` on good rows |

#### Seller data: never

Most Kleinanzeigen sellers are private people, so this Actor returns **the ad only**. It never returns the
seller's name, user id, profile or shop link, phone number, e-mail, street address, map coordinates,
rating, "active since" date or a dealer's imprint. `sellerType` says private or commercial and nothing
else. Phone numbers and e-mail addresses that sellers type into a title or description are masked as
`[phone removed]` and `[email removed]`. The description is otherwise the seller's own published text.

### Error codes

Error rows are never charged.

| `errorCode` | Meaning |
|---|---|
| `INVALID_INPUT` | A filter is not valid (minimum price above maximum, unknown category or location, a link that is not a Kleinanzeigen search or ad) |
| `NOT_FOUND` | The ad was deleted or expired, or the site does not know this search link |
| `NO_RESULTS` | Kleinanzeigen found no ad for this search and filters |
| `BLOCKED` | Kleinanzeigen did not answer after several attempts on new IPs; run again |
| `NOT_REACHED` | The run timeout arrived before this search or ad |
| `UNEXPECTED` | An ad or page came in a shape we did not expect; the rest of the run goes on |

### Pricing

Pay per event, no start fee:

- **US$ 1.00 per 1,000 ads delivered** (`anuncio-entregue`).
- **US$ 1.00 per 1,000 full ad pages** (`detalhe-entregue`), only with `includeDetails` or ad links, because
  each one is an extra request. An ad with its full page costs US$ 2.00 per 1,000.

Free: error rows, the same ad found twice in a run, ads skipped by `onlyNew`, unchanged ads skipped by
`onlyChanged`, and TOP ads left out with `includeTopAds: false`.

### Limitations

- Kleinanzeigen shows at most 50 pages (1,250 ads) per search. Split a broad search by category, city or
  price range to go further.
- `postedAt` is the date the site shows, which moves when a seller bumps the ad. The full ad page adds
  `createdOn`.
- Locations are resolved with the site's own suggestions: use the German name (München, Köln) or a zip
  code. English names such as "Munich" are not found.
- On apartment ads `price` is the cold rent (Kaltmiete); the full ad page adds `Warmmiete`, `Nebenkosten` and
  `Kaution` in `attributes`.
- Only public ads. Nothing behind a login (messages, phone numbers, seller inventories) is read.

### FAQ

**Do I need a Kleinanzeigen account or a German IP?** No. Nothing to log in to, no cookies to paste. The
default Apify datacenter proxy is used; if Kleinanzeigen refuses it several times in a row the run switches
to a stronger proxy group on its own.

**Can I paste a search link from my browser?** Yes. Category, location, radius, price, offers or wanted,
private or commercial and the order in the link are all kept. Page numbers in the link are ignored: the
Actor starts at page 1 and pages on its own.

**How fast is it?** About 25 ads per results page; a test read 60 Berlin apartment ads in under 2 seconds. Full
ad pages take one request each.

**Can I get only new apartments or cars every morning?** Yes: `onlyNew: true` and a Schedule. A run with
nothing new delivers nothing and costs nothing.

**Can I use it from n8n, Make, Zapier or an AI agent?** Yes, through the Apify app or the Apify MCP server.
Error rows are free, so an agent can explore cheaply.

**What if a run is cut by its timeout?** The Actor stops shortly before the limit and ends successfully with
what it delivered, telling you in the status message how to get the rest.

### Changelog

- **0.1 (2026-10-06)**: first version. Search links and keyword search with category, location, radius,
  price, offers or wanted and seller type; `onlyNew`, `trackPriceChanges` and `onlyChanged` for scheduled
  monitoring; optional full ad page; no seller data; free error rows.

# Actor input Schema

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

Search or category links from kleinanzeigen.de, one per line, with every filter you set on the site, for example https://www.kleinanzeigen.de/s-wohnung-mieten/berlin/c203l3331. Ad links (/s-anzeige/...) or numeric ad ids return the full ad page. Each ad is charged once as 'Kleinanzeigen ad delivered'. Also accepts 'startUrls' or 'urls'.

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

Search terms, one per line, each one a separate search with the filters below, for example iphone 15, golf 7 or ps5. German or English both work. Leave empty with a category or location to get every ad there.

## `categoryId` (type: `string`):

Optional Kleinanzeigen category number, the digits after 'c' in a site link: 216 cars, 203 apartments for rent, 196 apartments for sale, 173 phones, 161 electronics, 217 bicycles, 192 free stuff. Empty = all categories.

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

German city, district or 5-digit postal code as the site writes it, for example Berlin, München, Köln or 10115. Used by keyword searches; links keep their own location. Empty = all of Germany.

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

Distance around the location in km: 0 (default, the place itself), 5, 10, 20, 30, 50, 100, 150 or 200. Other values snap to the nearest one. Needs a location.

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

Only ads at or above this price in euros, as a whole number, for example 100. Empty = no minimum.

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

Only ads at or below this price in euros, as a whole number, for example 800. Empty = no maximum.

## `adType` (type: `string`):

All (default), Offers (Angebote, ads that sell something) or Wanted (Gesuche, ads looking for something).

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

All (default), Private sellers or Commercial sellers, as Kleinanzeigen classifies them. Only the type is returned, never who the seller is.

## `sortBy` (type: `string`):

Kleinanzeigen's own order for keyword searches: Newest (default), Recommended, Price low to high, or Price high to low. With a limit it also decides which ads you get. Links keep their own order. 'Only new ads' and 'Only new ads and price changes' always use newest.

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

Stop each search (each link and each keyword) after this many ads, for example 50 (default). The site shows 25 per page and at most 50 pages (1,250 ads) per search. 0 = as many as the site shows. You pay per ad delivered, so this caps the cost of each search.

## `includeTopAds` (type: `boolean`):

Keep the paid TOP ads Kleinanzeigen pins above the results (flagged isTopAd). Empty = on for normal searches and off with 'Only new ads' or 'Only new ads and price changes', because TOP ads rotate and are often weeks old.

## `onlyNew` (type: `boolean`):

Deliver only ads this search never returned before. Ads already delivered are skipped, not charged and do not count toward the limit, so a quiet run costs nothing. The first run delivers up to the limit and remembers every ad on the pages it read. Forces the newest order.

## `trackPriceChanges` (type: `boolean`):

Remember the last price seen for every ad of the search and add previousPrice, priceChange, priceChangePct, isPriceDrop, isNewAd, firstSeenAt and previousSeenAt to each row. On the first run every ad is new.

## `onlyChanged` (type: `boolean`):

Deliver only ads that are new to this search or whose price changed since they were last seen. Turns on price tracking and replaces 'Only new ads'. Unchanged ads are checked but not delivered and not charged; the limit counts checked ads.

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

Off (default): rows come from the search results (title, price, location, date, first image, short text). On: opens each delivered ad and adds the full description, attributes (mileage, rooms, condition...), features, every image, creation date, category path and shipping options. One extra request per ad, charged as a separate 'Full ad page read' event. Never any seller data.

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

Apify datacenter proxy is the default. If Kleinanzeigen refuses it several times in a row, the run switches to a stronger proxy group on its own (only when you did not pick a proxy group), with no extra charge.

## Actor input object example

```json
{
  "searchUrls": [
    "https://www.kleinanzeigen.de/s-wohnung-mieten/berlin/c203l3331"
  ],
  "keywords": [
    "iphone 15"
  ],
  "location": "Berlin",
  "radiusKm": 0,
  "adType": "all",
  "sellerType": "all",
  "sortBy": "newest",
  "maxItemsPerSearch": 50,
  "onlyNew": false,
  "trackPriceChanges": false,
  "onlyChanged": false,
  "includeDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `resultsCsv` (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 = {
    "searchUrls": [
        "https://www.kleinanzeigen.de/s-wohnung-mieten/berlin/c203l3331"
    ],
    "keywords": [
        "iphone 15"
    ],
    "location": "Berlin",
    "maxItemsPerSearch": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapewise/kleinanzeigen-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 = {
    "searchUrls": ["https://www.kleinanzeigen.de/s-wohnung-mieten/berlin/c203l3331"],
    "keywords": ["iphone 15"],
    "location": "Berlin",
    "maxItemsPerSearch": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapewise/kleinanzeigen-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 '{
  "searchUrls": [
    "https://www.kleinanzeigen.de/s-wohnung-mieten/berlin/c203l3331"
  ],
  "keywords": [
    "iphone 15"
  ],
  "location": "Berlin",
  "maxItemsPerSearch": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call scrapewise/kleinanzeigen-scraper --silent --output-dataset

```

## MCP server setup

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