# Kleinanzeigen Scraper & API - Listings, Prices & Price Drops (`surefeed/kleinanzeigen-scraper`) Actor

Monitor Kleinanzeigen listings by search, city and price range. Get asking prices, VB, price drops, location and listing links as CSV/JSON. Return only new listings and price changes across runs, or add full descriptions and attributes. Built for resellers and market research.

- **URL**: https://apify.com/surefeed/kleinanzeigen-scraper.md
- **Developed by:** [gianluca de dominicis](https://apify.com/surefeed) (community)
- **Categories:** E-commerce, Lead generation, Agents
- **Features:** Limited permissions
- **Stats:** 1 total users, 1 monthly users, 87.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 1,000 listings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### Kleinanzeigen Scraper & API - Listings, Prices & Price Drops

[Deutsch: Schnellstart](#deutsch-schnellstart) · [English guide](#english-guide)

#### Deutsch: Schnellstart

**Kleinanzeigen finden, Angebotspreise vergleichen und Änderungen verfolgen.** Suche auf Kleinanzeigen.de nach Begriff, Ort und Preisbereich oder füge einen Suchlink ein. Exportiere die Ergebnisse als CSV, Excel oder JSON – zum Beispiel für die Suche nach gebrauchten Smartphones, Fahrrädern oder anderen Waren zum Wiederverkauf.

##### Erster Test: bis zu 50 iPhone-Angebote in Berlin

1. Gib bei **Search terms** `iphone 15` und bei **Location** `Berlin` ein. Setze **Category** auf `Phones & telecom`, **Radius (km)** auf `10` und beide Ergebnislimits (**Max listings per search** und **Max listings for the whole run**) auf `50`.
2. Lasse **Include listing details** zunächst ausgeschaltet (`includeDetails: false`) und lege unter **Max cost per run** ein Ausgabenlimit fest.
3. Klicke **Start** und lade nach dem Lauf die Ergebnisse als CSV, Excel oder JSON herunter. Ein Kleinanzeigen-Login ist nicht erforderlich.

**Kosten dieses Tests:** Werden 50 Anzeigen ohne Details zurückgegeben, beträgt der Preis zum Basistarif etwa **0,03 US-Dollar**. Die Zahl verfügbarer Ergebnisse kann geringer sein. Die Basisrate beträgt **0,60 US-Dollar je 1.000 Ergebniszeilen**; mögliche Tarifrabatte sind hier nicht berücksichtigt.

**Beschreibungen und Attribute benötigt?** Aktiviere **Include listing details**. Der zusätzliche Preis beträgt **2,00 US-Dollar je 1.000 erfolgreich abgerufene Details** und fällt nur bei `detailsStatus: "complete"` an. Bei 50 Ergebniszeilen und 50 erfolgreichen Detail-Abrufen wären es insgesamt etwa **0,13 US-Dollar** zum Basistarif. Einzelne optionale Angaben können auf der Quellseite fehlen.

##### Wiederholt nach neuen Anzeigen und Preisänderungen suchen

Aktiviere `onlyNewListings` und verwende bei späteren Läufen denselben `monitorKey` sowie dieselben Suchfilter. Der erste Lauf eines neuen Monitors liefert passende Anzeigen; spätere Läufe liefern neue Anzeigen und Änderungen gegenüber dem zuletzt beobachteten Preis. Auch Preiserhöhungen werden erfasst.

`previousObservedPrice` ist der zuvor vom Monitor beobachtete Preis. `oldPrice` ist dagegen der auf Kleinanzeigen durchgestrichene Preis. Beide sind Angebotspreise, keine nachgewiesenen Verkaufspreise.

`deduplicateAcrossSearches: true` vermeidet standardmäßig doppelte Anzeigen bei überlappenden Suchen. Mit `false` werden bewusst getrennte, jeweils berechnete Ergebniszeilen je Suche ausgegeben.

**Wichtig:** Zeitpläne, Google Sheets und Benachrichtigungen müssen separat in Apify, n8n, Make oder einem anderen Dienst eingerichtet werden. Preisalarme sind nicht automatisch aktiv. Verfügbare Seiten, Suchfilter sowie Ergebnis- und Kostenlimits beeinflussen die Erfassung; vollständige Marktabdeckung und die Erkennung verkaufter Anzeigen werden nicht zugesichert.

**Neu in Version 0.3:** Kategorien direkt auswählen, Angebote und Gesuche trennen, Direktkauf und Fotos filtern sowie nach Datum, Preis oder Entfernung sortieren. Mit einem globalen Ergebnislimit und getrennten Seiten-/Zeitlimits bleibt auch ein wiederholter Monitor ohne Neuigkeiten begrenzt. `RUN_SUMMARY` zeigt gelesene Seiten, ausgelassene ungültige Karten und den Grund für einen Abbruch der Suche. Preise und bestehende Monitor-Historien bleiben unverändert.

[Anpassbare JSON-Beispiele](#three-ready-to-edit-inputs) · [Monitoring im Detail](#monitoring-and-price-changes) · [Preise und Abrechnung](#how-much-does-it-cost)

#### English guide

**Track the listings and asking prices that matter to your resale business.** Search Kleinanzeigen.de by keyword, location and price range, or paste a search URL. Export structured results to CSV, Excel or JSON, then use **Only new listings and price changes** for repeat checks instead of downloading unchanged ads again.

Start with a lightweight price feed, and request full descriptions and attributes only when you need them. The optional details event is charged only when details extraction succeeds. By default, overlapping searches return a listing once per run.

Use it for electronics sourcing, local bicycle searches and asking-price research. Notifications and spreadsheet updates require an Apify schedule or an external workflow; they are not switched on automatically.

[Three ready-to-edit inputs](#three-ready-to-edit-inputs) · [Monitoring and price changes](#monitoring-and-price-changes) · [Deutsch: Schnellstart](#deutsch-schnellstart)

> ⭐ **Found this Actor useful?** [Leave a review](https://apify.com/surefeed/kleinanzeigen-scraper/reviews) — it takes 30 seconds and helps other people find it.
>
> 🛠️ **Something not working?** [Open an issue](https://apify.com/surefeed/kleinanzeigen-scraper/issues) with the run link, expected behavior and observed result. We review reported issues; resolution time depends on the cause.

**What you get for each listing**

- 💶 **Price**, **VB / negotiable**, **price drop** (the crossed-out old price), free listings ("Zu verschenken")
- 📍 Postcode and district, **posting date and time** (German time), shipping possible, PRO seller and shop name, photo count
- 📝 With details: **full description**, **attributes** (brand, model, condition, size…), category path, all photos, shipping options ("+ Versand ab 2,99 €"), **private or commercial seller** and "active since"
- 🔔 **Only new listings and price changes**: compare with the last observed price, including both decreases and increases
- ✅ **Explicit detail status**: distinguish details that were not requested, obtained successfully or could not be retrieved

**Quick start:** the prefilled form requests up to **20** of the newest "iphone" listings in Germany, without details (about **$0.012** at the base rate if 20 listings are returned). It also sets a three-page scan cap and a 120-second soft work limit. Then choose your words, category and location, or paste a search link. No Kleinanzeigen login is needed. Set **Max cost per run** before starting; listing details are a separate, optional charge. Existing API calls that omit the new controls keep the prior defaults: 100 results per search, with the new global/page/time caps disabled.

Real results — bikes in Berlin (+10 km), 100–300 €, October 2026, as shown on kleinanzeigen.de:

| Listing | Price | Old price | Postcode | Posted | Shipping | Condition |
|---|---|---|---|---|---|---|
| Author Profile Mountainbike silber 26 Zoll | 125 € | 170 € | 10711 Charlottenburg | Oct 10 | + Versand ab 0,99 € | Sehr Gut |
| Rennrad Stadler One Series 1.2 | 220 € VB | — | 12043 Neukölln | Oct 11, 02:34 | Nur Abholung | In Ordnung |
| Riverside 500 C6 L Fahrrad / Hybrid Bike | 250 € | — | 10115 Mitte | Oct 11, 03:14 | Nur Abholung | Sehr Gut |

#### Why this Kleinanzeigen scraper?

| Data collection challenge | This Actor |
|---|---|
| Unverified data | **Checked against the live site: 200 of 200 fields identical** on 20 listings (title, price, VB, postcode, district, shipping, seller type, active since, category, description) |
| Stop at 1,250 listings: Kleinanzeigen shows at most 50 pages per search | **Expanded coverage for priced listings**: big searches are split into price bands and merged without duplicates — bikes in Berlin (+10 km): **23,644 of the 24,251** listings the site reported (97.5%; listings without a price can't be split by price) |
| Different listing-page layouts | Extraction supports the classic and newer layouts represented in the saved test pages; future source changes can still require an update |
| Only the current price | Source-reported crossed-out prices **and**, in monitoring mode, comparison with your own last observed price |
| Re-download everything | **Only new listings and price changes** relative to the monitor's last observation |
| The same ad matches several searches | **Deduplication across searches** is enabled by default; disable it explicitly if you need separate query rows |
| Seller names and phone numbers of private people | **Dedicated seller identity fields omitted**: no private seller names, no phone numbers — business names only for commercial sellers |
| Inflate view counters to read them | **Never touches the ad's view counter** (reading it would count a fake visit for the seller) |

Coverage depends on source availability, filters and accessible pages; complete coverage is not guaranteed. Any validation figures below or above describe historical samples, not a guarantee of future accuracy or uptime.

#### Use cases

- **Deal hunters and resellers**: follow new listings and asking-price changes for saved searches, using a schedule you configure.
- **AI agents**: "find a used road bike under 500 € within 20 km of Munich" — through the Apify integration.
- **Price research**: real asking prices by model, condition and region — for cars, electronics, furniture, rentals.
- **Dealers**: see what competitors list, at which prices, and how often they cut them.
- **Market monitoring**: supply and price trends in any category, city by city.

#### How to use

1. Enter **Search terms** (e.g. `iphone 15`, `rennrad`), choose an optional **Category**, **Location** and **Radius (km)** — or paste **Search links** with any category and category-specific filters. Select a category with no words to browse that category.
2. Optional: **Min / Max price**, **Seller**, **Listing type** (offers / wanted), **Buy now**, **Photos only**, and **Sort by**. Nearest-first sorting requires a location and positive radius.
3. Set **Max listings per search** and **Max listings for the whole run**. The first reached limit wins; 0 disables that cap. For regular monitoring, also set **Max search pages per search** and a soft time limit. Turn on **Include listing details** only if you need descriptions and attributes.
4. Click **Start** and download the results as JSON, CSV or Excel, or fetch them via API.

#### New search controls and bounded monitoring

| Input | Purpose | Omitted value for existing API calls |
|---|---|---|
| `category` | Guided shortlist: electronics, phones, desktop PCs, bicycles, home/garden and cars. Other categories remain available through URLs. Applies only to generated keyword/category searches. | `"all"` |
| `offerType` | `"offered"` for offers or `"wanted"` for wanted ads. `"any"` keeps an existing URL filter. | `"any"` |
| `buyNowOnly` / `photosOnly` | Source direct-purchase filter / exclude cards without listing photos before requesting details or charging. | `false` |
| `sortBy` | `"newest"`, `"price_asc"`, `"distance"`, or `"inherit"`. Inherit keeps URL sorting and the existing `sortNewest` fallback. Promoted ads can still affect source ordering. | `"inherit"` |
| `maxTotalListings` | Total saved rows across all searches, after filtering and deduplication. Stops optional detail requests once reached. | `0` (disabled) |
| `maxSearchPagesPerSearch` | Search-page attempts per original search, shared across all its price bands. Bounds work even with zero new monitor outputs. HTTP retries and detail requests are separate. | `0` (disabled) |
| `maxRunSeconds` | Soft run-wide work deadline; a request already in progress may finish later. Use Apify's timeout for a hard deadline. | `0` (disabled) |

Changing an explicit filter changes the monitor's search identity. Enabling `photosOnly` creates a separate baseline; turning it off restores the prior unfiltered identity. New neutral controls and new result/page/time caps do not reset existing histories.

**Check `RUN_SUMMARY`, not just the row count.** It includes per-search pages attempted, observed and saved listings, skipped invalid cards, `stopReason`, `truncated`, and global limit counters. A successful run with zero updates means no updates were returned **from the pages actually checked**, not that the whole market is unchanged. A partially invalid page can return its valid cards, with a warning and no charge for skipped cards. A page with no valid recognizable results and no genuine empty-search signal still fails visibly. If Kleinanzeigen refuses or throttles a request, the run switches automatically to German residential proxies (rotating IPs) and then to datacenter proxies; if one search still cannot be loaded, the listings already saved are kept, the run finishes with a note and that search is marked `error` in `RUN_SUMMARY`. An input the site cannot use (for example an unknown location) ends the run with an "Input problem" status message, no results and no charge.

Category-specific shipping attributes in pasted URLs (for example `+fahrraeder.versand_s:ja` for bicycles) are preserved. There is no universal shipping checkbox: the source uses different attributes for different categories.

#### Three ready-to-edit inputs

Paste an example into the JSON input editor, adjust its filters and set a spending limit. These are starting configurations, not promises of a particular number of available ads. `maxListingsPerSearch` applies to each search; overlapping results are deduplicated by default.

##### 1. iPhone listings in Berlin: lightweight monitoring

The first run of this monitor returns matching listings within your limits. Later runs return new ads and observed price changes. Keep the same `monitorKey` and search configuration for subsequent runs.

```json
{
    "searchTerms": ["iphone 15"],
    "location": "Berlin",
    "radiusKm": 10,
    "minPrice": 200,
    "maxPrice": 600,
    "sellerType": "private",
    "sortNewest": true,
    "maxListingsPerSearch": 50,
    "includeDetails": false,
    "onlyNewListings": true,
    "monitorKey": "berlin-iphone-15",
    "deduplicateAcrossSearches": true
}
```

At the base rate, 50 returned listings cost about $0.03. This example does not request the details add-on.

##### 2. Road bikes in Munich: condition and attributes

Use the details option when descriptions and category-specific attributes help you assess a listing. The example requests a snapshot; turn on `onlyNewListings` and set a stable `monitorKey` if you want to repeat it as a monitor.

```json
{
    "searchTerms": ["rennrad"],
    "location": "München",
    "radiusKm": 20,
    "minPrice": 200,
    "maxPrice": 800,
    "sellerType": "private",
    "sortNewest": true,
    "maxListingsPerSearch": 100,
    "includeDetails": true,
    "onlyNewListings": false,
    "deduplicateAcrossSearches": true
}
```

If 100 listings are returned and all 100 detail extractions succeed, the base-rate event cost is about $0.26. Optional source fields can still be absent.

##### 3. Model and asking-price research: overlapping searches

Compare public asking prices for a model and a more specific variant across Germany. Deduplication prevents the same ad being returned twice when both searches match it. Inspect condition and attributes before comparing prices: asking prices are not completed-sale prices or a valuation.

```json
{
    "searchTerms": ["thinkpad t14", "thinkpad t14 gen 2"],
    "minPrice": 100,
    "maxPrice": 700,
    "sortNewest": true,
    "maxListingsPerSearch": 50,
    "includeDetails": true,
    "onlyNewListings": false,
    "deduplicateAcrossSearches": true
}
```

#### Monitoring and price changes

- Set `onlyNewListings: true` to remember the latest observed price for each ad in a search. A fresh monitor emits the initial matching listings as `new`; later runs emit new ads and changes to the last observed price. For example, **100 → 80 → 100 → 80** contains three changes, not just one previously unseen price.
- `monitorKey` is optional. Use a stable value to keep independent monitors separate, and reuse that value on subsequent runs. The search itself also forms part of the monitor's identity: changing filters creates a different search baseline. Omit the key to use the default monitor namespace.
- Existing monitors created with the older ID/price history re-establish a current baseline once for known ads. Historical price order is not reconstructed; comparisons resume from that baseline. Version 2 writes separate records and preserves the old history for pinned older builds and rollback.
- Avoid concurrent runs of the same search and `monitorKey`. Use one schedule per monitor or separate keys for independent workflows.
- A monitor can only compare listings it actually reads. Result caps, budget limits, filters and source availability can leave some ads unobserved. The Actor does not promise to detect every market change or infer that an absent ad was sold.

**Two different kinds of previous price:** `oldPrice` and `priceDropped` describe the crossed-out price shown by Kleinanzeigen. `previousObservedPrice` and `priceChange` compare your monitor's observations across runs. These values may differ; neither is a completed-sale price.

**Overlapping searches:** `deduplicateAcrossSearches` defaults to `true`. Each ad is emitted once per run and attributed to the first matching search. Set it to `false` only when you intentionally want separate rows for matching searches; each returned row is then a billable listing event. Deduplication within an individual search remains in place.

#### Output

The output below is a separate historical example, not the response to the input example above. Values, dates and source availability change between runs.

By default, one item per unique listing in a run. This historical example is a €125 bike in Berlin, from a separate Berlin €100–€300 search, not the Munich €200–€800 input above. **Include listing details** was on; description and photo links are shortened. It predates the change/status metadata documented below:

```json
{
    "adId": "3537089111",
    "title": "Author Profile Mountainbike silber 26 Zoll",
    "url": "https://www.kleinanzeigen.de/s-anzeige/author-profile-mountainbike-silber-26-zoll/3537089111-217-3344",
    "price": 125,
    "priceText": "125 €",
    "negotiable": false,
    "oldPrice": 170,
    "priceDropped": true,
    "zip": "10711",
    "location": "Charlottenburg",
    "postedDate": "2026-10-10",
    "postedTime": "21:52",
    "promoted": false,
    "shipping": true,
    "sellerIsPro": false,
    "imageCount": 3,
    "description": "Ich verkaufe mein Mountainbike, da ich auf ein anderes Rad umgestiegen bin. Es…",
    "attributes": {
        "Art": "Herren",
        "Typ": "Mountainbikes",
        "Zustand": "Sehr Gut"
    },
    "category": "Fahrräder & Zubehör",
    "categoryPath": [
        "Auto, Rad & Boot",
        "Fahrräder & Zubehör"
    ],
    "priceType": "FIXED",
    "shippingText": "+ Versand ab 0,99 €",
    "sellerType": "private",
    "sellerActiveSince": "2026-05-25",
    "images": [
        "https://img.kleinanzeigen.de/api/v1/prod-ads/images/cc/cc2c41a3-a6bc-4220-9091-ada969a946cb…",
        "https://img.kleinanzeigen.de/api/v1/prod-ads/images/92/925e4031-85f9-4bba-b334-1377f2daa5ad…"
    ],
    "scrapedAt": "2026-10-11T01:20:39.611Z"
}
```

| Field | Meaning |
|---|---|
| `price` / `priceText` | Asking price in euros as a number, and as shown ("899 € VB"); `null` when only "VB" is given |
| `negotiable` | "VB" (Verhandlungsbasis) |
| `oldPrice` / `priceDropped` | The previous price crossed out on Kleinanzeigen; this is not your monitor's previous observation |
| `postedDate` / `postedTime` | When the listing was posted, German time ("Heute, 02:52" becomes today's date) |
| `promoted` | A promoted listing (shown without a date) |
| `changedSinceLastRun` | With **Only new listings**: whether the price changed from the monitor's last observation |
| `changeType` | `snapshot` outside monitoring; `new`, `price_decreased`, `price_increased` or `price_changed` in monitoring mode |
| `previousObservedPrice` | The monitor's previously observed asking price, where available; separate from the source's `oldPrice` |
| `priceChange` / `priceChangePercent` | Change from the previous observed price where the comparison can be calculated; negative values mean a decrease |
| `detailsStatus` | `not_requested`, `complete` or `failed`. The extra details event is charged only for `complete`; optional fields may still be absent on the source |
| `attributes` | With details: the listing's attribute list, e.g. `{"Marke": "Apple", "Zustand": "Sehr Gut"}` |
| `sellerType` / `sellerActiveSince` | With details: `private` or `commercial`, and since when the account exists |

#### How much does it cost?

Prices and examples below use base rates before plan discounts. The Store’s “from” price may reflect a higher-plan discount; check the Pricing tab for your current plan’s rate.

**Pay per event** — no monthly rental, no start fee, no proxy or compute surcharge:

- **Listing** — each returned listing row: **$0.60 per 1,000**.
- **Listing details** — successfully extracted details for a listing (only with **Include listing details** and `detailsStatus: "complete"`): **$2.00 per 1,000** more.

Examples: 100 listings ≈ $0.06; 1,000 listings with details ≈ $2.60. Set **Max cost per run** for a hard cap on spending.

If detail extraction fails but the base listing is returned, the base listing event still applies and the extra details event does not. Unchanged ads omitted by monitoring are not listing outputs. If you disable cross-search deduplication, the same ad can legitimately appear in multiple paid query rows.

#### Automate & integrate

Scheduling, notifications and external destinations require configuration in Apify or the relevant integration.

For a repeatable workflow, enable **Only new listings and price changes**, keep a stable `monitorKey`, and configure an Apify schedule. Call the Actor from any language through the Apify API, or route its output through n8n or Make.

**Telegram alert for new listings and price drops (n8n, three nodes):**

1. **Schedule Trigger** — for example every 30 minutes.
2. **HTTP Request** — `POST https://api.apify.com/v2/acts/surefeed~kleinanzeigen-scraper/run-sync-get-dataset-items`, authenticated with your Apify token stored as an n8n credential (header `Authorization: Bearer <token>`), JSON body:

```json
{
  "searchTerms": ["rennrad"],
  "location": "München",
  "radiusKm": 20,
  "minPrice": 150,
  "maxPrice": 400,
  "sellerType": "private",
  "onlyNewListings": true,
  "monitorKey": "rennrad-muenchen",
  "maxListingsPerSearch": 25
}
```

3. **Telegram → Send Message** — one message per returned item, for example:

```
{{ $json.changeType === 'price_decreased' ? '📉 Preis gesenkt' : '🆕 Neue Anzeige' }}
{{ $json.title }}
{{ $json.priceText }}{{ $json.previousObservedPrice ? ' (vorher ' + $json.previousObservedPrice + ' €)' : '' }}
{{ $json.zip }} {{ $json.location }}
{{ $json.url }}
```

The first run returns the current matches; later runs return only new listings and price changes, so nothing is sent when nothing changed. Set a `minPrice`: without it, a search like "rennrad" also returns accessories. The request above was run against this Actor; the n8n node setup is a guide to adapt, and a failed run must be handled separately from a quiet one.

**Manual n8n → Google Sheets setup:**

1. Configure your Apify and Google Sheets credentials in n8n; do not put tokens in the Actor input or in a shared workflow file.
2. Use an n8n Schedule Trigger and **Run Actor**, or an Apify schedule with n8n's **On new Apify event** trigger. Choose one scheduling approach to avoid duplicate executions.
3. Continue only after a successful run, then use **Get Dataset Items** with that run's `defaultDatasetId`.
4. For a current-price table, update or insert rows keyed by `adId`. For an observation history, keep each emitted observation with its `scrapedAt`, `changeType` and price fields, and prevent duplicate appends when a workflow retries.
5. Optionally add an email, Slack or other notification step for the events you want. An empty dataset means there are no returned updates; a failed run must be handled separately, not treated as a quiet day.

This is a configuration guide, **not a supplied, pre-tested SureFeed workflow template**. External accounts and services require their own setup and may have separate costs. See the official [Apify n8n guide](https://docs.apify.com/integrations/n8n) or [Make guide](https://docs.apify.com/integrations/make).

#### Use with AI assistants (MCP)

Add this Actor as a tool to Claude, ChatGPT, Cursor or any MCP client through the Apify MCP server (`https://mcp.apify.com`). Your assistant can then search Kleinanzeigen on demand.

#### Agent-ready: autonomous payments (x402 & Skyfire)

Where supported by Apify, agents can use platform payment integrations such as x402 or Skyfire. Availability, authentication and payment setup depend on Apify’s current integration requirements.

#### FAQ

**How do you get past the 50-pages limit?**
Kleinanzeigen shows at most 50 pages (1,250 listings) per search. When a search has more, the Actor repeats it in price bands (halving any band that is still too big) and merges the results without duplicates. Listings without a price (only "VB") can't be found by price, so past the first 1,250 only priced listings are included.

**How accurate is the data?**
We compared 20 listings (bikes in Berlin) with their pages on kleinanzeigen.de in a browser (October 2026): title, price, VB, postcode, district, shipping, seller type, active since, category and description — 200 of 200 values identical — and every attribute (brand, type, condition…) on all 20, on both page layouts Kleinanzeigen serves. Price drops matched the crossed-out price on the site.

**Why is the posting date sometimes one day later than on the listing page?**
For listings posted just after midnight, Kleinanzeigen's listing page shows the previous day (it uses UTC), while the search shows "Heute". We use German local time.

**Can I get the seller's name or phone number?**
No. Private sellers' names and phone numbers are personal data, so they're left out; for commercial sellers you get the business name.

**Is it legal to scrape Kleinanzeigen?**
The Actor only reads public listings, the same data anyone sees on kleinanzeigen.de, without logging in. Check Kleinanzeigen's terms and your local laws (including GDPR) for your use case.

**Something is broken — what now?**
Open an issue on the **Issues** tab with the run link. Include the expected behavior and observed result. Resolution time depends on the cause.

#### More tools from us

- [Eventbrite Scraper & API](https://apify.com/surefeed/eventbrite-scraper) — available events by city or online, with ticket prices, sold out and organizer details.
- [Workday Jobs Scraper & API](https://apify.com/surefeed/workday-jobs-scraper) — available jobs from supported Workday careers sites, with exact posting date, salary range and remote status.
- [Google Hotels Scraper & API](https://apify.com/surefeed/google-hotels-scraper) — hotel prices with taxes, available booking partner offers, and hotels near any address.
- [Google Flights Scraper & API](https://apify.com/surefeed/google-flights-scraper) — fares, stops, layovers and CO2 emissions for any route, flexible dates.
- [Google Trends Scraper & API](https://apify.com/surefeed/google-trends-scraper) — interest over time, by region, related queries and trending searches.
- [Google Play Scraper & API](https://apify.com/surefeed/google-play-scraper) — Android app data, star histograms and reviews.
- [App Store Scraper & API](https://apify.com/surefeed/app-store-scraper) — iPhone app data, star histograms, top charts and reviews in 150+ countries.

#### Legal

**Disclaimer:** This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Kleinanzeigen GmbH or eBay. It only collects publicly available listings. All trademarks belong to their respective owners.

# Actor input Schema

## `searchTerms` (type: `array`):

What to search, one per line (e.g. iphone 15, rennrad, kinderwagen). Combined with Location, Radius and the filters below.

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

Optional category for search terms. With a category and no terms, browse that category. Pasted search URLs keep their own categories; use a URL for any category not listed here.

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

Optional: a city, district or 5-digit postcode (e.g. Berlin, München, 10115). Empty = all of Germany.

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

Optional: around the location, e.g. 10, 20, 50, 100. 0 = the location only.

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

Optional Kleinanzeigen search/category links. Their categories and category-specific attributes are preserved. Explicit price, seller, offer, Buy now and sort options below override the corresponding URL filter; neutral options preserve it. Newest first only fills in a missing sort.

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

Optional: only listings from this price.

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

Optional: only listings up to this price.

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

All sellers, only private sellers or only commercial sellers (dealers, shops).

## `sortNewest` (type: `boolean`):

Legacy/default option: use newest first only when the URL has no sort. An explicit Sort by choice takes precedence. Off preserves the URL/site order.

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

Separate items offered for sale from wanted ads. Any preserves a filter already present in a pasted URL.

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

Use Kleinanzeigen's direct-purchase filter. False does not remove a Buy now filter already present in a URL.

## `photosOnly` (type: `boolean`):

Skip listings without a photo or positive photo count on the search card, before details or charging. Enabling this creates a separate monitor baseline.

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

Explicit choices replace the URL's sort. Distance requires a location and a positive radius in every search. Source ordering may include promoted listings.

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

Maximum saved listings for each search, after filtering and deduplication. 0 disables this output cap; it does not guarantee complete coverage. Use a page/time cap to bound monitoring work even when there are no changes.

## `maxTotalListings` (type: `integer`):

Global limit across all searches. Counts only saved results after filtering/deduplication. 0 disables this additional limit.

## `maxSearchPagesPerSearch` (type: `integer`):

Limits search-page requests, including every price band, even when monitoring finds no changes. Retries and optional detail pages are not included. 0 disables this cap. RUN_SUMMARY records where scanning stopped.

## `maxRunSeconds` (type: `integer`):

Soft time limit for the whole run. In-flight requests and final storage writes may finish later. Use Apify's run timeout for a hard deadline. 0 disables this limit.

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

Open each listing for its full description, attributes (brand, model, condition…), category, all photos, shipping options and seller type / active since. Slower and charged as an extra event.

## `onlyNewListings` (type: `boolean`):

Return new listings and changes from the last observed price, including returns to an earlier price. Reuse the same search and Monitor key. Old monitor histories establish a fresh baseline once for known ads.

## `deduplicateAcrossSearches` (type: `boolean`):

Recommended: return and charge each listing once per run, using its first matching search. Disable only if you explicitly need a separately billed row for each matching search.

## `monitorKey` (type: `string`):

Optional name for an independent monitor (up to 80 characters). Keep this unchanged between scheduled runs. Different keys maintain separate baselines. Do not run the same search/key concurrently.

## Actor input object example

```json
{
  "searchTerms": [
    "iphone"
  ],
  "category": "all",
  "radiusKm": 0,
  "sellerType": "any",
  "sortNewest": true,
  "offerType": "any",
  "buyNowOnly": false,
  "photosOnly": false,
  "sortBy": "inherit",
  "maxListingsPerSearch": 20,
  "maxTotalListings": 20,
  "maxSearchPagesPerSearch": 3,
  "maxRunSeconds": 120,
  "includeDetails": false,
  "onlyNewListings": false,
  "deduplicateAcrossSearches": true
}
```

# Actor output Schema

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

Items produced by the run.

## `runSummary` (type: `string`):

Saved/charged results, skipped invalid cards, per-search pages checked and reasons for stopping at a spending, result, page or time limit.

# 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 = {
    "searchTerms": [
        "iphone"
    ],
    "maxListingsPerSearch": 20,
    "maxTotalListings": 20,
    "maxSearchPagesPerSearch": 3,
    "maxRunSeconds": 120,
    "includeDetails": false,
    "onlyNewListings": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("surefeed/kleinanzeigen-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchTerms": ["iphone"],
    "maxListingsPerSearch": 20,
    "maxTotalListings": 20,
    "maxSearchPagesPerSearch": 3,
    "maxRunSeconds": 120,
    "includeDetails": False,
    "onlyNewListings": False,
}

# Run the Actor and wait for it to finish
run = client.actor("surefeed/kleinanzeigen-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchTerms": [
    "iphone"
  ],
  "maxListingsPerSearch": 20,
  "maxTotalListings": 20,
  "maxSearchPagesPerSearch": 3,
  "maxRunSeconds": 120,
  "includeDetails": false,
  "onlyNewListings": false
}' |
apify call surefeed/kleinanzeigen-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,surefeed/kleinanzeigen-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ld879oX6Riqtuvr2f/builds/CgYUKzuAHRoStha94/openapi.json
