# Kleinanzeigen.de Scraper - Ads, Prices & Seller Details (`abotapi/kleinanzeigen-de-scraper`) Actor

From $1/1K. Scrape Kleinanzeigen.de ads by keyword, category, location, price, seller type, or search URL. Extract descriptions, photos, attributes, seller details, posted dates, category paths, and imprint data. Returns flat JSON records ready for spreadsheets or databases.

- **URL**: https://apify.com/abotapi/kleinanzeigen-de-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 6 total users, 1 monthly users, 63.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

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.de Scraper: Classifieds Listings, Prices, Sellers & Photos

Kleinanzeigen.de Scraper turns Germany's largest classifieds marketplace (formerly eBay Kleinanzeigen) into structured data. Search by keyword, category, location, radius, price, offer type and seller type across any category, vehicles, electronics, furniture, property, jobs, fashion, hobby and more, or paste your own Kleinanzeigen.de search or category URLs. Each ad comes back as one flat record with price, full description, every photo, the attribute list, the seller profile and, for registered traders, the legal imprint. Export to JSON, CSV or Excel, or pull results straight into your app through the API.

### Why This Scraper?

- **Two ways in.** A filter builder (keyword, category id, location with radius, min/max price, offered vs wanted, private vs commercial, sort) or a paste-URL mode that accepts any Kleinanzeigen.de search or category URL.
- **Every category, not just one niche.** The same filters and fields work whether you're tracking bicycles, cars, flats, electronics, jobs or collectibles.
- **Automatic forward pagination.** 27 ads per page, walked until Kleinanzeigen.de's own real end of results or your page limit, whichever comes first.
- **About 40 fields per ad.** Full description, all photo URLs, attribute key/value pairs, category path, seller name/type/registration date, and the commercial-seller legal imprint (name, address, email) when the seller is a registered trader.
- **A lean, cheaper mode.** Turn off detail fetching for a lighter run that still returns about 14 fields straight from the search results.
- **Deduplicates promoted listings** that repeat across result pages, so you don't pay for the same ad twice.
- **Built for schedules.** Incremental mode tags every ad NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED on recurring runs, and a resumable full-catalogue walk survives platform interruptions.

### Use Cases

- **Resellers and classifieds arbitrage:** watch a category and price band for underpriced items worth flipping.
- **Market and pricing research:** build price distributions for a product type or city over time from real, live asking prices.
- **Property and rental scouting:** monitor a city or postal code for new listings the moment they post.
- **Local lead generation:** find people offering or seeking specific goods or services in a chosen city or radius.
- **Marketplace monitoring:** schedule a saved search and get only new and changed ads each run with incremental mode.

### Data You Get

> Sample shape, values are illustrative placeholders, not from a live ad.

| Field | Example |
| --- | --- |
| `adId` | `"3400000001"` |
| `url` | `"https://www.kleinanzeigen.de/s-anzeige/sample-listing-title/3400000001-217-0001"` |
| `title` | `"Sample Listing Title"` |
| `descriptionShort` | `"First sentence of the ad as shown in the result list."` |
| `descriptionFull` | `"Full ad description text appears here when fetchDetails is true."` |
| `price` | `"1.500 € VB"` |
| `priceAmount` / `priceType` / `currency` | `1500` / `"NEGOTIABLE"` / `"EUR"` |
| `locationText` | `"10115 Berlin - Mitte"` |
| `postalCode` / `cityName` | `"10115"` / `"Mitte"` |
| `latitude` / `longitude` / `locationId` / `categoryParentId` | `52.5200` / `13.4050` / `"3331"` / `"195"` | the ad's map position, its location id, and the top-level category id; `null` when the ad hides them or `fetchDetails` is off |
| `categoryId` / `categoryName` | `"217"` / `"Fahrräder & Zubehör"` |
| `categoryPath` | `"Auto, Rad & Boot > Fahrräder & Zubehör"` |
| `postedDate` / `postedDateText` | `"2026-01-01"` / `"Heute, 09:00"` |
| `imageCount` / `imageUrls` | `5` / `["https://img.kleinanzeigen.de/api/v1/prod-ads/images/00/00000000-0000-0000-0000-000000000000?rule=$_59.AUTO"]` |
| `attributes` / `attributesRaw` | `{ "Art": "Herren", "Typ": "Elektrofahrräder" }` / `[{ "name": "Art", "value": "Herren" }]` |
| `isPro` / `isTopAd` / `isSold` | `false` / `false` / `false` |
| `sellerId` / `sellerName` / `sellerType` | `"100000000"` / `"Sample Seller"` / `"PRIVATE"` |
| `sellerActiveSince` / `sellerOtherAdsCount` | `"2026-01-01"` / `4` |
| `sellerSatisfaction` / `sellerFriendliness` / `sellerReliability` / `sellerBadges` | seller badges as shown on the ad, e.g. `"TOP Zufriedenheit"`, `"Sehr freundlich"` (empty when the seller has none) |
| `rooms` / `size` / `lotArea` / `floor` / `numberOfFloors` / `yearConstructed` / `sleepingRooms` / `bathrooms` / `flatType` / `houseType` / `vacantAtFrom` | real-estate ads: typed values from the detail attribute list, empty when the ad does not state them |
| `rentCold` / `warmRent` / `ancillaryCosts` / `heatingCosts` / `deposit` / `provision` / `purchasePrice` | real-estate cost breakdown in EUR where the ad states it |
| `balcony` / `terrace` / `fittedKitchen` / `garden` / `cellar` / `elevator` / `garage` / `guestWc` / `isFurnished` / `wgPossible` / `swapOffer` / `bathtub` / `features` | real-estate amenity flags built from the ad's feature list (empty for non-real-estate ads) |
| `street` / `adStatus` | street line when shown, and `active` / `reserved` / `sold` |
| `imprint` | `{ "raw": "Sample Trader\nEinzelunternehmer\n10115 Berlin, Sample Str. 1\ntrader@example.com", "lines": ["Sample Trader", "Einzelunternehmer"], "email": "trader@example.com", "phone": null, "vatId": null, "registerNumber": null, "registerCourt": null, "managingDirector": null }` (commercial sellers only, `null` for private sellers) |
| `searchUrl` / `scrapedAt` | `"https://www.kleinanzeigen.de/s-fahrrad/k0"` / `"2026-01-01T00:00:00.000Z"` |

Also included: `tags` (site badges such as shipping available) and `adType` (`OFFERED` or `WANTED`). Fields that only come from the detail page (`descriptionFull`, `categoryName`, `categoryPath`, extra photos beyond the first, `attributes`, seller fields, `imprint`, `postedDate`) are `null` or empty when `fetchDetails` is `false`. In Incremental mode every record also carries `changeType`, `changedFields`, `firstSeenAt` and `lastSeenAt`.

### How to Use

1. Pick a **mode**: `search` (build from the filters below) or `url` (paste ready-made Kleinanzeigen.de search or category URLs).
2. For search mode, fill in keyword, category, location, radius, price range, offer type, seller type and sort. For url mode, paste one or more search URLs.
3. Set **Max pages per search**, **Max listings total** and **Fetch full ad details** to shape run size, depth and cost, then click Start.
4. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Search by keyword:**

```json
{
  "mode": "search",
  "keyword": "fahrrad",
  "maxPages": 3,
  "maxListings": 50,
  "fetchDetails": true
}
```

**Search a category in a city with a radius and a price range, newest first:**

```json
{
  "mode": "search",
  "keyword": "",
  "categoryId": "217",
  "location": "Berlin",
  "radiusKm": 20,
  "minPrice": 100,
  "maxPrice": 800,
  "sortBy": "date",
  "maxPages": 5,
  "maxListings": 100,
  "fetchDetails": true
}
```

**Only private sellers, wanted ads:**

```json
{
  "mode": "search",
  "keyword": "sofa",
  "offerType": "gesuche",
  "sellerType": "privat",
  "maxPages": 2,
  "fetchDetails": false
}
```

**Paste several search URLs (filter fields are ignored in this mode):**

```json
{
  "mode": "url",
  "urls": [
    "https://www.kleinanzeigen.de/s-fahrrad/k0",
    "https://www.kleinanzeigen.de/s-berlin/sofa/k0l3331"
  ],
  "maxPages": 3,
  "maxListings": 80,
  "fetchDetails": true
}
```

#### Supported URL shapes

| Shape | Example |
| --- | --- |
| Keyword search | `/s-fahrrad/k0` |
| Category page | `/s-fahrraeder/c217` (any slug; the id decides) |
| Keyword inside a category | `/s-fahrraeder/mountainbike/k0c217` |
| Start page | `/s-seite:2/fahrrad/k0` (walks forward from page 2; EXPIRED detection is skipped because earlier pages were not scanned) |
| Start page in a category | `/s-fahrraeder/seite:2/c217` (also `/s-berlin/seite:2/c217l3331`); the next pages are fetched as `seite:3`, `seite:4` ... in the same form |
| Start page by query | `/s-fahrrad/k0?sortingField=SORTING_DATE&pageNum=2` (walks forward from page 2) |
| Sorted | `?sortingField=SORTING_DATE` or `PRICE_AMOUNT` |
| City and radius / postcode | `/s-berlin/fahrrad/k0l3331r20`, `/s-85737/fahrrad/k0l7554`, `/s-berlin/c217` |
| City only, no keyword | `/s-berlin/k0l3331` |
| Price, offer and seller filters | `/s-anzeige:angebote/anbieter:privat/preis:100:300/fahrrad/k0` |
| Single ad | `/s-anzeige/<slug>/<id>-<cat>-<loc>` or `/s-anzeige/<id>`: returns exactly that ad, keyed on its ad id, not its similar ads |
| Seller listings | `/s-bestandsliste.html?userId=1`: the seller's ads with their own paging |
| Commercial shop page | `/pro/<name>`: the shop front shows only its first 25 ads with no paging, so the actor reads the shop's seller id from the page and scrapes the shop's full seller ad list (same output as a seller listing); a shop page without a seller id is skipped with a warning |
| Legacy search form | `/s-suchanfrage.html?keywords=fahrrad`: rewritten to `/s-fahrrad/k0` |
| Mobile host | `m.kleinanzeigen.de/...`: rewritten to the www host |
| ebay-kleinanzeigen.de | Shut down by the site (HTTP 410); such a URL is skipped with a warning in the log |

The `urls` field is not empty by default: in `url` mode, leaving it untouched scrapes one example of every supported shape above (capped by Max listings, split evenly across the URLs). Clear it and paste your own URLs for a real run. A seite:N segment counts as a page number as the first path segment or directly before a category id (`/seite:2/c217`); in a keyword search such as `/s-fahrrad/seite:2/k0` the site itself reads it as the keyword "seite:2", so it is left alone.

Pasted searches share one list of collected ads. If a later result page of one search is made up entirely of ads that another pasted search already collected in the same run, that search treats the page as its end and stops there, skipping all of its remaining pages. With Incremental mode and `emitExpired` on, the ads on those skipped pages can then be reported as EXPIRED (and billed) even though they are still live. If your pasted searches overlap heavily, run them one at a time (or give each its own schedule) instead of together.

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/kleinanzeigen-de-scraper").call(run_input={"mode": "search", "keyword": "fahrrad", "maxListings": 50})
for ad in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(ad["title"], ad["price"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/kleinanzeigen-de-scraper').call({ mode: 'search', keyword: 'fahrrad', maxListings: 50 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Resuming a full-catalogue walk

For an unbounded pull (`maxPages: 0`, `maxListings: 0`), two things protect a long run:

- **`resumeFromRunId`** lets you continue in a brand-new run: paste the id of a previous run (or its dataset), and this run skips every `adId` already saved there, appending only new ads. The resumed walk keeps going through pages made up of already-saved ads and only stops at the real end of the search, a page that repeats ads this run already saw, or your page/listing limits.
- A **checkpoint** is saved automatically as the run progresses. If the run is interrupted by an Apify platform migration or you click Resurrect on a failed run, it picks back up from where it left off in the same run, without re-saving (or re-charging for) ads it already collected. No input is needed for this; it's automatic.

If Incremental mode is also on, that checkpoint saves the collected-ad list and the run's counters, but not this run's in-progress change-tracking. Rows already written to the dataset stay there, and ads are not falsely marked EXPIRED, but the ads that were tagged NEW, UPDATED or REAPPEARED before the interruption are not remembered as such. The next scheduled run reports those same ads with the same tag once more (and bills those rows again); tracking for them is accurate again from the run after that. The interrupted run may also skip its EXPIRED check; any ads that really disappeared are then reported on the next complete run instead.

#### Tracking a recurring search (incremental mode)

Turn on **`incrementalMode`** to monitor the same search on a schedule instead of re-scraping everything each time. The actor remembers what it saw last time (keyed by `stateKey`, or an auto-derived hash of your search/filter settings when left empty) and tags every ad in the dataset with a `changeType`:

- **`NEW`**: first time this ad has been seen for this search.
- **`UPDATED`**: the ad changed since last time; `changedFields` lists exactly which fields.
- **`UNCHANGED`**: nothing changed. Suppressed by default (not pushed, not billed); turn on `emitUnchanged` to include these rows anyway.
- **`REAPPEARED`**: an ad that had gone `EXPIRED` is back.
- **`EXPIRED`**: an ad tracked previously is no longer found. Only emitted when `emitExpired` is on AND the run reached the real end of every tracked search (no `resumeFromRunId`, no pasted URL starting at a page after the first, no `maxListings`/`maxPages` cap cutting it short); a run cut short by a cap never marks anything expired, since it can't tell "gone" apart from "not reached yet". The one exception is several overlapping pasted URLs, described under How to Use above, where a search can stop early without the run noticing.

Every row also carries `changedFields`, `firstSeenAt`, and `lastSeenAt`. `incrementalMode` is independent of `resumeFromRunId`: the former tracks the same search across many separate scheduled runs, the latter continues one specific interrupted run.

**`ignoreFieldsForChanges`** lists fields that must not count as a change (for example `description` or `imageUrls`; a bare name matches at any depth, a dotted path such as `seller.name` targets one nested field). Ignored fields stay in the output but never make an ad UPDATED or appear in `changedFields`. Always ignored: `scrapedAt`, `searchUrl`, `viewCount`, `sellerOtherAdsCount`, the relative `postedDateText`, `isTopAd`, image resize parameters, and the order of `tags`. Changing this list takes effect immediately without a one-time wave of UPDATED rows.

A few behaviours worth knowing before you rely on this for billing-sensitive schedules:

- **Two ad-page layouts, one ad.** The site serves one of two layouts per page load, and the newer one leaves out the seller block, the amenity tags and the breadcrumb path (and spells the city differently). When a load comes back in the newer layout, or the detail page fails and only the search-result fields are available, those missing fields are left out of the comparison and the previously stored values are kept, so the same unchanged ad is not tagged UPDATED (and not billed) just because the layout flipped. The cost: a real change in exactly those fields is only noticed on a load that shows them, and the first full-layout load after a reduced one is not flagged for them either. Fields both layouts show (title, price, description, location, attributes) always count.

- **EXPIRED rows are not capped by `maxListings`.** That cap counts the ads checked during the walk; once a run reaches a complete scan and `emitExpired` fires, every ad that disappeared since last time is pushed in one batch regardless of `maxListings`. If your tracked baseline is large and many ads vanish at once, a single run's item count (and bill) can exceed `maxListings`.

- **Combining `resumeFromRunId` with `incrementalMode` on the very first run for a search** seeds every ad from the resumed dataset into the baseline without a real stored snapshot. The next ordinary scheduled run then classifies each of them it finds as UPDATED, with every field listed in `changedFields` (and bills those rows), even though nothing on the ad actually changed, simply because there was nothing genuine to compare against yet. This happens once per ad and then self-corrects.

- **A manually-set `stateKey` is taken exactly as given**, unlike the auto-derived key, which always changes when your filters change. Reusing the same manual `stateKey` for two differently configured recurring searches (different keyword, category, or a different `fetchDetails` setting) merges them into one shared baseline. Real, unchanged ads can then show up as UPDATED purely because the two setups return a different field shape, and with `emitExpired` on, ads that only one of the searches covers can be reported as EXPIRED by the other even though they are still live. Give each independent recurring search its own `stateKey`, or leave it blank.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape. The Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` to build URLs from the filters below, `url` to paste your own search URLs. |
| `keyword` | string | (empty; prefilled `fahrrad`) | Free-text query. Leave empty to browse a whole category. |
| `categoryId` | string | (empty) | Category name (e.g. `Fahrräder`, matched against the site's own category tree; an unknown name stops the run with an error) or numeric id such as `217`. |
| `location` | string | (empty) | City name or German postal code. Resolved automatically to the matching location id. |
| `radiusKm` | integer | `0` | Search radius in km around the location. Ignored when no location is set. |
| `minPrice` | integer | (empty) | Lowest price to include. |
| `maxPrice` | integer | (empty) | Highest price to include. |
| `offerType` | string | `any` | `angebote` for offered ads, `gesuche` for wanted ads, `any` for both. |
| `sellerType` | string | `any` | `privat` for private sellers, `gewerblich` for commercial sellers/shops, `any` for both. |
| `zipCodes` | array | none | Several postal codes or cities in one run (one search each, radius applies to each, duplicates returned once). Replaces `location` when set. |
| `minRooms` | number | none | Minimum number of rooms. Real-estate room and living-area filters. Need `categoryId` 203 (Mietwohnungen), 196 (Eigentumswohnungen), 205 (Häuser zur Miete) or 208 (Häuser zum Kauf); another category stops the run with a clear message. Search mode only. |
| `maxRooms` | number | none | Maximum number of rooms. |
| `minArea` | number | none | Minimum living area in square metres. |
| `maxArea` | number | none | Maximum living area in square metres. |
| `sortBy` | string | `relevance` | `relevance`, `date` (newest), `price-asc`, `price-desc` (highest first), `distance`. Promoted ads can appear first regardless of sort. |
| `urls` | array of strings | one example per supported shape (also prefilled) | Kleinanzeigen.de URLs to scrape (used only when `mode` is `url`): search, category, single-ad and seller-listing URLs. |
| `maxPages` | integer | `0` | Result pages to walk per search/URL (27 ads per page). `0` means unlimited: the walk keeps going until Kleinanzeigen.de has no next page for that search (Kleinanzeigen.de may itself cap a search around 50 pages, which is fine, the walk simply stops there). |
| `maxListings` | integer | `20` | Stop after this many ads across all searches. With several URLs the cap is split evenly between them. In Incremental mode, unchanged ads that are not returned still count toward it. `0` means unlimited (still bounded by `maxPages`). |
| `fetchDetails` | boolean | `true` | Open each ad's detail page for the full description, all photos, attributes, seller info and imprint. Turn off for a faster, cheaper run. |
| `resumeFromRunId` | string | (empty) | A previous run id or dataset id to continue ONE interrupted full-catalogue walk across separate runs. Ads already collected there (matched by `adId`) are skipped, so this run only saves the new ones. |
| `incrementalMode` | boolean | `false` | Track a recurring search over time: remembers the ads from this actor's own previous run of the SAME search and tags every ad with a `changeType` (`NEW`/`UPDATED`/`UNCHANGED`/`REAPPEARED`/`EXPIRED`). Pair with the Apify Scheduler. Distinct from `resumeFromRunId` above. |
| `ignoreFieldsForChanges` | array | (empty) | Incremental mode only. Field names (or dotted paths) that never count as a change. |
| `stateKey` | string | (empty) | Incremental mode only. A name for this recurring search's saved state. Leave empty to auto-derive one from your search/filter settings; two different filter setups never share a baseline this way. |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Also push ads with no detected change (`changeType=UNCHANGED`). Bills one extra row per unchanged ad every run. |
| `emitExpired` | boolean | `false` | Incremental mode only. Also push ads tracked previously but no longer found, once, tagged `changeType=EXPIRED`. Only fires on a run that reaches the real end of every tracked search (no `resumeFromRunId`, no `maxListings`/`maxPages` cap hit). Bills one extra row per expired ad, and this batch is not itself limited by `maxListings`. |
| `proxy` | object | Residential | Apify Proxy. Residential is the default and rotates across EU countries automatically unless you pin one; Datacenter can be picked instead in the same field. |
| `mcpConnectors` | array | (none) | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which records are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. |

### Output Example

> Sample shape, values are illustrative placeholders, not from a live ad.

```json
{
  "adId": "3400000001",
  "url": "https://www.kleinanzeigen.de/s-anzeige/sample-listing-title/3400000001-217-0001",
  "title": "Sample Listing Title",
  "descriptionShort": "First sentence of the ad as shown in the result list.",
  "descriptionFull": "Full ad description text appears here when fetchDetails is true.",
  "price": "1.500 € VB",
  "priceAmount": 1500,
  "priceType": "NEGOTIABLE",
  "currency": "EUR",
  "locationText": "10115 Berlin - Mitte",
  "postalCode": "10115",
  "regionName": "Berlin",
  "cityName": "Mitte",
  "latitude": 52.52,
  "longitude": 13.405,
  "categoryId": "217",
  "categoryName": "Fahrräder & Zubehör",
  "categoryPath": "Auto, Rad & Boot > Fahrräder & Zubehör",
  "categoryParentId": "210",
  "locationId": "3331",
  "postedDate": "2026-01-01",
  "postedDateText": "Heute, 09:00",
  "viewCount": null,
  "imageCount": 1,
  "thumbnailUrl": "https://img.kleinanzeigen.de/api/v1/prod-ads/images/00/00000000-0000-0000-0000-000000000000?rule=$_59.AUTO",
  "imageUrls": ["https://img.kleinanzeigen.de/api/v1/prod-ads/images/00/00000000-0000-0000-0000-000000000000?rule=$_59.AUTO"],
  "attributes": { "Art": "Herren", "Typ": "Elektrofahrräder", "Zustand": "Sehr Gut" },
  "attributesRaw": [{ "name": "Art", "value": "Herren" }, { "name": "Typ", "value": "Elektrofahrräder" }, { "name": "Zustand", "value": "Sehr Gut" }],
  "isPro": false,
  "isShop": false,
  "isTopAd": false,
  "isSold": false,
  "adType": "OFFERED",
  "tags": ["Versand möglich"],
  "sellerId": "100000000",
  "sellerName": "Sample Seller",
  "sellerType": "PRIVATE",
  "sellerUrl": "https://www.kleinanzeigen.de/s-bestandsliste.html?userId=100000000",
  "sellerActiveSince": "2026-01-01",
  "sellerOtherAdsCount": 4,
  "imprint": null,
  "searchUrl": "https://www.kleinanzeigen.de/s-fahrrad/k0",
  "scrapedAt": "2026-01-01T00:00:00.000Z"
}
```

**Dataset views.** The dataset has four views on the Output tab: Listings overview, Real estate, Ad detail and Changes (change tracking columns for Incremental mode).

### Plan Requirement

Apify Residential proxy is the default and recommended connection for Kleinanzeigen.de; it rotates automatically across EU countries unless you pin one. Datacenter can be picked instead in the **Proxy configuration** field; in our tests Kleinanzeigen.de served most Datacenter connections, and the actor automatically retries on a fresh connection when one is refused. If the selected connection is unavailable to your account, the run continues on a backup connection and says so in the log. On a plan without residential access the log warns "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." and a served page is logged as "Served over the backup connection (the default connection was unavailable)." If no results page can be loaded at all the run ends FAILED, never as an empty success: with a refused connection the message is "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required", on a free plan "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Kleinanzeigen pages", and otherwise "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search." A genuine search with no ads still ends successfully with the warning "Run finished with 0 listings." Detail enrichment (`fetchDetails`) adds one request per ad; use **Max listings total**, **Max pages per search**, or turn detail enrichment off to control a run's size and cost.

### FAQ

#### How much does it cost?

You pay per ad returned. Every row saved to the dataset is billed as one dataset item, and that includes UNCHANGED rows (only saved when you turn on `emitUnchanged`) and EXPIRED rows (only saved when you turn on `emitExpired`); both are off by default, so they are never billed unless you switch them on. The detail enrichment event is charged one unit per saved row that carries a fetched detail page, right after that row is saved to the dataset. That includes UNCHANGED rows when `emitUnchanged` is on, and pasted single-ad URLs only when `fetchDetails` is on. It is never charged for suppressed UNCHANGED rows, EXPIRED rows or failed rows. The **Pricing** tab shows the current rates. Use **Max listings total** and **Max pages per search** to cap the cost of any run.

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

This actor collects only publicly available classifieds data. You are responsible for how you use it: follow Kleinanzeigen.de's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Personal data such as a seller's name or a commercial imprint is publicly posted by the seller themselves; handle it in line with applicable data protection law.

#### Can I get only new or changed ads on a schedule?

Yes. Schedule the actor from the **Schedules** tab with the same search or URLs and turn on **Incremental mode**. Each run then tags every ad NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED, and unchanged ads are not billed unless you turn `emitUnchanged` on.

#### What's the difference between "Resume from a previous run" and "Incremental mode"?

`resumeFromRunId` continues ONE specific interrupted run: paste its run or dataset id and this run only appends ads it hasn't already saved there. `incrementalMode` is for a recurring search you run again and again on a schedule; it remembers its own state between runs and classifies every ad as NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED. They solve different problems and can be combined for the first run of a new search, though the scheduled run right after that combined run reports the ads carried over from the resumed dataset as UPDATED once before tracking settles down.

#### Why did my incremental run return more rows, and cost more, than Max listings?

`maxListings` bounds how many ads a run checks while walking result pages (unchanged ads that are skipped still count toward it). It does not bound the EXPIRED batch: once a run completes a full scan with `emitExpired` on, every previously tracked ad that has disappeared is pushed in that same run, on top of whatever the walk itself collected. If your tracked baseline is large, turn `emitExpired` off, or expect that batch to add to the run's total.

#### Why did my run return 0 listings instead of failing?

An empty search is a legitimate outcome, not an error, so the run finishes successfully with a status message when the search page loaded but held no ads (usually filters that are too narrow). The run fails when no results page could be loaded at all (a connection problem, not an empty search), and in specific cases: an unreadable `resumeFromRunId`, or turning on `incrementalMode` together with `resumeFromRunId` when that search already has saved incremental state.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more classifieds data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🚗 <a href="https://apify.com/abotapi/mobile-de-scraper"><b>mobile.de Scraper</b></a><br>Scrape mobile.de vehicle listings with 30+ fields including price, mileage, registration...</td><td>👗 <a href="https://apify.com/abotapi/otto-de-scraper"><b>OTTO.de Scraper</b></a><br>Scrape OTTO.de products by search or URL. Extract prices, discounts, variants...</td></tr>
<tr><td>🏷️ <a href="https://apify.com/abotapi/marktplaats-nl-scraper"><b>Marktplaats.nl Scraper</b></a><br>Scrape Marktplaats.nl listings by keyword or URL. Extract title, price, condition...</td><td>🏷️ <a href="https://apify.com/abotapi/tutti-ch-scraper"><b>Tutti.ch Scraper</b></a><br>Scrape listings from tutti.ch across all 23 categories, including vehicles, property...</td></tr>
<tr><td>🚗 <a href="https://apify.com/abotapi/craigslist-classifieds-scraper"><b>Craigslist Scraper</b></a><br>Scrape Craigslist postings in any of 700+ cities worldwide: price, title, posting text...</td><td>🏷️ <a href="https://apify.com/abotapi/finn-no-jobs-scraper"><b>FINN.no Jobs Scraper</b></a><br>Scrape active FINN.no job listings by search filters or URL. Extract 30+ fields including...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/kleinanzeigen-de-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

Pick how you want to start: 'search' builds search URLs from the filter fields below, 'url' takes ready-made Kleinanzeigen.de search URLs you paste yourself.

## `keyword` (type: `string`):

Free-text query, e.g. 'fahrrad', 'iphone 14', 'sofa'. Leave empty to browse a whole category.

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

Category name or numeric id. A name such as 'Fahrräder' or 'Elektronik' is matched against the site's own category tree (an unknown name stops the run with a clear error); a numeric id such as '217' (Fahrräder & Zubehör) or '161' (Elektronik) is used as is. Find ids in any category URL: .../c217. Leave empty to search all categories.

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

City name or German postal code, e.g. 'Berlin', 'München', '10115'. Resolved automatically to the matching location id. Leave empty to search all of Germany.

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

Search radius in kilometres around the chosen location. Ignored when no location is set. 0 = exact location only.

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

Lowest price to include. Leave empty for no lower bound.

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

Highest price to include. Leave empty for no upper bound.

## `offerType` (type: `string`):

Limit to ads offering something ('angebote') or wanted ads ('gesuche'). 'any' includes both.

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

Limit to private sellers ('privat') or commercial sellers/shops ('gewerblich'). 'any' includes both.

## `zipCodes` (type: `array`):

Search several places in one run, one search per entry (postal codes such as '10115' or city names). When set it replaces the single Location field; the radius applies to each entry. Ads found by more than one entry are returned once.

## `minRooms` (type: `number`):

Fewest rooms. Works with categoryId 203 (Mietwohnungen), 196 (Eigentumswohnungen), 205 (Häuser zur Miete) or 208 (Häuser zum Kauf); any other category stops the run with a clear message. Search mode only; in URL mode paste a URL that already carries the filter.

## `maxRooms` (type: `number`):

Most rooms. Same categories as Min rooms.

## `minArea` (type: `integer`):

Smallest living area in m². Same categories as Min rooms.

## `maxArea` (type: `integer`):

Largest living area in m². Same categories as Min rooms.

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

Result ordering. Note: Kleinanzeigen shows promoted ads first regardless of sort.

## `urls` (type: `array`):

One or more Kleinanzeigen.de URLs to scrape (used only when mode = url): search and category pages (keyword k0, category cNNN, keyword within category k0cNNN, location k0l<id>r<km>, price/offer/seller filters, sortingField sort, s-seite:N start page, also /s-<category>/seite:N/cNNN), single ad pages (/s-anzeige/...) which return exactly that ad, seller listing pages (/s-bestandsliste.html?userId=...), commercial shop pages (/pro/<name>, scraped through the shop's seller ad list), the legacy /s-suchanfrage.html?keywords= form and the m. mobile host. ebay-kleinanzeigen.de no longer exists and is skipped with a warning. The default list holds one example per shape.

## `maxPages` (type: `integer`):

How many result pages to walk per search/URL (27 ads per page). 0 = unlimited (walk until the last page Kleinanzeigen serves for that search; Kleinanzeigen may itself cap a search around 50 pages, which is fine, the walk simply stops there).

## `maxListings` (type: `integer`):

Stop after this many ads across all searches. 0 = unlimited (still bounded by maxPages).

## `fetchDetails` (type: `boolean`):

Open every ad's detail page to pull the full description, all photos, attributes, seller info, view count, category path, and commercial-seller imprint. Turn off for a faster, cheaper run with just the search-result fields (~14 fields).

## `proxy` (type: `object`):

Apify Residential proxy is strongly recommended and is the default. When no country is pinned (the default), each new connection lands on a different EU country (DE, AT, NL, BE, FR, IT, ES, PL, CZ, SE, DK, CH) so a single throttled exit cannot stall the whole run. Pin a country (DE recommended for kleinanzeigen.de) to force all connections through one location.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue ONE interrupted full-catalogue walk across separate runs. Ads already collected there (matched by ad id) are skipped, so this run only saves the new ones. Leave empty for a normal fresh run. Distinct from 'Incremental mode' below, which tracks the SAME recurring search across many scheduled runs by itself.

## `incrementalMode` (type: `boolean`):

When ON, this actor remembers the ads from its own previous run of the SAME search (see 'State key' below) and tags every ad with a changeType: NEW, UPDATED, UNCHANGED, REAPPEARED, or EXPIRED. Use with the Apify Scheduler to monitor a search over time instead of re-scraping everything from scratch. Distinct from 'Resume from a previous run' above, which continues one specific interrupted run rather than tracking a recurring search. Default OFF — existing users/schedules are unaffected.

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

A name for this recurring search's saved state, e.g. 'berlin-fahrrad-watch'. Leave empty to auto-derive one from your search/filter settings (mode, keyword, category, location, radius, price, offer/seller type, sort, URLs, fetch details) — two different filter setups never share a baseline. Set this explicitly if you want a stable name across input tweaks that shouldn't reset tracking, or to run several independent monitors from the same actor.

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

When ON, ads with no detected change are still pushed to the dataset (tagged changeType=UNCHANGED) instead of being skipped. This returns — and BILLS — one extra row per unchanged ad every run. Default OFF: only NEW/UPDATED/REAPPEARED/EXPIRED ads are pushed.

## `emitExpired` (type: `boolean`):

When ON, ads tracked in a previous run but no longer found after a complete, uncapped scan are pushed once more tagged changeType=EXPIRED. This returns — and BILLS — one extra row per expired ad. Only fires on a run that reaches the real end of every tracked search (no resumeFromRunId, no maxListings/maxPages cap hit) — a partial run never marks anything expired. Default OFF.

## `ignoreFieldsForChanges` (type: `array`):

Field names that must NOT count as a change in incremental mode, e.g. description or imageUrls. A name matches at any depth; use a dotted path such as seller.name to target one nested field. Ignored fields stay in the output; they just never turn an ad into UPDATED or appear in changedFields. Already ignored by default: scrapedAt, searchUrl, viewCount, sellerOtherAdsCount, postedDateText, isTopAd, image resize parameters and the order of tags.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "keyword": "fahrrad",
  "radiusKm": 0,
  "offerType": "any",
  "sellerType": "any",
  "sortBy": "relevance",
  "urls": [
    "https://www.kleinanzeigen.de/s-fahrrad/k0",
    "https://www.kleinanzeigen.de/s-fahrraeder/c217",
    "https://www.kleinanzeigen.de/s-auto-rad-boot/c161",
    "https://www.kleinanzeigen.de/s-fahrraeder/mountainbike/k0c217",
    "https://www.kleinanzeigen.de/s-seite:2/fahrrad/k0",
    "https://www.kleinanzeigen.de/s-fahrraeder/seite:2/c217",
    "https://www.kleinanzeigen.de/s-fahrrad/k0?sortingField=SORTING_DATE",
    "https://www.kleinanzeigen.de/s-fahrrad/k0?sortingField=PRICE_AMOUNT",
    "https://www.kleinanzeigen.de/s-rennrad/k0?sortingField=SORTING_DATE&pageNum=2",
    "https://www.kleinanzeigen.de/s-berlin/fahrrad/k0l3331r20",
    "https://www.kleinanzeigen.de/s-85737/fahrrad/k0l7554",
    "https://www.kleinanzeigen.de/s-berlin/c217",
    "https://www.kleinanzeigen.de/s-berlin/k0l3331",
    "https://www.kleinanzeigen.de/s-fahrraeder/c217?sortingField=SORTING_DATE",
    "https://www.kleinanzeigen.de/s-anzeige:angebote/anbieter:privat/preis:100:300/fahrrad/k0",
    "https://www.kleinanzeigen.de/s-anzeige/-jubilaeumsangebot-bullitt-e6100-kiddo-ebullitt-e6100-lizzard-king-ebullitt-lastenrad-cargobike-shimano-6100-kindertransport-504wh-larry-vs-harry/2807683512-217-6860",
    "https://www.kleinanzeigen.de/s-anzeige/2807683512",
    "https://www.kleinanzeigen.de/s-bestandsliste.html?userId=1",
    "https://www.kleinanzeigen.de/pro/ClassyCycleClub",
    "https://www.kleinanzeigen.de/s-suchanfrage.html?keywords=fahrrad",
    "https://m.kleinanzeigen.de/s-fahrrad/k0"
  ],
  "maxPages": 0,
  "maxListings": 20,
  "fetchDetails": true,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `realEstate` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `changes` (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 = {
    "mode": "search",
    "keyword": "fahrrad",
    "urls": [
        "https://www.kleinanzeigen.de/s-fahrrad/k0",
        "https://www.kleinanzeigen.de/s-fahrraeder/c217",
        "https://www.kleinanzeigen.de/s-auto-rad-boot/c161",
        "https://www.kleinanzeigen.de/s-fahrraeder/mountainbike/k0c217",
        "https://www.kleinanzeigen.de/s-seite:2/fahrrad/k0",
        "https://www.kleinanzeigen.de/s-fahrraeder/seite:2/c217",
        "https://www.kleinanzeigen.de/s-fahrrad/k0?sortingField=SORTING_DATE",
        "https://www.kleinanzeigen.de/s-fahrrad/k0?sortingField=PRICE_AMOUNT",
        "https://www.kleinanzeigen.de/s-rennrad/k0?sortingField=SORTING_DATE&pageNum=2",
        "https://www.kleinanzeigen.de/s-berlin/fahrrad/k0l3331r20",
        "https://www.kleinanzeigen.de/s-85737/fahrrad/k0l7554",
        "https://www.kleinanzeigen.de/s-berlin/c217",
        "https://www.kleinanzeigen.de/s-berlin/k0l3331",
        "https://www.kleinanzeigen.de/s-fahrraeder/c217?sortingField=SORTING_DATE",
        "https://www.kleinanzeigen.de/s-anzeige:angebote/anbieter:privat/preis:100:300/fahrrad/k0",
        "https://www.kleinanzeigen.de/s-anzeige/-jubilaeumsangebot-bullitt-e6100-kiddo-ebullitt-e6100-lizzard-king-ebullitt-lastenrad-cargobike-shimano-6100-kindertransport-504wh-larry-vs-harry/2807683512-217-6860",
        "https://www.kleinanzeigen.de/s-anzeige/2807683512",
        "https://www.kleinanzeigen.de/s-bestandsliste.html?userId=1",
        "https://www.kleinanzeigen.de/pro/ClassyCycleClub",
        "https://www.kleinanzeigen.de/s-suchanfrage.html?keywords=fahrrad",
        "https://m.kleinanzeigen.de/s-fahrrad/k0"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    },
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/kleinanzeigen-de-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 = {
    "mode": "search",
    "keyword": "fahrrad",
    "urls": [
        "https://www.kleinanzeigen.de/s-fahrrad/k0",
        "https://www.kleinanzeigen.de/s-fahrraeder/c217",
        "https://www.kleinanzeigen.de/s-auto-rad-boot/c161",
        "https://www.kleinanzeigen.de/s-fahrraeder/mountainbike/k0c217",
        "https://www.kleinanzeigen.de/s-seite:2/fahrrad/k0",
        "https://www.kleinanzeigen.de/s-fahrraeder/seite:2/c217",
        "https://www.kleinanzeigen.de/s-fahrrad/k0?sortingField=SORTING_DATE",
        "https://www.kleinanzeigen.de/s-fahrrad/k0?sortingField=PRICE_AMOUNT",
        "https://www.kleinanzeigen.de/s-rennrad/k0?sortingField=SORTING_DATE&pageNum=2",
        "https://www.kleinanzeigen.de/s-berlin/fahrrad/k0l3331r20",
        "https://www.kleinanzeigen.de/s-85737/fahrrad/k0l7554",
        "https://www.kleinanzeigen.de/s-berlin/c217",
        "https://www.kleinanzeigen.de/s-berlin/k0l3331",
        "https://www.kleinanzeigen.de/s-fahrraeder/c217?sortingField=SORTING_DATE",
        "https://www.kleinanzeigen.de/s-anzeige:angebote/anbieter:privat/preis:100:300/fahrrad/k0",
        "https://www.kleinanzeigen.de/s-anzeige/-jubilaeumsangebot-bullitt-e6100-kiddo-ebullitt-e6100-lizzard-king-ebullitt-lastenrad-cargobike-shimano-6100-kindertransport-504wh-larry-vs-harry/2807683512-217-6860",
        "https://www.kleinanzeigen.de/s-anzeige/2807683512",
        "https://www.kleinanzeigen.de/s-bestandsliste.html?userId=1",
        "https://www.kleinanzeigen.de/pro/ClassyCycleClub",
        "https://www.kleinanzeigen.de/s-suchanfrage.html?keywords=fahrrad",
        "https://m.kleinanzeigen.de/s-fahrrad/k0",
    ],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/kleinanzeigen-de-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 '{
  "mode": "search",
  "keyword": "fahrrad",
  "urls": [
    "https://www.kleinanzeigen.de/s-fahrrad/k0",
    "https://www.kleinanzeigen.de/s-fahrraeder/c217",
    "https://www.kleinanzeigen.de/s-auto-rad-boot/c161",
    "https://www.kleinanzeigen.de/s-fahrraeder/mountainbike/k0c217",
    "https://www.kleinanzeigen.de/s-seite:2/fahrrad/k0",
    "https://www.kleinanzeigen.de/s-fahrraeder/seite:2/c217",
    "https://www.kleinanzeigen.de/s-fahrrad/k0?sortingField=SORTING_DATE",
    "https://www.kleinanzeigen.de/s-fahrrad/k0?sortingField=PRICE_AMOUNT",
    "https://www.kleinanzeigen.de/s-rennrad/k0?sortingField=SORTING_DATE&pageNum=2",
    "https://www.kleinanzeigen.de/s-berlin/fahrrad/k0l3331r20",
    "https://www.kleinanzeigen.de/s-85737/fahrrad/k0l7554",
    "https://www.kleinanzeigen.de/s-berlin/c217",
    "https://www.kleinanzeigen.de/s-berlin/k0l3331",
    "https://www.kleinanzeigen.de/s-fahrraeder/c217?sortingField=SORTING_DATE",
    "https://www.kleinanzeigen.de/s-anzeige:angebote/anbieter:privat/preis:100:300/fahrrad/k0",
    "https://www.kleinanzeigen.de/s-anzeige/-jubilaeumsangebot-bullitt-e6100-kiddo-ebullitt-e6100-lizzard-king-ebullitt-lastenrad-cargobike-shimano-6100-kindertransport-504wh-larry-vs-harry/2807683512-217-6860",
    "https://www.kleinanzeigen.de/s-anzeige/2807683512",
    "https://www.kleinanzeigen.de/s-bestandsliste.html?userId=1",
    "https://www.kleinanzeigen.de/pro/ClassyCycleClub",
    "https://www.kleinanzeigen.de/s-suchanfrage.html?keywords=fahrrad",
    "https://m.kleinanzeigen.de/s-fahrrad/k0"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/kleinanzeigen-de-scraper --silent --output-dataset

```

## MCP server setup

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