# Kleinanzeigen Deal Radar (`lowlanddata/kleinanzeigen-deal-radar`) Actor

Sweeps one Kleinanzeigen search each run, computes the query's live median price, and delivers listings priced far below it - median and discount percent attached, optional radius around your city. Scheduled hourly it is a German-market deal alert. No seller data, no auto-buy.

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

## Pricing

from $3.79 / 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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Kleinanzeigen Deal Radar — underpriced listings, caught while they're live

Point it at any **Kleinanzeigen.de** search and it tells you which of the newest listings are priced well below what that market currently asks. Each run sweeps the search newest-first, computes the query's current median asking price, and delivers only the listings you have not seen before that sit at least your chosen percentage below that median — with the median and the discount attached to every deal. Schedule it hourly and it becomes a deal alert feed for the German second-hand market.

Built on the [Kleinanzeigen.de Scraper](https://apify.com/lowlanddata/kleinanzeigen-scraper) and its GDPR-first output: **no seller personal data, ever.**

### Quick start (30 seconds)

1. Put what you'd type in the Kleinanzeigen search box into **searchQuery** — e.g. `e-bike`.
2. Click **Start**. That's the whole minimum setup.
3. The first run establishes the baseline and shows you every current deal in the snapshot. From the second run on, only new deals arrive.

Real numbers from a real first run: for `e-bike`, the sweep put the median at €750.00 over 79 priced listings and surfaced more than 25 listings priced 40% or more below it — on the very first pass.

To turn it into an alert feed, create an hourly **Schedule** in the Apify Console with the same input and add an email or Slack integration — the setup is spelled out in [the monitoring section below](#how-do-i-get-kleinanzeigen-deal-alerts).

### What you can build with it

- **Source resale stock.** Flippers watch a niche (`categoryId` 217, "e-bike cube") and get every underpriced unit the hour it posts, discount already computed — no manual median-guessing, no refreshing the site.
- **Wait for one item at the right price.** Set your search, your city and a 15 km radius, and let the radar sit on it. When someone lists a Kettler trampoline 35% under market three towns over, you hear about it once, with the link.
- **Restock a small shop.** Dealers in used bikes, phones or furniture schedule a handful of watches and skim the feed each morning — the price, the median and the distance are already in the row.

### What you get

Each deal is one dataset item:

```json
{
  "listingId": "2098800001",
  "url": "https://www.kleinanzeigen.de/s-anzeige/e-bike/2098800001",
  "title": "E-Bike Cube Reaction Hybrid",
  "description": "Wenig gefahren, Akku neu.",
  "priceCents": 95000,
  "priceType": "NEGOTIABLE",
  "location": "80331 München",
  "postedAt": "Heute, 10:15",
  "imageUrl": "https://img.kleinanzeigen.de/example.jpg",
  "adType": "offer",
  "shippingPossible": true,
  "queryMedianCents": 165000,
  "discountPct": 42
}
```

Field notes, so you know exactly what you are buying:

- `queryMedianCents` is the query's median asking price **at the time of this run**, computed from the same sweep the deal came from — not a stale reference number.
- `discountPct` is how far below that median the listing is priced. A deal is only delivered when this meets your `discountThresholdPct`.
- `priceCents` is the asking price in euro cents; `priceType` tells you how to read it. `NEGOTIABLE` is the German "VB" — an asking price the seller expects to haggle down from, so the real price is usually lower still.
- `adType` is always `offer` here — "Gesuch" wanted ads and give-aways never count as deals, so they never pollute the feed.
- `location` and `shippingPossible` tell you whether it's a pickup trip or a parcel.
- The run's status message summarizes the result at a glance: `N deals in the current snapshot (baseline established)` on the first run, `N new deals at least X% below the median` after that.

Seller identity is never collected — same guarantee as the parent scraper.

### How much does deal hunting cost?

$1.99 per 1,000 deals delivered, pay-as-you-go. Quiet runs — the sweeps that find nothing new — cost nothing beyond the actor start. In practice:

- **An hourly watch that catches ~5 deals a day ≈ $0.30/month.**
- **Three watches at that pace ≈ under a dollar a month.**
- You pay for deals, not for watching.

The per-deal price is all-inclusive — proxy and platform usage are covered. Free-plan runs are limited to 25 deals, enough to see the feed working against your real query.

### Not technical? Let your AI assistant set it up

Copy this into ChatGPT, Claude or any AI assistant, fill in the one line, and follow the conversation:

```text
Help me set up the "Kleinanzeigen Deal Radar" actor on Apify
(https://apify.com/lowlanddata/kleinanzeigen-deal-radar). Guide me one step at a time.

What I want to catch: [E.G. "e-bikes at least 30% under market price, within 25 km of Köln"]

Guide me to:
1. Propose my input values: searchQuery (what I'd type in the Kleinanzeigen search
   box, German terms work best), discountThresholdPct (how far below the market
   median a listing must be — 30 is a sane start), optional location + radiusKm if
   I can only pick things up nearby, optional priceMinEur/priceMaxEur to keep
   accessories out of the median, and maxItems.
2. If my watch fits one category: browse kleinanzeigen.de to that category and read
   the number from the URL (/s-fahrraeder/c217 means categoryId 217), then add it —
   it keeps the median honest.
3. Create a free Apify account (apify.com), open the actor page, paste the values
   into the Input form, and start a run. The first run establishes the baseline.
4. Set up an hourly Schedule in the Apify Console with the same input, plus an
   email or Slack integration on the runs, so each new deal reaches me while the
   listing is still live.
5. Show me how to export deals as CSV/Excel, or read them from the API if I code.
6. If it catches real deals for me, remind me at the end to leave a quick rating on
   the actor page, and to report anything broken or missing on its Issues tab.
```

### Input

| Field                         | Description                                                                                                                                        |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `searchQuery`                 | The market slice to hunt in, e.g. `e-bike cube`. Required. Narrower queries give sharper medians.                                                  |
| `categoryId`                  | Optional numeric category ID (the `c217`-style number in the site's URLs) to keep the median clean — e.g. `217` for bikes.                         |
| `location` / `radiusKm`       | Hunt only around a German city or region (resolved via the site's own location search), `radiusKm` 1–200, default 10. The radius needs a location. |
| `priceMinEur` / `priceMaxEur` | Ignore listings outside this range — the classic use is a floor that keeps €15 accessories out of the median.                                      |
| `discountThresholdPct`        | A listing must be at least this far below the current median to count as a deal. 10–80, default 30.                                                |
| `samplePages`                 | Pages swept per run, 1–10, default 4. Page 1 carries the new listings; the extra pages steady the median.                                          |
| `stateStoreName`              | Named store remembering which listings were already evaluated, so scheduled runs alert each deal once. Use a different name per watched query.     |
| `resetState`                  | Forget everything evaluated so far and re-baseline on this run.                                                                                    |
| `maxItems`                    | Upper bound on deals delivered per run. Default 100 (25 on the free plan). Deals found beyond the cap stay pending and arrive with the next run.   |

The sweep needs at least 20 priced listings to define a market price. Fewer than that, and the run fails with a readable message instead of scoring deals against a coin-flip median — broaden the query or raise `samplePages`.

### Use it from your code

Run the radar and get deals straight back with one HTTP call:

```bash
curl "https://api.apify.com/v2/acts/lowlanddata~kleinanzeigen-deal-radar/run-sync-get-dataset-items?token=<YOUR_API_TOKEN>" \
  -X POST -H "Content-Type: application/json" \
  -d '{"searchQuery": "e-bike", "discountThresholdPct": 30}'
```

Node.js:

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

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('lowlanddata/kleinanzeigen-deal-radar').call({
  searchQuery: 'e-bike',
  discountThresholdPct: 30,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("lowlanddata/kleinanzeigen-deal-radar").call(
    run_input={"searchQuery": "e-bike", "discountThresholdPct": 30})
deals = client.dataset(run["defaultDatasetId"]).list_items().items
```

Schedules, webhooks and the Make/Zapier/n8n integrations all work out of the box — this is a standard Apify actor.

### Use it with AI agents (MCP)

Claude, Cursor and other MCP-capable agents can run the radar as a tool through Apify's hosted MCP server — the agent sets the search, starts the run and reads the deals itself.

Claude Code:

```bash
claude mcp add apify --transport http "https://mcp.apify.com?actors=lowlanddata/kleinanzeigen-deal-radar"
```

Cursor or Claude Desktop (add a custom connector / MCP server with this URL):

```text
https://mcp.apify.com?actors=lowlanddata/kleinanzeigen-deal-radar
```

Sign in with your Apify account when prompted — runs are billed to it. Setup details per client: [Apify MCP docs](https://docs.apify.com/platform/integrations/mcp).

Prompts that work once connected:

- "Run the Kleinanzeigen deal radar for 'Makita Akkuschrauber' at 25% and tell me which deals look genuinely good."
- "Hunt e-bike deals within 20 km of München and rank them by discount."
- "Check my sofa watch and summarize anything new at least 40% under the median."

### Is it legal?

The radar reads the same public listing data as its parent scraper — prices, titles, area-level locations — and inherits the same GDPR-first design: **seller-identifying data is never extracted**, so no personal data enters your dataset in the first place. Requests are paced to keep load on the marketplace negligible, and no anti-bot protections are bypassed. Titles and descriptions are the seller's own words, delivered as-is.

### Is there a Kleinanzeigen deals API?

No — Kleinanzeigen publishes no public API, and its own saved-search emails tell you about *new* listings, not *underpriced* ones. This actor is the practical alternative: a per-query market median computed live, new listings scored against it, and only the outliers delivered — through one HTTP call, a schedule, or an MCP tool.

### Does Kleinanzeigen block it?

The radar makes a handful of paced search-page requests per run — a few pages, newest first — over standard Apify datacenter proxies. That is a lighter footprint than a human refreshing the site. No CAPTCHA fights; if the site's markup changes, the run fails with a readable message instead of silently scoring against wrong data.

### How do I get Kleinanzeigen deal alerts?

This actor **is** the alert — the scheduling is the only part you add:

1. Get one manual run working (the baseline run).
2. In the Apify Console, create a **Schedule** with the same input, hourly.
3. Add an email or Slack **integration** on the actor's runs.

From then on, every hour the radar sweeps the newest listings, scores them against the current median, and delivers only the not-yet-seen deals — so a notification means there is actually something to look at. Your latency to a new deal is your schedule cadence: hourly means you hear about a deal within minutes to an hour of it posting. There is no auto-buying and no auto-messaging — the radar finds, you decide.

### FAQ

**How do I find cheap deals on Kleinanzeigen?** The manual way is refreshing a search sorted by date and knowing the market price by heart. This actor automates exactly that: it computes the market median for your search live, checks every new listing against it, and hands you the ones priced far enough below — link, price, median and discount included.

**Kleinanzeigen Schnäppchen automatisch finden?** Ja — genau dafür ist dieser Actor gebaut. Suchbegriff eingeben, Schwelle setzen (z. B. 30 % unter dem Marktmedian), stündlichen Schedule anlegen: jedes neue Schnäppchen kommt einmal, mit Preis, Median und Rabatt. / Yes — that is exactly what this actor does: set your search and threshold, schedule it hourly, and each new bargain arrives once, with price, median and discount attached.

**How is the median calculated?** Every run sweeps up to `samplePages` pages of your search newest-first, takes every priced offer in the sample (give-aways and wanted ads excluded), and uses the median of those asking prices. It is the query's *current* market price, recomputed each run — when the market moves, the yardstick moves with it.

**Why did my run fail with "too few priced listings"?** The median needs at least 20 priced listings to mean anything. Below that the run refuses to score deals and says so: broaden the query or raise `samplePages`.

**What does VB mean for a deal's price?** `NEGOTIABLE` in the output. It is an *asking* price the seller expects offers below — so a VB deal is usually even better than its `discountPct` suggests. But remember the whole median is built from asking prices too; the discount compares like with like.

**A listing is 60% below the median — is it real?** Sometimes. It can also be a defect the title doesn't mention, a part sold as the whole thing, or a scam. The radar does the math; the listing text and the photos are the judge. Steeper discount, harder look — and never pay in advance to a stranger.

**Can I hunt only where I can pick things up?** Yes — set `location` to your city and `radiusKm` (1–200 km, default 10). For bulky items like furniture or bikes, a tight radius plus a modest threshold beats a nationwide feed you can't act on.

**What discount threshold should I set?** The default 30% is a good hunting ground: real bargains, not just haggling room. Go 20–25% for slow, high-value markets where any discount is rare; go 40%+ if you only want the drop-everything finds. The range is 10–80.

**What does "baseline established" mean?** The first run for a watch has no memory yet, so it shows you every deal in the current snapshot and remembers everything it evaluated. From the next run on, only listings it has never seen can become deals — that's what makes the hourly feed quiet and worth reading.

**Will I ever get the same deal twice?** No. Each listing is evaluated exactly once and remembered for a month — long past the point where a real deal would still be available.

**What happens to deals beyond maxItems?** They stay pending. If a run finds more deals than it may deliver, the surplus is not lost — it arrives with the next run.

**Can I watch several searches at once?** Yes — one Schedule per search. Give each watch its own `stateStoreName` so their memories stay separate; the actor also keys state by the full query (search, category, location, prices), so watches never contaminate each other.

**Do give-aways and wanted ads count as deals?** Never. "Zu verschenken" items have no price to discount, and "Gesuch" ads are people *looking for* items — both are excluded from the median and from the feed. (For give-aways, see the Free Finds actor below.)

**How fast do I hear about a deal?** As fast as your schedule runs. The sweep itself takes seconds; hourly scheduling means minutes-to-an-hour latency. There is no push connection to the site — the cadence is yours to set.

**How do I start a watch over?** Set `resetState: true` for one run — the watch forgets everything evaluated so far and re-baselines. Useful after changing the search or the threshold substantially.

**Can I export deals to Excel or CSV?** Yes — every run's dataset exports as CSV, Excel, JSON or XML from the Apify Console or API, and the Overview tab shows deals as a clean table with median and discount columns.

**What does it cost?** $1.99 per 1,000 deals delivered, all-inclusive. Runs that find nothing cost nothing beyond the actor start — you pay for deals, not for watching. Free-plan runs deliver up to 25 deals.

**Does it collect seller data?** No — same guarantee as the parent scraper: seller names, IDs, phone numbers and profile links never appear in the output, by design.

**Does it buy or message sellers for me?** No. The radar finds and scores; contacting the seller and judging the item stay with you — deliberately.

### Related scrapers

Same GDPR-clean guarantee, same marketplace family:

- [Kleinanzeigen.de Scraper](https://apify.com/lowlanddata/kleinanzeigen-scraper) — the parent: full search extraction, categories, car specs, market snapshots.
- [Kleinanzeigen Free Finds](https://apify.com/lowlanddata/kleinanzeigen-free-finds) — the "Zu verschenken" watch: free items near you, alerted once.
- [Marktplaats New Listing Alerts](https://apify.com/lowlanddata/marktplaats-new-listing-alerts) — the new-listing watch for the Netherlands' largest marketplace.

### Troubleshooting

The actor fails fast with the reason in the run's status message:

- **"Only N priced listings in the sample — too few to define a market price for this query."** — broaden the query, drop a too-tight price band, or raise `samplePages`.
- **First run delivered a flood of deals** — that's the baseline run showing the current snapshot; subsequent runs deliver only what's new. Lower `maxItems` if you want the baseline capped (the rest stays pending).
- **No deals for days** — normal in slow markets; the status message still tells you the run swept and found nothing new. Check that the threshold isn't set above what the market ever offers.
- **Same-looking listing appeared again** — sellers delete and repost ads to bump them; a reposted ad has a new listing ID and is honestly a new listing.
- **Fewer deals than expected on a free plan** — the 25-item free cap; a paid Apify plan removes it.

### Support

Found an issue or missing a field you need? Open an issue on the actor's **Issues tab** — reports get fixed, this actor is actively maintained.

Catching real deals with it? A **rating on this page** takes ten seconds and helps other bargain hunters find it — it is also the clearest signal of what to build next.

# Actor input Schema

## `searchQuery` (type: `string`):

The market slice to hunt deals in, exactly as you would type it on kleinanzeigen.de, e.g. "e-bike cube". Narrower queries give sharper medians.

## `categoryId` (type: `integer`):

Optional Kleinanzeigen category id to keep the median clean (e.g. 217 for bikes).

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

Optional German city or region name, resolved via the site's own location search.

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

Hunt this many km around the location (up to 200, default 10). Needs a location.

## `priceMinEur` (type: `integer`):

Ignore listings below this price - cuts parts-and-accessories noise from the median.

## `priceMaxEur` (type: `integer`):

Ignore listings above this price.

## `discountThresholdPct` (type: `integer`):

A listing must be at least this far below the query's current median to count as a deal.

## `samplePages` (type: `integer`):

Pages swept per run (1-10). Page 1 carries the new listings; extra pages steady the median.

## `stateStoreName` (type: `string`):

Named store remembering which listings were already evaluated, so scheduled runs alert each deal once. Use a different name per watched query.

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

Forget everything evaluated so far and re-baseline on this run.

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

Upper bound on deals delivered per run.

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

Proxy settings. Keep Apify proxy enabled.

## Actor input object example

```json
{
  "searchQuery": "e-bike",
  "discountThresholdPct": 30,
  "samplePages": 4,
  "stateStoreName": "kleinanzeigen-deal-radar-state",
  "resetState": false,
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `deals` (type: `string`):

Underpriced listings, one JSON object per deal.

# 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 = {
    "searchQuery": "e-bike",
    "discountThresholdPct": 30,
    "samplePages": 4,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("lowlanddata/kleinanzeigen-deal-radar").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 = {
    "searchQuery": "e-bike",
    "discountThresholdPct": 30,
    "samplePages": 4,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("lowlanddata/kleinanzeigen-deal-radar").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 '{
  "searchQuery": "e-bike",
  "discountThresholdPct": 30,
  "samplePages": 4,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call lowlanddata/kleinanzeigen-deal-radar --silent --output-dataset

```

## MCP server setup

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

```

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/dnRWbhVUX2yPJ3rgB/builds/E6UcdxrLYsv66Htdw/openapi.json
