# Kleinanzeigen.de Scraper: eBay Kleinanzeigen Ads (`nice_dev/kleinanzeigen-listings-scraper`) Actor

Scrape Kleinanzeigen.de (formerly eBay Kleinanzeigen) ads by keyword, category, location, seller or search URL: title, price, description preview, photos, GPS location, seller type and posting date. Monitor new ads on a schedule. Export JSON, CSV or Excel.

- **URL**: https://apify.com/nice_dev/kleinanzeigen-listings-scraper.md
- **Developed by:** [Nice Dev](https://apify.com/nice_dev) (community)
- **Categories:** E-commerce, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.19 / 1,000 ads

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

### 🛒 What is Kleinanzeigen.de Scraper (eBay Kleinanzeigen)?

**Kleinanzeigen.de Scraper** extracts **eBay Kleinanzeigen ads from [Kleinanzeigen.de](https://www.kleinanzeigen.de)** (the classifieds site formerly called eBay Kleinanzeigen): **title, price, description preview, all photos, exact publication time, GPS position and seller type**, for any keyword, category, city, seller or search URL. Schedule it to **monitor new ads**.

Type a **keyword** (`fahrrad`), pick a **category** and a **city**, or paste any Kleinanzeigen search URL, click **Start**, and download the ads in JSON, CSV or Excel. No login, nothing to set up, and it is **fast: about 1,000 ads in 40 seconds without details, about 100 ads with full details in a minute**.

### 📋 What data can you extract from Kleinanzeigen.de?

One item per ad, 69 fields:

| Category | What you get |
| --- | --- |
| 🏷️ **Ad** | title, link, the site's ad number, offer or wanted ad, status, top ad, labels — `AXA Solid Plus Fahrradschloss` |
| 💰 **Price** | price and currency, fixed / negotiable (VB) / free, the price as the site shows it, buy now, offers accepted — `33 €` |
| 📝 **Text, photos and documents** | description (preview; the full text with **Extract details**; plain text and the seller's own layout), every photo in full size, PDFs the seller attached |
| 🕒 **Dates** | publication time to the second, last edit — `2026-09-19 03:02:51` |
| 🗂️ **Category and item details** | category and parent category, every detail the seller filled in: make, mileage, size, rooms… — `Art: Zubehör` |
| 📍 **Location** | town (even where the site shows the district), district, postcode, federal state, street when given, GPS position — `München, Hadern, 80689` |
| 👤 **Seller** | name (with **Extract details**), private or commercial, member since, rating and badges, link to all their ads — `Autohaus Amelung` |
| ☎️ **Seller contacts** (with **Extract details**) | phone number, e-mail and legal notice (imprint), when the seller published them — `02191479471` |
| 🏪 **Shop** | shop number, name and link of commercial sellers |
| 🚚 **Shipping and payment** | shipping possible, carriers, lowest shipping price, secure payment — `2.99 €` |
| 👁️ **Views** | how many times the ad was viewed (option) |
| 🔎 **Search** | which search found the ad and at which position, when it was read, the site's untouched data (option) |

Every field, with an example, is listed in the **Output** section below.

Fields marked **(detail)** in the Output tab (`description` in full, `sellerName`, `sellerPhone`, `sellerEmail`, `sellerImprint`, `street`, `shippingPriceFrom`, `offerPossible`, `securePaymentPossible`, seller badges, `lastEditedAt`, `documents`) are filled only when **Extract details** is on (off by default: a listing-card-only scrape is much faster and cheaper). Tick it when you need the full description or the seller's contact.

### 🔎 eBay Kleinanzeigen scraper: every ad, one search or many

Kleinanzeigen.de is the German classifieds site formerly called eBay Kleinanzeigen. This scraper reads its ads the way the website shows them: pick a keyword, a category (`Fahrräder & Zubehör`), a city and a radius, or paste a search URL, a seller page or a single ad. Run it once, or schedule it with **Only new ads** to follow a search and get only the ads it has never returned.

### ✅ Why use Kleinanzeigen.de Scraper?

- 🚀 **Fast**: about 1,000 ads in 40 seconds in list mode, about 100 with full details in a minute (measured on Apify).
- 📚 **Deep**: up to **10,000 ads per search**, eight times what the website itself pages through; a bigger search is split by category, city or price band (see Tips).
- ☎️ **Seller contacts** (with **Extract details**): the phone number the seller published with the ad (about 69 % of commercial sellers, 4 % of private ones), and the e-mail and legal notice of commercial sellers (an e-mail for about half of them), plus their name, rating, badges and registration date.
- 🎛️ **Every filter of the site**, including the per-category ones: car make and model, mileage, first registration, fuel, equipment, rooms, floor area… (`attributeFilters`), plus price, radius, private/commercial, offers vs wanted ads, photo only, buy now.
- 🗂️ **Several searches in one run**: keywords × cities, sellers and shops to follow, single ad ids — each with its own cap so that one big city cannot eat the whole budget.
- 🔔 **Monitoring built in**: tick **Only new ads**, schedule the Actor, and each run returns (and charges) only what it has never delivered before — the publication time is exact to the second, so an hourly run returns exactly the new ads.
- 📅 **Date filters**: `postedAfter: "24 hours"` — with the default sort, a search stops at the first page that is entirely too old.
- 🔌 API, scheduling, monitoring, integrations (Make, Zapier, n8n, Google Sheets…), proxy rotation and JSON/CSV/Excel export via the Apify platform.

### 🚀 How to scrape Kleinanzeigen.de

1. Create a free Apify account.
2. Open **Kleinanzeigen.de Scraper** and type a **Search keyword** (e.g. `fahrrad`).
3. Narrow it down if you want: **Category** (`Fahrräder & Zubehör`), **Location** (`Berlin`, `10115`) and **Radius**, price range, private or commercial sellers.
4. Or paste your own Kleinanzeigen URLs into **Start URLs**: any search results page (the filters and the sort written in the URL are kept, pagination is automatic), a seller page, or single ad pages.
5. Set **Max ads** (100 by default, 0 = no limit) — and **Max ads per search** when you run several searches — then click **Start** and download the dataset in JSON, CSV, Excel or via API.

### 💰 How much does it cost to scrape Kleinanzeigen.de?

This Actor uses **pay per event** pricing: you pay for each ad, and for each option only when you turn it on — except that an ad you ask for by id or by URL is always read on its own page, so it is charged Details too.

| Event | Price per 1,000 |
| --- | --- |
| Ad (every row of the dataset) | **$0.22** on Free, $0.21 on Bronze, $0.20 on Silver, $0.19 on Gold |
| + Details: the ad's own page (full description, seller name, phone, e-mail and imprint, badges, shipping price) — only when **Extract details** is ticked | + $0.14 on Free, $0.13 on Bronze, $0.12 on Silver, $0.11 on Gold |
| + View count (`includeViewCount`) | + $1.19 on Free, $1.18 on Bronze, $1.17 on Silver, $1.16 on Gold |
| Filtered listing: an ad's own page read, then dropped by **With phone number only** (`requirePhone`) | $0.32 on Free, $0.31 on Bronze, $0.30 on Silver, $0.29 on Gold |
| Filter check: an ad dropped on its results page by a filter of this Actor (dates, excluded keywords, minimum photos, price types, shipping); an ad that passes the filters pays its row, not a check | $0.19 on Free, $0.18 on Bronze, $0.17 on Silver, $0.16 on Gold |
| Run start | $0.019 per run |

With the default settings (details off, list mode) 1,000 ads cost **$0.22** on the Free plan; with **Extract details** on **$0.36**. Example on the Free plan: 20,000 ads with details ≈ $7.22; a daily monitor of 300 new ads with details ≈ $0.13 a day. An option is charged only on the ads that got it (a view counter that failed is not charged). Each ad dropped by one of the Actor's own filters (dates, `excludeKeywords`, `minImages`, `priceTypes`, `shipping`) is charged as a *Filter check*; an ad that passes them is charged as an ad, nothing more; with **With phone number only** (`requirePhone`) the phone is only on the ad's own page, so each page read and then dropped for lack of a phone is charged as a *Filtered listing*. With `onlyNew`, an ad dropped by the same filters in a previous run is remembered: it is neither read nor charged again. The filters sent to the site with the search (price, radius, seller type…) cost nothing extra. Platform usage (compute, proxy) is included in the price.

### ⚙️ Input

```json
{
    "query": "fahrrad",
    "location": "Berlin",
    "radiusKm": 20,
    "maxItems": 200,
    "extractDetails": true
}
```

Several searches, a cap per search, only recent ads, only the ones not delivered before:

```json
{
    "searchQueries": ["fahrrad", "e-bike"],
    "locations": ["Berlin", "Hamburg"],
    "maxItemsPerQuery": 50,
    "postedAfter": "24 hours",
    "onlyNew": true,
    "stateKey": "bikes-de"
}
```

Cars of one make under 150,000 km, commercial sellers only:

```json
{
    "category": "216",
    "sellerType": "COMMERCIAL",
    "attributeFilters": { "autos.marke": "volvo", "autos.km": ",150000" },
    "minPrice": 1000,
    "maxPrice": 9000,
    "maxItems": 500
}
```

Or with your own URLs, a seller to follow and single ads:

```json
{
    "startUrls": [
        { "url": "https://www.kleinanzeigen.de/s-fahrrad/k0" },
        { "url": "https://www.kleinanzeigen.de/s-bestandsliste.html?userId=123745993" },
        { "url": "https://www.kleinanzeigen.de/s-anzeige/mein-fahrrad/3516612023-217-9212" }
    ],
    "listingIds": ["2371237393"],
    "maxItems": 500
}
```

| Field | Notes |
| --- | --- |
| `query`, `searchQueries` | Free-text search, exactly as typed on the site; `searchQueries` adds more keywords (one search each). Ignored when `startUrls` is set. |
| `category` | Category name (`Fahrräder & Zubehör`), URL key (`Fahrraeder`) or id (`217`). Subcategories are included. |
| `location`, `locations` | City, district or postcode (`Berlin`, `10115`), or the site's own id (`l3331`); `locations` adds more places. Every keyword is searched in every location (max 500 searches per run). |
| `radiusKm` | A circle of this many kilometres around the centre of each location; ignored without a location. Inside a big city a small radius returns fewer ads than 0 (Berlin, bikes: 34,600 with 0, 24,900 with 10 km). |
| `sellerIds`, `storeIds` | Every ad of these sellers (`123745993`) or commercial shops; each one is a search of its own, with the first keyword and the first location of the input, if any. |
| `listingIds` | Fetch single ads by id (`3516612023`) or URL, whatever the searches. Each is read on its own page: charged Details even with `extractDetails` off. |
| `startUrls` | Search results pages (the filters and the sort in the URL are kept, pagination is automatic), seller pages or single ad pages. When set, the search fields and the site filters above and below (`category`, `location`, `sellerIds`, `storeIds`, `radiusKm`, `minPrice`, `maxPrice`, `sellerType`, `adType`, `pictureRequired`, `buyNowOnly`, `sortBy`, `attributeFilters`) are ignored, and the log says which: set them on the site before copying the URL. |
| `extractDetails` | Open each ad for the full description, the seller's name, phone and imprint (default off; `requirePhone` needs it on). |
| `maxItems`, `maxItemsPerQuery` | Stop after this many ads for the whole run (`0` = unlimited) / for EACH search. |
| `minPrice`, `maxPrice` | Price range in euros. Either one set, even `0`, drops the ads that show no amount (VB without a price, most wanted ads); a free giveaway counts as 0 €. |
| `sellerType` | `ANY`, `PRIVATE` or `COMMERCIAL`. |
| `adType` | `ANY`, offers (`OFFERED`) or wanted ads, the site's Gesuche (`WANTED`). |
| `pictureRequired`, `buyNowOnly` | With a photo only; buy-now only. |
| `sortBy` | Order of the results: `DATE_DESCENDING`, `PRICE_ASCENDING`, `PRICE_DESCENDING`, `DISTANCE_ASCENDING`. |
| `attributeFilters` | The site's per-category filters, as an object of attribute names and values (see the Tips below): car make, fuel, equipment, mileage… Numbers and dates take a range with open ends. |
| `postedAfter`, `postedBefore` | Publication date range: `2026-09-01`, or a period before now (`24 hours`, `7 days`, `2 weeks`, `1 month`). |
| `excludeKeywords`, `requirePhone`, `minImages`, `shipping` | Drop the ads whose title or description holds one of these words (also inside a longer word); keep only the ads with a phone number, with at least N photos, or with shipping. |
| `priceTypes` | Keep only some kinds of price: `SPECIFIED_AMOUNT` (fixed), `PLEASE_CONTACT` (negotiable, VB), `FREE` (Zu verschenken). |
| `onlyNew`, `stateKey`, `resetState` | Monitoring: only the ads never delivered under this memory key; `resetState` forgets the memory. |
| `includeViewCount`, `includeRaw` | Add the number of views of each ad (1 extra request per ad, and it counts as one visit); add the untouched site data. |
| Advanced | `proxyConfiguration` (Apify proxy by default, included in the price; the residential proxy is not available), `maxConcurrency`, `maxRequestsPerMinute` (with `includeViewCount`, an ad page and its view counter count as two requests), `maxRequestRetries`, `debugLog`. |

### 📦 Output

One real item of a run, shortened (69 fields in the dataset):

```json
{
    "id": "3516612023",
    "url": "https://www.kleinanzeigen.de/s-anzeige/axa-solid-plus-fahrradschloss/3516612023-217-9212",
    "title": "AXA Solid Plus Fahrradschloss Kette 100 cm",
    "description": "Fahrradschloss, gebraucht. 2 Schlüssel.",
    "price": 33,
    "currency": "EUR",
    "priceType": "SPECIFIED_AMOUNT",
    "priceText": "33 €",
    "adType": "OFFERED",
    "publishedAt": "2026-09-19T03:02:51.000Z",
    "category": "Fahrräder & Zubehör",
    "location": "88048 Friedrichshafen",
    "city": "Friedrichshafen",
    "district": null,
    "postalCode": "88048",
    "latitude": 47.696945,
    "longitude": 9.496915,
    "sellerId": "149605046",
    "sellerName": "Thomas",
    "sellerType": "PRIVATE",
    "sellerPhone": null,
    "images": ["https://img.kleinanzeigen.de/api/v1/prod-ads/images/fc/fc5b394b.jpg"],
    "imageCount": 3,
    "attributes": { "Art": "Zubehör", "Typ": "Elektrofahrräder" },
    "shippingAvailable": true,
    "searchUrl": "https://api.kleinanzeigen.de/api/ads.json?q=fahrrad&size=100&page=0",
    "searchQuery": "fahrrad",
    "searchRank": 1,
    "scrapedAt": "2026-09-20T06:00:00.000Z"
}
```

You can download the dataset in various formats such as JSON, HTML, CSV or Excel.

#### All 69 fields

| Fields | What you get |
| --- | --- |
| `id`, `url`, `title` | `3516612023`, `https://www.kleinanzeigen.de/s-anzeige/...`, `AXA Solid Plus Fahrradschloss` |
| `description`, `descriptionHtml`, `descriptionFormat`, `isDescriptionTruncated` | full text, the seller's own line breaks, `text/markdown`, `false` |
| `price`, `currency`, `priceType`, `priceText`, `isNegotiable`, `buyNowPossible` | `33`, `EUR`, `SPECIFIED_AMOUNT` / `PLEASE_CONTACT` / `FREE`, `33 €`, `false`, `false` |
| `adType`, `status`, `isTopAd`, `features`, `labels` | `OFFERED`, `ACTIVE`, `false`, `["TOPAD"]`, `["Versand möglich"]` |
| `publishedAt`, `lastEditedAt` | `2026-09-19T03:02:51.000Z` (exact to the second — what the date filters read), `null` |
| `categoryId`, `category`, `categorySlug`, `parentCategoryId` | `217`, `Fahrräder & Zubehör`, `Fahrraeder`, `210` |
| `location`, `city`, `district`, `postalCode`, `state`, `street` | `80689 Hadern`, `München` (the town, even where the site prints the district), `Hadern`, `80689`, `Bayern`, `Karl-Kahlhöfer-Str. 5` |
| `latitude`, `longitude`, `locationRadiusKm`, `locationId` | `47.696945`, `9.496915`, `6.07`, `9212` |
| `sellerId`, `sellerName`, `sellerType`, `sellerAccountType` | `149605046`, `Autohaus Amelung`, `PRIVATE`, `PRO` |
| `sellerPhone`, `sellerEmail`, `sellerImprint`, `sellerProfileUrl` | `02191479471`, `info@mg-amelung.de` (from a commercial seller's legal notice), that legal notice, the page listing all their ads |
| `sellerActiveSince`, `sellerRating`, `sellerBadges` | `2022-10-21T13:55:03.000Z`, `0.978`, `[{ "name": "rating", "level": 2 }]` |
| `sellerRatingLevel`, `sellerFriendlinessLevel`, `sellerReliabilityLevel` | `2`, `3`, `1` |
| `storeId`, `storeTitle`, `storeUrl` | `99090`, `Autohaus Amelung`, `https://www.kleinanzeigen.de/pro/...` |
| `images`, `imageUrl`, `imageCount`, `documents` | photo URLs in full size, the first one, `3`, PDFs attached by the seller |
| `attributes`, `attributeList` | `{ "Art": "Zubehör" }` and the same with machine names, units and types |
| `shippingAvailable`, `shippingOptions`, `shippingPriceFrom`, `securePaymentPossible`, `offerPossible` | `true`, `["HERMES_001"]`, `2.99` (euros, the site's "Versand ab"), `false`, `false` |
| `viewCount` | `32560` (only with **View count**) |
| `searchUrl`, `searchQuery`, `searchLocation`, `searchRank` | which search returned the ad, and at which position |
| `raw`, `scrapedAt` | the untouched site data (only with **Include raw data**), ISO timestamp |

### 💡 Tips

#### How to get more results

One search returns at most 10,000 ads (the site's own limit). Beyond that, split it: by category, by city (`locations`), or by price band (`minPrice` / `maxPrice`) — every piece is a search of its own with its own cap. Set `maxItems` to `0` to take everything a search has.

#### How to reduce costs

The price is per ad, so the levers are `maxItems`, `maxItemsPerQuery`, the filters sent to the site with the search (price, radius, seller type, category filters: a listing they drop is never downloaded — the Actor's own filters are charged per ad they drop) and `onlyNew` for recurring runs (you never pay twice for the same ad). Leave `extractDetails` off (the default) when the card is enough (title, price, short description, photos, location, date, seller id and type): the ad then costs $0.22 per 1,000 instead of $0.36 (Free plan), and the run is much faster. Leave `includeViewCount` off unless you need the number of views.

#### Several searches in one run

Fill `searchQueries` and / or `locations`: the Actor runs one search per keyword × city (3 keywords × 4 cities = 12 searches, up to 500 per run). The single `query` and `location` fields still work and are added to the lists. Sellers (`sellerIds`) and shops (`storeIds`) are searches too: the category, the filters and the FIRST keyword and FIRST location of the input apply to them (one search per seller or shop, never one per keyword × city). An ad found by several searches is saved — and charged — once. Set `maxItemsPerQuery` to give every search its own cap.

#### Per-category filters (`attributeFilters`)

Every category of the site has its own attributes, and the Actor passes them all: run a first search, look at `attributeList` in the output (`autos.marke`, `autos.km`, `haus_kaufen.zimmer`…), and put what you need in `attributeFilters`. A name the site does not know filters nothing, so check `attributeList` rather than guessing.

#### Monitoring: only the new ads

Tick **Only new ads** (`onlyNew`) and schedule the Actor. The first run returns everything; each later run skips the ads already delivered: they are not saved, not charged, and their page is not even opened. The memory lives in a named key-value store of your account (`kleinanzeigen-listings-scraper-seen`, up to 150,000 ads per key) and is only updated with ads that really reached the dataset, so a failed run never hides anything. Give each schedule its own `stateKey` (two schedules sharing a key would hide each other's ads), and tick `resetState` once to start over. With the default sort (newest first), a search stops once it meets 500 ads in a row you already have — or, under a smaller `maxItems` / `maxItemsPerQuery`, once 80% of that cap are ads you already have (paid top ads pinned above the sort are not counted); another sort reads the search to its end or to your caps.

#### Filter by publication date

`postedAfter` and `postedBefore` take a date (`2026-09-01`, the whole day is included, German time zone) or a period before now (`24 hours`, `7 days`, `2 weeks`, `1 month`; via the API also a full ISO date-time). The site gives the publication time to the second, so an hourly run with `postedAfter: "1 hour"` returns exactly the new ads. Each ad the date filter checks is charged as a *Filter check*, kept or not; filtered-out ads are not saved and do not count in `maxItems`; the run summary tells how many were filtered. With `postedAfter` and the default sort (newest first), a search stops at the first page that is entirely too old; another sort reads on, up to your caps, keeping only the recent ads.

### 🔌 Integrations and API

Call the Actor via the Apify API, the JavaScript or Python clients, or connect it with integrations and webhooks (Make, Zapier, n8n, Google Sheets, Slack, Airtable…). The dataset can be fetched as JSON or CSV from any tool.

### 🤖 Use with AI agents (MCP)

AI agents (Claude, ChatGPT, Cursor…) can find and run this Actor through the [Apify MCP server](https://mcp.apify.com), billed to their Apify account like any run. It returns one item per Kleinanzeigen.de ad. Actor id: `nice_dev/kleinanzeigen-listings-scraper`; MCP server with this Actor only: `https://mcp.apify.com/?tools=fetch-actor-details,nice_dev/kleinanzeigen-listings-scraper`.

Smallest input, for a cheap first call:

```json
{
    "query": "fahrrad",
    "maxItems": 10
}
```

Key output fields: `url`, `title`, `price`, `publishedAt`, `city`, `sellerName`, `sellerPhone`, `images`.

Cost: $0.22 per 1,000 ads on the Free plan (details off, the default; $0.36 with **Extract details** on) plus $0.019 per run start; the view count and the filters cost extra, see the pricing section above. Cap each call with `maxItems` and, through the API, with the run option `maxTotalChargeUsd`.

### ❓ FAQ

#### Is it legal to scrape Kleinanzeigen.de?

The Actor only reads what Kleinanzeigen.de shows publicly to any anonymous visitor. It logs in to nothing. Results can contain personal data — a seller's name, and the phone number or imprint they chose to publish with their ad — which is protected by GDPR: do not store it without a legitimate reason, and do not use it for unsolicited marketing. You are responsible for using the data in compliance with Kleinanzeigen's Terms of Use and applicable law. This Actor is not affiliated with Kleinanzeigen.de or eBay.

#### Does it need a login or a proxy?

No login. The proxy is included in the price: leave the default setting (the residential proxy is not available). A request the site turns away is retried at once on a new proxy session, up to 10 times on top of the retries (without a proxy, after a pause of 5 seconds, doubled at each retry up to 150 seconds). Behind the Apify proxy, a request turned away on 3 proxy IPs (8 for the lookups made before the crawl) is then sent without proxy, and the run's log says so; with your own proxy URLs, every request goes through them.

#### Why am I charged for ads I did not get with `requirePhone`?

A phone number is only on the ad's own page, so **With phone number only** reads that page before it knows whether the seller published one. Each page read and then dropped is charged as a *Filtered listing*: the search is paid even when it finds few ads. Never charged as a *Filtered listing*: an ad dropped on its results page (another filter, a date — a *Filter check* there), an ad a previous run already delivered (`onlyNew`), a page that failed or an ad removed meanwhile, and never twice the same ad (a retry, a resurrected run). The run stops after 300 pages in a row without a phone — private sellers rarely publish one (about 4 %) — and never goes past your maximum cost: choose **Seller type: Commercial sellers only** to get far more phone numbers per page read.

#### Is the data safe to open in Excel or to show on a web page?

Titles and descriptions are the sellers' own words, copied as they are. A text can begin with `-`, `+`, `=` or `@` (a phone number, a title such as `-20% heute`): Excel and Google Sheets may read such a cell of a CSV file as a formula or as a number. The Actor leaves the text as it is, so that the JSON and the API give the real value: when you open a CSV, import these columns as text. Every URL field holds an http(s) URL or `null`; `descriptionHtml` holds the seller's own markup, which the Actor does not sanitize. On a web page, escape every field like any text written by a stranger.

#### Known limitations

- One search returns at most 10,000 ads: split it (see Tips) to go further.
- `description` in full, `sellerName`, `sellerPhone`, `sellerEmail`, `sellerImprint`, `street`, `offerPossible`, `securePaymentPossible` and the seller badges come from the ad's own page: they are empty (`null`, `[]`, the short preview for `description`) when `extractDetails` is off.
- A phone number is only there when the seller published one with the ad (about 69 % of commercial sellers, 4 % of private ones).
- A shop URL (`/pro/...`) carries no id: use the `storeId` a previous run returned.
- `onlyNew` remembers ad ids, not their content: an ad whose price changed is not returned again.
- Two runs sharing the same `stateKey` at the same time may both return the same new ad.
- `includeViewCount` asks the site for the counter of each ad, which counts as one visit for the seller, exactly like opening the page in a browser.

**A run that reaches its timeout** stops itself about 45 seconds before it: no new page is asked, what it read is saved and, with `onlyNew`, remembered, and the run ends *Succeeded* with "Stopped before the run's timeout". In list-only mode with `includeViewCount`, the ads whose counter was not read yet are saved without it. Resurrect the run to go on from there, or give the next run a longer timeout (Run options).

**A run the platform stops without warning** (out of memory)

- Resurrect it: it goes on from where it stood at most a minute before the stop. What it had read since is read again, and the ads already saved are skipped: none is delivered or charged twice, and `maxItems` still counts them.
- With `onlyNew`, the memory is saved after each write (at most once every 2 seconds): resurrect the stopped run and the ads it had saved meanwhile join the memory; leave it stopped for good, and the next run may return the ads of its last 2 seconds once more.

#### Something doesn't work?

The last line of the log counts the ads saved, filtered out and no longer on Kleinanzeigen.de (removed while the run was reading them), and the requests that failed after every retry. Those requests and the removed ads are listed, with the reason, in the `FAILED_REQUESTS` record of the run's key-value store. A run that saved nothing and had failed requests fails, and its last message gives the cause (a search URL that does not exist says so, instead of "run it again"). A run that saved some ads fails too when at least as many requests failed for good as were read (page 1 read, the next pages or the ad pages blocked), or — with `includeViewCount` — when at least as many view counters failed as were read (most rows would lack the view count you asked for): a green run with a short dataset would hide the outage. One failed request among many is only a warning.

If Kleinanzeigen.de changes its pages, you are told instead of paying for blank rows. A results page that counts ads but gives none the Actor can read, or an ad page that comes back empty, is an error (retried, then listed in `FAILED_REQUESTS`), never a quiet "No listings found". If the first 20 ads read all lack their title, publication date, seller id, seller type, postcode, place id, category, the amount of a price typed as an amount, the type of a price that has an amount or — with `extractDetails` — description, seller name or the imprint of a professional seller, the run saves nothing more, stops and fails, and its last message names the missing field: at most those first ads are charged. An ad that a filter drops because it lacks that field (`postedAfter` / `postedBefore` for the date, `priceTypes` for the price type) counts among those 20. A scheduled `onlyNew` run is judged on the first 20 ads of its pages, the ones a previous run already delivered included: if the site changed, it stops before saving anything. A run of fewer ads (a small **Max ads**) is checked at its end, from 5 ads: its ads are already saved, and the run fails with the missing field instead of ending green. If the site refuses the access of its app (HTTP 401), the run fails before reading anything and says so: running it again will not help, please report it.

### 🛟 Support

Open an issue in the **Issues** tab with a link to your run: the run log and the `FAILED_REQUESTS` record of the key-value store show exactly which URLs failed and why.

# Actor input Schema

## `startUrls` (type: `array`):

Kleinanzeigen search-result URLs (`https://www.kleinanzeigen.de/s-fahrrad/k0`, pagination is automatic), seller pages (`https://www.kleinanzeigen.de/s-bestandsliste.html?userId=123745993`) or single listing URLs (`https://www.kleinanzeigen.de/s-anzeige/mein-fahrrad/3516612023-217-9212`). A pasted search keeps the search, the filters and the sort written in its URL: when this list is not empty, the search fields below (keyword, category, location, sellers, shops) AND the **Site filters** are ignored (the log names them) — set those on the site before copying the URL. The other filters (dates, excluded keywords, photos…), the caps and monitoring still apply. Max 1 000 URLs.

## `query` (type: `string`):

Free-text search, exactly as typed on the site (e.g. `fahrrad`). Empty = every listing of the chosen category / location.

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

Several keywords in one run: one search per keyword (times each location below). Added to **Search keyword**; listings found by several searches are saved once.

## `category` (type: `string`):

Category name as on the site (`Fahrräder & Zubehör`), its URL slug (`Fahrraeder`, `c217`) or its numeric id (`217`). Empty = all categories. Subcategories are included.

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

City, district or postcode as typed on the site (e.g. `Berlin`, `10115`), or the site's own id (`l3331`). A name several places share gives the place of that name, the biggest one if several (`Hagen` = Hagen in NRW, not Hagen im Bremischen; `Frankfurt` = Frankfurt am Main; the log names the others — type the full name, `Frankfurt (Oder)`, to choose another). Empty = whole Germany.

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

Several locations in one run: every keyword is searched in every location (3 keywords × 4 locations = 12 searches, max 500). Added to **Location**.

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

Radius of a circle around the centre of each location. 0 = the location as the site defines it. Inside a big city a small radius returns FEWER ads than 0 (Berlin: 24,900 ads with 10 km against 34,600 with 0). Ignored when no location is given.

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

Every ad of these sellers: numeric user id (`123745993`) or seller-page URL (`https://www.kleinanzeigen.de/s-bestandsliste.html?userId=123745993`). Each seller counts as one search (its own per-search cap); the category, the filters and the first keyword and first location of the input still apply to it.

## `storeIds` (type: `array`):

Every ad of these commercial shops: the numeric shop id this Actor returns for a commercial ad (a shop page URL carries no id). Same rules as seller ids.

## `listingIds` (type: `array`):

Fetch these listings directly, by id (`3516612023`) or by URL. Always added to the searches above; each id costs one request and is not affected by the site filters. Such a listing is always read on its own page, so it is charged the **Extract details** price even when that box is off.

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

Maximum number of listings to save for the whole run (after deduplication and filters). 0 = no limit.

## `maxItemsPerQuery` (type: `integer`):

Cap for EACH search (keyword × location, seller, shop or search URL), so that the first search cannot use up the whole **Max listings** budget. 0 = no per-search cap. The site itself never returns more than 10 000 listings per search.

## `extractDetails` (type: `boolean`):

Open each listing (1 extra request per listing) for the full description, the seller's name, registration date, rating and badges, the shop imprint and e-mail, the street, the phone number when the seller published one, the shipping price, and whether a price offer and secure payment are possible. Charged as an option on top of the listing (see Pricing). Off (the default) = listing-card fields only (≈ 100 listings per request, much faster, and cheaper: no Details charge). **With phone number only** needs it on.

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

Lowest price. With **Min price** or **Max price** set, even `0`, the site drops the listings that show no amount (`VB` without a price, most wanted ads); a free giveaway counts as 0 €.

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

Highest price. Set, it drops the listings that show no amount too (see **Min price**).

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

Keep private sellers only, commercial sellers only, or both.

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

Offers, the site's Angebote — or wanted ads, its Gesuche — or both.

## `pictureRequired` (type: `boolean`):

Keep only the listings that have at least one photo.

## `buyNowOnly` (type: `boolean`):

Keep only the listings that can be bought directly (`Direkt kaufen`).

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

Order in which the site returns the listings. Newest first is the default and the one used to stop early on **Posted after** / **Only new listings**.

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

Per-category filters of the site, as `attribute: value`. The attribute names are the `name` of each entry of `attributeList` in the output — not the German labels of `attributes` (e.g. `autos.marke`: `volvo`, `autos.model`: `v40`, `autos.fuel`: `diesel`, `autos.navi`: `true`). Numbers and dates take a range `min,max` with open ends allowed: `autos.km`: `,50000`, `autos.power`: `100,200`, `autos.ezdate`: `2015-01,2020-12`. Needs a category that owns the attribute; one value per attribute.

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

Only listings published on or after this date: `2026-09-01`, a full ISO date-time, or a period before now such as `24 hours`, `7 days`, `2 weeks`, `1 month`. The site gives the publication time to the second, so a run every hour with `1 hour` returns exactly the new ads.

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

Only listings published on or before this date (the whole day is included), or older than a period such as `30 days`.

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

Drop the listings whose title or description contains one of these words (case and German umlauts ignored; a word inside a longer one counts: `rad` drops `Fahrrad`). Without **Extract details** the description is the short preview of the card.

## `priceTypes` (type: `array`):

Keep only these kinds of price: a fixed amount, negotiable (VB, with or without an amount), or free (Zu verschenken). Empty = all. Checked on each listing; a listing it drops is charged as a filter check.

## `requirePhone` (type: `boolean`):

Keep only the listings whose seller published a phone number (≈ 69 % of commercial sellers, ≈ 4 % of private ones). Needs **Extract details**. The phone is only on the listing's own page: each page read, then dropped for lack of a phone, is charged as a *Filtered listing* event (see the pricing). On private sellers, hundreds of pages can be read for few listings: the run stops after 300 pages in a row without a phone, and never goes past your maximum cost.

## `minImages` (type: `integer`):

Drop the listings with fewer photos than this. 0 = no filter.

## `shipping` (type: `boolean`):

Keep only the listings whose seller offers shipping (`Versand möglich`).

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

Skip the listings that a previous run (same **Memory key**) already delivered: they are not saved and not charged, and their page is not even opened. First run = everything is new.

## `stateKey` (type: `string`):

Name of the memory used by **Only new listings**. Give each schedule / task its own key (e.g. `berlin-bikes`) so that they do not share their memory. Letters, digits, `-` and `_`.

## `resetState` (type: `boolean`):

Forget everything remembered under this **Memory key** before the run: this run returns (and charges) every listing again. Untick it afterwards.

## `includeViewCount` (type: `boolean`):

Add how many times the listing has been viewed (`viewCount`). Costs 1 extra tiny request per listing and counts as one visit on the site, exactly like opening the page in a browser.

## `includeRaw` (type: `boolean`):

Add a `raw` field with the untouched JSON of the site (listing card and detail). Useful to recover a field this Actor does not map yet; makes the dataset ≈ 5× bigger.

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

Apify Proxy or your own proxies. Keep the default: it is included in the price. The residential Apify proxy is not available in this Actor.

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

Maximum number of requests processed in parallel.

## `maxRequestsPerMinute` (type: `integer`):

Global request rate, for every request to the site. With **View count** on, a listing page and its view counter count as TWO requests here (the counter is a request of its own, read together with its page). Lower it if the site answers HTTP 429 / 403 in the log.

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

Retries per request before it is marked as failed. Behind a proxy, a request the site turns away is also retried on a new proxy session up to 10 times without using up these retries.

## `debugLog` (type: `boolean`):

Include debug messages in the run log.

## Actor input object example

```json
{
  "startUrls": [],
  "query": "fahrrad",
  "searchQueries": [],
  "locations": [],
  "radiusKm": 0,
  "sellerIds": [],
  "storeIds": [],
  "listingIds": [],
  "maxItems": 100,
  "maxItemsPerQuery": 0,
  "extractDetails": false,
  "sellerType": "ANY",
  "adType": "ANY",
  "pictureRequired": false,
  "buyNowOnly": false,
  "sortBy": "DATE_DESCENDING",
  "attributeFilters": {},
  "excludeKeywords": [],
  "priceTypes": [],
  "requirePhone": false,
  "minImages": 0,
  "shipping": false,
  "onlyNew": false,
  "stateKey": "default",
  "resetState": false,
  "includeViewCount": false,
  "includeRaw": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 8,
  "maxRequestsPerMinute": 240,
  "maxRequestRetries": 5,
  "debugLog": false
}
```

# Actor output Schema

## `results` (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 = {
    "query": "fahrrad",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("nice_dev/kleinanzeigen-listings-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 = {
    "query": "fahrrad",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("nice_dev/kleinanzeigen-listings-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 '{
  "query": "fahrrad",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call nice_dev/kleinanzeigen-listings-scraper --silent --output-dataset

```

## MCP server setup

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