# Kleinanzeigen Scraper — Listings, Prices & New Ads Alerts (`northbell/kleinanzeigen-scraper`) Actor

Scrape Kleinanzeigen.de (formerly eBay Kleinanzeigen), Germany's biggest classifieds: listings for any search with price, place, date and photo count. Run it again or on a schedule for new listings and price drops since the last run. Keywords or search URLs, place + radius, price range. No login.

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

## Pricing

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

## Kleinanzeigen Scraper — Listings, Prices & New Ads Alerts

Give it keywords (or the address of any Kleinanzeigen results page) and get one clean row per listing from Germany's biggest classifieds site, Kleinanzeigen.de (formerly eBay Kleinanzeigen): **title, price, price type, postal code and city, posting date, photo count, shipping, a short description and the link**. **$1.00 per 1,000 listings**, up to 1,000 listings per search (Kleinanzeigen shows at most 1,250 results for any search), no login, no API key.

Run it again — or put it on a schedule — and every row also says **what changed since the last run**: `isNew` for listings you have not seen before, and `previousPrice` / `priceChange` for price drops and rises. Switch on *Only new or price-changed listings* and you pay only for what changed.

Fixed 22 Oct 2026: listings the previous run had simply not reached (it stopped at *max listings*) counted as new, so with *Only new or price-changed listings* a second run could return older listings as new and charge for them. New now means posted after the newest listing the previous run saw (with two hours' slack for listings that appear late).

### What you get

| Field | What it is |
|---|---|
| `title`, `url`, `adId`, `search` | the listing, its link, its Kleinanzeigen number, and which of your searches found it |
| `price`, `priceText`, `priceType`, `currency` | the price as a number (EUR), the text as shown (`1.599 €`, `999 € VB`), and the type: `fixed`, `negotiable` (VB), `free` (*Zu verschenken*, price 0), `free-or-swap` (category *Zu verschenken & Tauschen*, which shows no price) or `not-shown` |
| `postalCode`, `city` | the place exactly as the listing card shows it (`80331 Altstadt-Lehel`) |
| `postedAt`, `postedDate`, `postedText` | when it was posted: a UTC timestamp, the date, and the text as shown (`Heute, 22:44`, `Gestern, 23:00`, `06.10.2026`) |
| `shippingPossible`, `directBuy`, `isWanted` | the badges on the card: *Versand möglich*, *Direkt kaufen*, *Gesuch* (a wanted ad) |
| `sellerType` | `private` or `pro-shop` when the card says so, otherwise empty. Never a name |
| `imageUrl`, `imageCount` | the first photo and the number of photos shown on the card |
| `details` | extra facts on the card, such as `["134.000 km", "EZ 06/1996"]` for cars or `["60 m²", "3 Zi."]` for flats |
| `descriptionSnippet` | the first part of the description (up to 250 characters), with phone numbers and email addresses removed |
| `categoryId` | Kleinanzeigen's category number (for example 173 for phones, 216 for cars) |
| `isPromoted` | `true` for paid "Top" ads (they are skipped unless you ask for them) |
| `isNew`, `firstSeenAt`, `previousPrice`, `priceChange`, `priceChangePct` | the change since your previous run of the same search: `isNew`, `previousPrice`, `priceChange` and `priceChangePct` are empty on the first run; `firstSeenAt` is then the time of that first run |
| `observedAt` | when this row was read |

Every search also gets one free `summary` row: `searchUrl` (the address that was read), `totalResults` (the count Kleinanzeigen reports), `listingsRead`, `listingsReturned`, `newListings`, `priceChanges`, `pagesFetched`, `place`, `firstRun` and `stoppedBecause` (`limit`, `end-of-results`, `page-limit` or `error`). Searches that cannot be read give an `error` row. Both are free.

Shape of a row (shortened):

```json
{
  "type": "listing", "search": "iphone 15", "adId": "3535132090",
  "url": "https://www.kleinanzeigen.de/s-anzeige/iphone-15-pro-max-512-gb/3535132090-173-6619",
  "title": "Iphone 15 Pro Max 512 gb", "price": 599, "priceText": "599 €", "priceType": "fixed", "currency": "EUR",
  "postalCode": "85399", "city": "Hallbergmoos", "categoryId": 173,
  "postedText": "Heute, 23:00", "postedAt": "2026-10-08T21:00:00.000Z", "postedDate": "2026-10-08",
  "shippingPossible": true, "directBuy": false, "isWanted": false, "sellerType": null,
  "imageUrl": "https://img.kleinanzeigen.de/api/v1/prod-ads/images/2c/2c6859ea-43f2-485c-b2d4-c69866c671ab?rule=$_59.AUTO", "imageCount": 3,
  "details": [], "descriptionSnippet": "Ich verkaufe ein iPhone 15 Pro Max (512 GB) in einem sehr guten Zustand …",
  "isPromoted": false, "isNew": false, "firstSeenAt": "2026-10-08T21:02:08.000Z",
  "previousPrice": 599, "priceChange": 0, "priceChangePct": 0, "observedAt": "2026-10-08T21:03:11.602Z"
}
```

### Run it again and again

- **On a schedule** — save your input as a task and add a **Schedule**: the same searches every hour or every morning. Send each run to Google Sheets, Slack or email with an integration on the task.
- **Watch for changes** — Turn on **Only new or price-changed listings**.
- **In bulk** — Put every search in **Searches** in one run.

### Use it for

- **Deal alerts and resale** — schedule a search such as `iphone 15` with a maximum price, switch on *Only new or price-changed listings*, and get just the new listings and the price drops each time
- **Price research** — pull a few hundred listings for a model, a bike or a piece of furniture and work out what it really sells for, by city or across Germany
- **Market scans and sourcing** — see how many listings a search has (`totalResults`), who lists near a place, and how prices differ between places
- **Your own dashboards** — export to Sheets, a database or a BI tool, or call it from an AI agent

### Input

```json
{
  "searches": ["iphone 15", "https://www.kleinanzeigen.de/s-fahrraeder/rennrad/k0c217"],
  "location": "München",
  "radiusKm": 20,
  "minPrice": 100,
  "maxPrice": 600,
  "maxListingsPerSearch": 100,
  "onlyNew": false,
  "adType": "offers",
  "seller": "all",
  "includePromoted": false
}
```

- **searches** — keywords, or the address of a Kleinanzeigen results page (a category, or any search you set up on the site). Mix them freely.
- **location**, **radiusKm**, **minPrice**, **maxPrice**, **adType** (offers, wanted ads or both), **seller** (all, private, commercial) apply to keyword searches. An address is used exactly as you pasted it.
- **maxListingsPerSearch** — how many listings to read per search, newest first (default 100, up to 1,000).
- **onlyNew** — return only listings you have not seen before plus listings whose price changed.

The memory behind *since last run* is kept per search in a named key-value store in your Apify account, so the second run of the same Actor already shows changes. Schedule it (Apify → Schedules) for alerts. A search is the same search when its keywords, place, radius, price range, seller and offer/wanted settings are the same.

### Pricing

**$1.00 per 1,000 listings** ($0.001 each), plus $0.01 per run. Error rows and search summary rows are free. The $0.01 run-start fee is the one charge every run pays, even one that returns nothing. With *Only new or price-changed listings* you are charged only for the rows returned.

### Notes

- **Public data only**: what anyone sees on the search results pages without signing in. Seller names, seller or shop links, phone numbers and email addresses are not returned, and phone numbers and emails are also removed from titles and descriptions (including numbers the site cut off at the end of a description, such as `Tel.: 034296…`). The place is the postal code and city the card shows. Titles and descriptions are the seller's own words, so a name or a street address that a seller writes into the text itself is not removed.
- **Speed and limits**: the Actor asks for one page every 2 seconds, 25 listings per page, so 1,000 listings take about 80 seconds (measured on 9 Oct 2026: 40 pages in 80 seconds). Kleinanzeigen shows at most 1,250 results for any one search (page 51 sends you back to page 50), so split a very large search by place or price range.
- **Newest first**: searches you type are read sorted by date. An address keeps the sort it has. Posting times are Berlin time on the site; `postedAt` is converted to UTC. For listings older than yesterday the card shows only a date, so `postedAt` is empty and `postedDate` is filled. Many real-estate cards (the flat, house and rental category pages) show no date at all, so all three posting fields are empty there.
- **Paid "Top" ads** sit above the date-ordered results and have no posting date. They are skipped unless you switch on *Include promoted (Top) ads*.
- **Other places**: when a search with a place has only a few hits, Kleinanzeigen adds a second list called "Weitere Ergebnisse in anderen Orten" (more results in other places) with ads from all over Germany. Those are not results of your search, so they are never returned or charged.
- **Prices**: `negotiable` means the card says VB (*Verhandlungsbasis*); `price` is empty when it says only VB, and for categories that show no price (jobs, neighbourhood help, swaps). `priceChange` is empty when either price is missing.
- **Since last run**: on the first run of a search `isNew`, `previousPrice` and `priceChange` are empty, and *Only new or price-changed listings* returns everything it read. Listings are remembered for 45 days, up to 6,000 per search. "New" means not among the listings read in an earlier run of the same search, so if you raise *Listings to read per search* between runs, the extra, older listings you now reach also show as new. A listing someone re-posts under a new number counts as new.
- **Fields filled only in some cases**: `imageCount` is empty when the card shows no photo count; `imageUrl` is empty for listings without a photo; `details` is filled only for categories whose cards carry extra facts (cars, real estate, clothes); `sellerType` only where the card says *Von Privat* or shows a PRO shop; `descriptionSnippet` is the excerpt Kleinanzeigen puts on the results page, not the full text.
- **Place**: *Place* takes a city or a 5-digit postal code and is looked up with Kleinanzeigen's own place search (the first match is used; the summary row's `place` shows which). Results follow the place you choose, not where the Actor runs. Germany only.
- If Kleinanzeigen refuses the requests (for example HTTP 403) the search gets an error row; busy responses (429) and connection errors are retried twice.

Same idea for other classifieds sites: [Marktplaats Scraper](https://apify.com/northbell/marktplaats-scraper) and [Gumtree Scraper](https://apify.com/northbell/gumtree-scraper).

### For AI agents

`{"searches": ["iphone 15"], "maxListingsPerSearch": 50, "maxPrice": 500}` returns up to 50 listing rows, newest first, plus one summary row. Rows have `type`: `listing`, `summary` or `error`. To watch a search over time, call it again with the same input and `"onlyNew": true`: rows with `isNew: true` are new listings, rows with a non-zero `priceChange` are price changes (negative = cheaper).

# Actor input Schema

## `searches` (type: `array`):

One search per line: keywords such as "iphone 15", or the address of a Kleinanzeigen results page (https://www.kleinanzeigen.de/s-fahrraeder/c217, a category page, or any search you set up on the site). Place, price and seller filters below apply to keywords; an address is used as given.

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

A German city or a 5-digit postal code, for example Berlin or 80331. Applies to keyword searches. Leave empty to search all of Germany.

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

How far around the place to look. 0 (the default) means the place itself only. Used only together with a place.

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

Only listings priced at or above this amount. Applies to keyword searches. Leave empty for no lower limit.

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

Only listings priced at or below this amount. Applies to keyword searches. Leave empty for no upper limit.

## `maxListingsPerSearch` (type: `integer`):

How many listings to read for each search, newest first, 25 per page. Kleinanzeigen shows at most 1,250 results per search, so 1,000 is the most you can ask for. With "Only new or price-changed listings" switched on, this is how deep to look; only the new or changed ones are returned and charged.

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

Return only listings you have not seen in an earlier run of this search, plus listings whose price changed. The first run of a search has nothing to compare with and returns everything it read. Good for scheduled alerts.

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

Kleinanzeigen has offers (for sale) and wanted ads ("Gesuch", people looking to buy). Offers only is the default. Applies to keyword searches.

## `seller` (type: `string`):

Filter by the kind of seller Kleinanzeigen shows: private persons or commercial sellers. Applies to keyword searches. No seller names are ever returned.

## `includePromoted` (type: `boolean`):

Kleinanzeigen puts a few paid "Top" ads above the date-ordered results. They are skipped by default; switch this on to return them too (flagged isPromoted, with no posting date).

## Actor input object example

```json
{
  "searches": [
    "iphone 15"
  ],
  "radiusKm": 0,
  "maxListingsPerSearch": 100,
  "onlyNew": false,
  "adType": "offers",
  "seller": "all",
  "includePromoted": false
}
```

# Actor output Schema

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

Title, price, place, posting date, new/price-change flags and the link to each listing.

## `searches` (type: `string`):

One row per search: how many results Kleinanzeigen reports, how many were read, how many are new.

# 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 = {
    "searches": [
        "iphone 15"
    ]
};

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

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

```

## MCP server setup

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