# Kleinanzeigen Scraper - Listings, New Ads & Price Drops (`neverempty/kleinanzeigen-scraper`) Actor

For resellers, deal alerts and price trackers: Kleinanzeigen.de listings by search term, category, city or postcode and price range, with price, VB, postcode, date, photo, shipping and PRO shop. Monitor returns only really new ads and price drops, never old ads pushed back to the top.

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

## Pricing

from $1.40 / 1,000 listing returneds

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 - Listings, New Ads & Price Drops

Get **Kleinanzeigen.de listings** (formerly eBay Kleinanzeigen, Germany's biggest classifieds site) as clean JSON: title, price and whether it is fixed, **VB** (negotiable) or free, strikethrough price, description snippet, postcode and place, the date Kleinanzeigen shows, photo, **Versand möglich** and **Direkt kaufen** flags, TOP ad and **PRO shop** flags, and the link. Search by **search term**, **category**, **city, district or postcode**, and a **price range**, or paste Kleinanzeigen search URLs. Turn on **Monitor mode** and scheduled runs return **only new ads and price drops** since the last check - so a reseller, a deal alert, a price tracker or a Telegram/Slack bot gets the new ones without paying for the same listings again.

- **New ads monitor that does not sell old ads as new.** Kleinanzeigen sorts by "posted or pushed to the top", so old ads come back to the top of the list every day, and older ads move up into view when others are deleted. Monitor mode tells them apart: it uses Kleinanzeigen's ad IDs (they grow with every new ad) together with the date on the card, and returns only ads that are really new since the last check (`changeType: new`).
- **Price drops.** A listing that this watch has seen before and whose price is now lower comes back as `changeType: price-drop` with `previousPrice` and `priceDrop` (euros).
- **Honest about what can be read.** Kleinanzeigen's robots.txt allows the first 5 pages of a search (125 newest listings). This Actor follows robots.txt: when a search has more listings than that, a free row says so, and in monitor mode a free row warns when more new ads appeared since the last check than fit on 5 pages.
- **No charge when nothing could be delivered.** A place Kleinanzeigen does not know (it would silently show all of Germany), a search with no results, no listing in your price range, a page that could not be read, and a check page come back as free rows that say why. Kleinanzeigen's "results in other places" under a small search are never returned as results.
- **Price range, TOP ads.** `minPrice` / `maxPrice` in euros; paid TOP placements (shown above every page whatever their age) are left out unless you ask for them.
- **Several searches in one run.** Search terms times categories times locations, plus search URLs, up to 10 searches per run.
- **Fast and light.** One page is 25 listings: production runs on 2026-09-24 took 3-9 s at 256 MB (50 listings from 2 pages in about 3.7 s; a monitor check of 4 pages in about 7 s).

Unofficial. Reads the public Kleinanzeigen search result pages, the same pages a person sees without logging in, and follows Kleinanzeigen's robots.txt. It does not open ad detail pages, does not log in, and does not solve or bypass check pages. It returns no private seller names and no phone numbers: private sellers are not named on search result pages at all, and phone numbers and e-mail addresses that sellers write into a title or description are replaced with `[phone removed]` / `[email removed]`. For PRO shops (businesses), the shop name shown on the listing is returned.

### What you get

One row per listing. Example (a production run on 2026-09-24, search term `fahrrad` in `Berlin`):

```json
{
  "status": "ok",
  "changeType": null,
  "adId": "3522051805",
  "title": "Erwachsenen-Dreirad",
  "price": 300,
  "priceText": "300 € VB",
  "priceType": "negotiable",
  "currency": "EUR",
  "previousPrice": null,
  "priceDrop": null,
  "strikethroughPrice": null,
  "description": "Ich biete mein 26er Dreirad inklusive Frontkorb und großen Heckkorb zum Verkauf an. Es ist in einem sehr gutem Zustand und wurde sehr wenig gefahren! Nur Abholung! Keine Garantie und keine Rücknahme!",
  "postalCode": "13088",
  "locationName": "Weissensee",
  "locationId": 3477,
  "categoryId": 217,
  "listedText": "Heute, 16:47",
  "listedAt": "2026-09-24T14:47:00.000Z",
  "listedDate": "2026-09-24",
  "imageUrl": "https://img.kleinanzeigen.de/api/v1/prod-ads/images/b0/b01f525f-be5f-4c0b-a782-e4e2b27edb96?rule=$_59.AUTO",
  "imageCount": 4,
  "shippingPossible": false,
  "directBuy": false,
  "isTopAd": false,
  "isProShop": false,
  "proShopName": null,
  "proShopUrl": null,
  "url": "https://www.kleinanzeigen.de/s-anzeige/erwachsenen-dreirad/3522051805-217-3477",
  "positionInSearch": 1,
  "listingsInSearch": 34749,
  "searchQuery": "fahrrad",
  "searchCategory": null,
  "searchLocation": "Berlin",
  "searchPlaceShown": "Berlin",
  "searchUrl": "https://www.kleinanzeigen.de/s-fahrrad/k0?locationStr=Berlin",
  "watchName": null,
  "checkedAt": "2026-09-24T14:47:59.928Z"
}
```

Checked in a browser after the runs: 25 of 25 listings from five production runs (fahrrad in Berlin, Rennrad in München 300-1,200 EUR, Zu verschenken in Hamburg, a monitor of iphone in Berlin, lego technic in Köln up to 150 EUR) showed the same ad ID, title, price and postcode on the ad page.

| Column | Meaning |
|---|---|
| `status` | `ok` for a listing row. Other values are free rows that say why nothing (or not everything) was returned (below) |
| `changeType` | Monitor mode only: `first-check` (first run of this watch), `new`, `price-drop`, or `resurfaced` (only with `includeResurfaced`). Null when monitor mode is off |
| `adId`, `url` | Kleinanzeigen's ad ID and the ad page |
| `title`, `description` | Title and the description snippet shown in the search result (contacts removed) |
| `price`, `priceText`, `priceType`, `currency` | Price in euros as a number, the text as shown (`559 € VB`), and its meaning: `fixed`, `negotiable` (VB), `free` (the price reads "Zu verschenken", price 0) or `other`. `price` is null when the listing shows no amount (only `VB`, or an ad in the Zu verschenken category, which shows no price) |
| `strikethroughPrice` | The crossed-out earlier price Kleinanzeigen shows on some listings |
| `previousPrice`, `priceDrop` | Monitor mode, `price-drop` rows: the price at the previous check and the difference in euros |
| `postalCode`, `locationName`, `locationId` | Postcode and place as shown, and Kleinanzeigen's place ID |
| `categoryId` | Kleinanzeigen's category ID of the ad |
| `listedText`, `listedAt`, `listedDate` | The date on the card (`Heute, 15:40`, `Gestern, 17:27`, `22.09.2026`), as a UTC time when it has a time, and as a date. It is when the ad was posted or last pushed to the top |
| `imageUrl`, `imageCount` | First photo and number of photos |
| `shippingPossible`, `directBuy` | "Versand möglich" and "Direkt kaufen" shown on the card |
| `isTopAd` | Paid TOP placement (only returned with `includeTopAds`) |
| `isProShop`, `proShopName`, `proShopUrl` | The listing belongs to a PRO shop (a business account), with the shop name and page |
| `positionInSearch`, `listingsInSearch` | Position in this search (TOP ads not counted) and the number of results Kleinanzeigen reports for it |
| `searchQuery`, `searchCategory`, `searchLocation`, `searchPlaceShown`, `searchUrl` | The search this row came from, and the place as Kleinanzeigen understood it |
| `note` | Free rows only: why nothing (or not everything) was returned |
| `watchName`, `checkedAt` | The watch name you gave, and the time of the check |

#### Free rows (not charged)

| `status` | When |
|---|---|
| `no-results` | Kleinanzeigen has no listing for this search |
| `none-in-price-range` | Listings were read, but none is in your price range (in monitor mode: on the first check) |
| `not-found` | Kleinanzeigen does not know the place (it would show all of Germany instead), or it sent a search URL or category somewhere else |
| `no-new-listings` | Monitor mode: nothing new and no price drop since the previous check |
| `more-not-returned` | Normal search: fewer listings than you asked for were returned because the search is bigger than the 5 pages robots.txt allows; says how many and what to do. Monitor mode: more new listings than `maxResultsPerSearch` (a later run returns them), or, on the first check, how many listings are now the starting point |
| `more-new-than-readable` | Monitor mode: more listings appeared since the last check than fit on the 5 readable pages, so some new ones may have been missed. Run more often or narrow the search |
| `not-allowed-by-robots-txt` | A search URL (or a page of it) that Kleinanzeigen's robots.txt does not allow, for example one with a price, radius or sort filter. Nothing was requested |
| `incomplete` | A page of the list could not be read; the listings before it were returned, the rest were not |
| `unreadable` | Kleinanzeigen could not be read, even after asking again from other IP addresses. In monitor mode also when only part of the list could be read (nothing is judged from a partial list, nothing is remembered) |
| `blocked` | Kleinanzeigen showed a check page. This Actor does not solve or bypass check pages; it stops and does not run the remaining searches |
| `budget-reached` | The run hit the maximum total charge you set. In monitor mode the listings not returned are returned by the next run |
| `bad-input` | The input could not be used |

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `searchQueries` | list of strings | - | Search terms: `iphone 15`, `rennrad`, `kühlschrank`, `lego technic` ... Leave search terms, categories, locations and search URLs all empty to run the example `fahrrad` in `Berlin` |
| `categories` | list of strings | - | Category numbers or names: `217` (Fahrräder & Zubehör), `173` (Handy & Telekom), `216` (Autos) ... (table below). A category without a search term lists everything in it |
| `locations` | list of strings | all of Germany | Cities, districts or postcodes: `Berlin`, `Mitte - Berlin`, `München`, `10115` |
| `searchUrls` | list of strings | - | Kleinanzeigen search result pages copied from the browser, for example `https://www.kleinanzeigen.de/s-berlin/fahrrad/k0l3331` |
| `minPrice`, `maxPrice` | number (EUR) | - | Price range, applied to the listings read. Listings without an amount (only `VB`) are left out when a range is set |
| `maxResultsPerSearch` | integer 1-125 | 50 | Most listings returned for one search. In monitor mode: most new listings and price drops per search in one run (the rest come next run) |
| `includeTopAds` | boolean | false | Also return paid TOP placements |
| `onlyNew` | boolean | false | Monitor mode: return only new listings and price drops since the previous check |
| `includePriceDrops` | boolean | true | Monitor mode: return price drops too |
| `includeResurfaced` | boolean | false | Monitor mode: also return older listings (not seen by this watch) that were pushed back to the top, as `resurfaced` |
| `watchName` | string | - | Separate memories for monitor mode, for example one per client or per alert |
| `resetMonitoringState` | boolean | false | Forget what this watch has seen, so the run is a first check again |

Each search term is searched in each category and each location (up to 10 searches per run).

Main categories: `210` Auto, Rad & Boot · `216` Autos · `223` Autoteile & Reifen · `217` Fahrräder & Zubehör · `305` Motorräder & Motorroller · `161` Elektronik · `173` Handy & Telekom · `278` Notebooks · `228` PCs · `279` Konsolen · `245` Foto · `172` Audio & Hifi · `176` Haushaltsgeräte · `80` Haus & Garten · `88` Wohnzimmer · `86` Küche & Esszimmer · `84` Heimwerken · `17` Familie, Kind & Baby · `23` Spielzeug · `25` Kinderwagen & Buggys · `153` Mode & Beauty · `154` Damenmode · `160` Herrenmode · `185` Freizeit, Hobby & Nachbarschaft · `234` Sammeln · `230` Sport & Camping · `73` Musik, Filme & Bücher · `74` Musikinstrumente · `130` Haustiere · `195` Immobilien · `203` Mietwohnungen · `231` Eintrittskarten & Tickets · `192` Zu verschenken.

### Examples

Road bikes in Munich between 300 and 1,200 euros:

```json
{ "searchQueries": ["rennrad"], "categories": ["217"], "locations": ["München"], "minPrice": 300, "maxPrice": 1200 }
```

A deal alert: new iPhone 15 Pro ads and price drops in Berlin under 700 euros, every 15 minutes (schedule this input):

```json
{ "searchQueries": ["iphone 15 pro"], "locations": ["Berlin"], "maxPrice": 700, "onlyNew": true, "watchName": "iphone-berlin" }
```

Everything given away for free in a postcode area:

```json
{ "categories": ["192"], "locations": ["10115"] }
```

### Pricing

Pay per event:

- **Run start** - once per run that read Kleinanzeigen: in a normal search, before the first listing row is returned (so not charged when nothing is in your price range); in monitor mode, when a list was read and compared, also when nothing changed or nothing is in your price range - that pays for the check. Never charged when nothing could be read, the place is unknown, or robots.txt does not allow the page.
- **Listing returned** - per listing row.

Free rows are never charged. If you set a maximum total charge for a run, the run stops before it would go over it and says so; a run whose maximum has no room for the start fee plus one listing does not request anything.

### Monitor mode, step by step

1. First run: reads the newest 5 pages of each search (up to 125 listings), returns up to `maxResultsPerSearch` as `first-check`, and remembers them (with their prices) as the starting point.
2. Later runs: read the newest 5 pages again. A listing is `new` when its ad ID is higher than every ad this watch has seen, or when it was published after the previous check with an ad ID from no more than about a day before it (ads that appear late after Kleinanzeigen's review). A listing this watch has seen whose price is lower now is a `price-drop`. Older ads that were pushed back to the top, and older ads that moved up from further down the list, are remembered but not returned as new.
3. If a page of a list cannot be read, that list is not judged in that run (nothing returned, remembered or charged); the next run checks it again.

Keep one schedule per search and watch name. Two overlapping runs of the same watch can both return the same new listing (the memory is a key-value store without transactions).

### Limits

- Kleinanzeigen's robots.txt allows the first 5 pages of a search (125 newest listings). Busy searches (for example `iphone` in all of Germany gets hundreds of new ads per hour) move faster than that: narrow them with a category, a location or a more exact search term, and schedule a watch often enough.
- Kleinanzeigen's own price, radius, sort, seller type and offer type filters are not allowed by its robots.txt, so this Actor does not use them. The price range is applied to the listings it reads; there is no radius and no private/business filter.
- Only the fields shown on the search result list are returned (the description is Kleinanzeigen's snippet, not the full text). Ad detail pages, seller profiles and phone numbers are not read.
- Price drops are seen for listings that are still on the 5 readable pages of the search.
- Ads in the Zu verschenken (give-away) category show no price on Kleinanzeigen and come back with `price` null (nothing is made up); a price range leaves them out.
- The date on the card is when the ad was posted or last pushed to the top; Kleinanzeigen does not show the original posting date on the list.

### Support

Questions, a field you need, or a search that does not work: open an issue on the Issues tab of this Actor.

# Actor input Schema

## `searchQueries` (type: `array`):

What you would type into the Kleinanzeigen search box, for example iphone 15, rennrad, kühlschrank, lego technic. Each search term is searched in each location and category. Leave Search terms, Categories, Locations and Search URLs all empty to run the example search fahrrad in Berlin.

## `categories` (type: `array`):

Optional. Kleinanzeigen category numbers or names, for example 217 (Fahrräder & Zubehör), 173 (Handy & Telekom), 216 (Autos), 88 (Wohnzimmer), 23 (Spielzeug). A category alone (without a search term) lists everything new in it. The README lists the main category numbers.

## `locations` (type: `array`):

Optional. German cities, districts or 5-digit postcodes as you would type them on Kleinanzeigen, for example Berlin, Mitte - Berlin, München, 10115. Empty = all of Germany. A place Kleinanzeigen does not know comes back as a free row (Kleinanzeigen itself would silently show all of Germany).

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

Optional. Kleinanzeigen search result pages copied from your browser, for example https://www.kleinanzeigen.de/s-berlin/fahrrad/k0l3331. Filters in the URL that Kleinanzeigen's robots.txt does not allow (price, radius, sort order, seller type) are not requested: a free row says so. Use Min price and Max price instead.

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

Optional. Only listings at or above this price. Applied to the listings this Actor reads (Kleinanzeigen's own price filter is not allowed by its robots.txt). Listings without a price (only "VB") are left out when a price filter is set.

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

Optional. Only listings at or below this price. A listing whose price reads "Zu verschenken" counts as 0. Ads in the Zu verschenken category show no price at all and are left out when a price range is set.

## `maxResultsPerSearch` (type: `integer`):

Most listings returned for one search. Kleinanzeigen's robots.txt allows the first 5 pages of a search (125 newest listings), so 125 is the maximum. Empty = 50. In monitor mode it limits the new listings and price drops returned per search in one run; the rest come in the next run.

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

Off (default): paid TOP placements, which Kleinanzeigen shows above every page whatever their age, are left out. On: return them too, marked isTopAd.

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

On: every run reads the newest 5 pages of each search and returns only listings that are new since the previous check (changeType new) and listings whose price went down (changeType price-drop, with previousPrice). Older listings that were pushed back to the top or moved up from further down are not sold as new. The first run returns up to Max listings per search (changeType first-check) and remembers the list as the starting point. Each run that reads the list is charged the run start fee even when nothing changed; runs where nothing changed return a free row saying so.

## `includePriceDrops` (type: `boolean`):

On (default): in monitor mode, also return listings whose price is lower than at the previous check. Off: only new listings.

## `includeResurfaced` (type: `boolean`):

Off (default): an older listing that the seller pushed back to the top (or edited), which this watch had not seen before, is not returned. On: return it with changeType resurfaced. It is never called new.

## `watchName` (type: `string`):

Optional. Keeps separate memories for monitor mode, for example one per client (letters, digits, dot, dash, underscore; up to 40). Runs with the same watch name and the same search share what has already been returned.

## `resetMonitoringState` (type: `boolean`):

On: forget what this watch has seen for these searches before running, so this run is a first check again.

## Actor input object example

```json
{
  "searchQueries": [
    "iphone 15"
  ],
  "locations": [
    "Berlin"
  ],
  "includePriceDrops": true,
  "includeResurfaced": false
}
```

# Actor output Schema

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

One row per Kleinanzeigen listing: ad ID, title, price with its meaning (fixed, VB, free), description snippet, postcode and place, date shown, photo, shipping and Direkt kaufen flags, TOP and PRO shop flags, link, and its position in the search. Monitor mode marks rows first-check, new or price-drop (with the previous price). A search with no listings, nothing new, an unknown place, a page robots.txt does not allow, a refused request or a run that hit its maximum charge comes back as a free row that says why.

# 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 = {
    "searchQueries": [
        "iphone 15"
    ],
    "locations": [
        "Berlin"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/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 = {
    "searchQueries": ["iphone 15"],
    "locations": ["Berlin"],
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/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 '{
  "searchQueries": [
    "iphone 15"
  ],
  "locations": [
    "Berlin"
  ]
}' |
apify call neverempty/kleinanzeigen-scraper --silent --output-dataset

```

## MCP server setup

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