# Vinted Deal Monitor — New Listings, Price Drops & Alerts (`kondasviktor/vinted-deal-monitor`) Actor

Monitor Vinted searches across 20+ markets. Get new listings, price drops and underpriced items vs market median, with Telegram or webhook alerts. Pay only for new rows. JSON, CSV, API, MCP.

- **URL**: https://apify.com/kondasviktor/vinted-deal-monitor.md
- **Developed by:** [Viktor Kondas](https://apify.com/kondasviktor) (community)
- **Categories:** E-commerce, Automation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Vinted Deal Monitor — New Listings, Price Drops & Alerts

**Never miss an underpriced Vinted item again.** Save your searches once, schedule the monitor, and get new listings, price drops and items priced below the market median — straight to Telegram or your webhook. Works across 26 Vinted countries. **You only pay for what's new:** $0.025 per search check + $0.001 per new or changed listing.

> Independent tool, not affiliated with Vinted. Reads public listings only — no login, no account.

### What does Vinted Deal Monitor do?

- **Watches your saved searches** (keyword, brand, size, condition, price) in one or many Vinted countries.
- **Finds real deals:** compares each listing with similar items (same brand, category, size, condition) and flags those priced well below the median.
- **Tracks price drops** and listings that disappear (likely sold or removed).
- **Alerts you on Telegram or any webhook** with photo, price, discount and link.
- **Charges only for new or changed rows** on repeat runs — unchanged listings are free.

### Who is it for?

| You are… | You use it to… |
|---|---|
| Reseller / flipper | Catch underpriced sneakers, streetwear, vintage or luxury first |
| Vintage shop | Source stock by brand and size across countries |
| Collector | Get pinged when a specific item appears under your price |
| Analyst | Export market snapshots by brand, condition and country |

### Ready-made examples (one click)

| Example | What you get |
|---|---|
| [Find underpriced Nike Dunks on Vinted France](https://apify.com/kondasviktor/vinted-deal-monitor/examples/vinted-nike-dunk-deals-france) | Listings below the market median, with discount % |
| [Track Vinted price drops in the UK](https://apify.com/kondasviktor/vinted-deal-monitor/examples/vinted-price-drops-uk) | Levi's 501 listings whose price fell |
| [Spot designer bags listed below market](https://apify.com/kondasviktor/vinted-deal-monitor/examples/vinted-designer-bags-below-market) | Luxury bags in FR + IT vs comparable prices |
| [Monitor new Arc'teryx listings in Germany](https://apify.com/kondasviktor/vinted-deal-monitor/examples/vinted-new-listings-germany) | Newest listings, ready for Telegram alerts |
| [Export Vinted listings for a brand to CSV](https://apify.com/kondasviktor/vinted-deal-monitor/examples/vinted-brand-export-csv) | Snapshot across ES, IT and PT with normalized condition |

### How to use it

1. Add a search: keyword, markets and optional filters (or paste Vinted search URLs).
2. Optional: add your Telegram bot token + chat ID, or a webhook URL.
3. Run once to check results, then **Schedule** it (every 30–60 minutes is a good start).

**Tip:** set a fixed **Monitor ID**. The monitor remembers what it has seen under that name — if you leave it empty it is derived from your searches, so editing a search starts a fresh history.

**The first monitor run is a baseline.** It records up to *Max items per search* current listings (written as `NEW` rows, $0.001 each) but does not alert about them. From the second run on you only pay for, and get alerted about, what changed.

#### Set up Telegram alerts (2 minutes)

1. In Telegram, open **@BotFather**, send `/newbot`, copy the token.
2. Send any message to your new bot, then get your chat ID (e.g. from **@userinfobot**).
3. Paste both into the input. Done — alerts arrive with photo and link.

#### Webhook alerts

Set `webhookUrl` (Discord, Slack, Make, n8n, Zapier, your own server). The Actor POSTs `{ "monitorId", "runId", "events": [ … ] }` as JSON. Add `webhookSecret` and every request carries `X-Signature: sha256=<HMAC-SHA256 of the raw body>` so you can verify the sender. Delivery is retried 3 times; an alert failure never fails the run (it is logged in the OUTPUT record).

### Input

| Field | Description |
|---|---|
| `mode` | `monitor` (only new/changed rows, for schedules) or `snapshot` (full export, every row) |
| `searches` | Your saved searches: query, markets, brands, sizes, conditions, price range, include/exclude words |
| `searchUrls` | Or paste Vinted search URLs — country and filters are read from the URL |
| `markets` | Default markets: fr, de, uk, es, it, nl, be, pl, cz, sk, hu, lt, lv, ee, at, lu, pt, ro, gr, hr, fi, dk, se, ie, si, us |
| `alertOn` | `NEW`, `PRICE_DROP`, `DEAL`, `LIKELY_SOLD_OR_REMOVED` |
| `dealThresholdPct` | How far below the median counts as a deal (default 30%) |
| `minCompsForDeal` | Comparable listings needed for a deal verdict (default 8) |
| `includeItemDetails` | Adds description, all photos and seller rating ($0.002 per item) |
| `sellerMinRating`, `sellerMinReviews`, `excludeSellerIds` | Seller-quality filters (rating/reviews need item details) |
| `maxItemsPerSearch` | Cap per search per country — also your cost cap |
| `telegramBotToken`, `telegramChatId`, `webhookUrl`, `webhookSecret` | Alert destinations (stored as encrypted secrets) |

### Output

```json
{
  "eventType": "DEAL",
  "searchName": "Nike Dunk FR",
  "market": "fr",
  "title": "Nike Dunk Low Panda 42",
  "brand": "Nike",
  "size": "42",
  "condition": "very_good",
  "conditionRaw": "Très bon état",
  "price": 45,
  "totalPrice": 48.05,
  "currency": "EUR",
  "compsCount": 14,
  "compsMedianEur": 72,
  "discountVsMedianPct": 37.5,
  "compsConfidence": "medium",
  "isDeal": true,
  "url": "https://www.vinted.fr/items/…",
  "photoUrl": "https://images1.vinted.net/…",
  "firstSeenAt": "2026-11-12T09:30:00Z",
  "status": "ok"
}
```

Every run also saves an **OUTPUT** summary per search: status (`OK`, `PARTIAL`, `VALID_EMPTY`, `UPSTREAM_FAILED`), how many listings Vinted reported, how many were fetched, price-band splits and coverage %. Blocked runs are never reported as "no results".

### How much does it cost?

| Event | Price | When |
|---|---|---|
| Actor start | Apify default | Once per run |
| Search check | $0.025 | Each saved search × country per run (not charged when the search fails or is blocked) |
| New or changed listing | $0.001 | Each row written (monitor mode: only new/changed) |
| Item details | $0.002 | Only if you turn on details (not charged when the fetch fails) |

**Examples:** 1 search checked every hour ≈ $18/month + new listings. A monitor run with 3 searches in 2 markets ≈ $0.15 plus $0.001 per new listing. A one-off export of 1,000 listings ≈ $1.03. Set *Max cost per run* in Apify to cap spending — the Actor stops cleanly when it is reached and never delivers unpaid rows. Higher Apify plans get Store discounts automatically.

### Why it's different

- **Deal score, not just rows** — discount vs comparable items, with a confidence level.
- **Telegram built in** — no Zapier needed.
- **Pay only for what's new** on scheduled runs.
- **Honest coverage** — Vinted caps every search at 960 results; the Actor splits larger searches by price band and tells you exactly how complete each run was.
- **Conditions in English** across all countries, original label kept.
- **Clean failure states** — blocked is never shown as empty.
- **Each search uses its own country site** — mix France, Germany and the UK in one run.

### Use with AI agents (MCP)

> "Use kondasviktor/vinted-deal-monitor in snapshot mode to find Levi's 501 in very good condition under €30 in France and Germany."

### FAQ

**Can it see sold prices?** Vinted doesn't publish sold listings. The monitor marks items that disappear across two complete runs as `LIKELY_SOLD_OR_REMOVED` (turn it on in *Send alerts for*) — useful for sell-through, but not proof of sale. That check needs a complete view of the search, so it runs without the early-stop shortcut and only counts runs where everything Vinted reported was fetched.

**How often should I schedule it?** Every 30–60 minutes for hot items, daily for research. Very frequent schedules cost more and gain little.

**Which countries work?** All markets listed in the input, checked against the live sites. Mixed-country runs are supported; each search uses its own country site and its own currency (prices are also converted to EUR with the daily ECB rate).

**Why is my deal not flagged?** A deal needs enough comparable listings (default 8). Niche items show `compsConfidence: low` and no verdict — raise *Max items per search* so the market median is based on more listings, or lower *Minimum comparables*.

**Is it legal?** It reads public listing data without logging in and stores only pseudonymous seller IDs by default (usernames only if you switch them on). Check Vinted's terms for your use case. Not legal advice.

**Fakes?** Use `mustExclude` (e.g. "replica", "inspired") and seller filters. A low price can also mean a fake — check before buying.

**Something broke?** Open an issue — fixes within 48 hours.

### Limitations

- Seller rating filters need item details (extra cost).
- Comparable prices need at least 8 similar listings for a deal verdict.
- Upload time and view counts are not in the search results: `uploadedAt` is filled (English markets, item details on) from the item page's relative text, `viewCount` is always null.
- Not a buying bot — it never contacts sellers or purchases.

### Related Actors

- [Kleinanzeigen Deal Monitor](https://apify.com/kondasviktor/kleinanzeigen-deal-monitor) — the same alerts for Kleinanzeigen.de, with scam-risk flags, pickup distance and free-item alerts
- [Shopify Discount & Promo Tracker](https://apify.com/kondasviktor/shopify-discount-promo-tracker) — track new-retail discounts and promo depth on Shopify
- [Product Catalog Scraper](https://apify.com/kondasviktor/product-catalog-scraper) — export store catalogs as import-ready CSV
- [Website Change Monitor](https://apify.com/kondasviktor/website-change-monitor) — watch promo / PDP pages after a deal or price drop
- [EU Omnibus Discount Checker](https://apify.com/kondasviktor/eu-omnibus-discount-checker) — audit claimed discounts vs the EU 30-day lowest price

***

Built by [Viktor Kondas](https://apify.com/kondasviktor) · [Vibe Coder's Life](https://vibecoderslife.com/?utm_source=apify\&utm_medium=readme\&utm_campaign=vinted-deal-monitor) · Custom monitors: hello@vibecoderslife.com

# Actor input Schema

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

**monitor** = remember what it has seen and write only NEW / PRICE_DROP / DEAL / LIKELY_SOLD rows (schedule this). **snapshot** = one-off export, every row is written and charged.

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

Your saved searches. Each: `{ "name": "Nike Dunk FR", "query": "nike dunk", "markets": ["fr","de"], "brandIds": [], "catalogIds": [], "sizeIds": [], "conditions": [], "priceMin": 0, "priceMax": 100, "mustInclude": [], "mustExclude": ["replica"] }`. Only `query` (or a catalog/brand id) is required; everything else falls back to the global fields below. One search × one market = one paid "tracked search" per run.

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

Paste Vinted search URLs instead — the country and filters (keyword, brand, size, condition, price) are read from each URL. Mixed countries are fine.

## `markets` (type: `array`):

Markets for searches that do not set their own. Each market is a separate paid search check.

## `conditions` (type: `array`):

Only listings in these conditions (normalized to English across all countries). Leave all selected for no filter.

## `priceMin` (type: `number`):

In the market's own currency (e.g. EUR, GBP, PLN, HUF).

## `priceMax` (type: `number`):

In the market's own currency.

## `mustInclude` (type: `array`):

Every term must appear in the title (and description when item details are on). Case- and accent-insensitive.

## `mustExclude` (type: `array`):

Drop listings containing any of these terms — use it against fakes: "replica", "fake", "inspired".

## `excludeSellerIds` (type: `array`):

Seller IDs to ignore, e.g. known relisters. The ID is in the `sellerId` output field.

## `maxItemsPerSearch` (type: `integer`):

Per search per market. **Also your cost cap**: every written row costs $0.001. Above 960 the Actor splits the search into price bands to get past Vinted's 960-result ceiling.

## `alertOn` (type: `array`):

Events that trigger Telegram / webhook alerts. The first monitor run is a baseline and does not announce existing listings as NEW. Alerts are sent in monitor mode only.

## `dealThresholdPct` (type: `number`):

A listing is a DEAL when its price is at least this far below the median of comparable listings (same brand, category, size, condition).

## `minCompsForDeal` (type: `integer`):

Below this many comparable listings there is no deal verdict (`compsConfidence: low`). 8–19 comparables = medium confidence, 20+ = high.

## `priceDropThresholdPct` (type: `number`):

Minimum price reduction since the last time the listing was seen.

## `includeItemDetails` (type: `boolean`):

Open each written listing for description, all photos, seller rating, review count and brand/category IDs. Costs $0.002 per item; a failed fetch keeps the row (as partial) and is not charged.

## `sellerMinRating` (type: `number`):

Needs "Include item details". Sellers without enough verified rating are left out.

## `sellerMinReviews` (type: `integer`):

Needs "Include item details".

## `includeSellerLogin` (type: `boolean`):

Off by default (GDPR-minimal): rows carry the pseudonymous seller ID and profile link only. Needs item details to fill the username.

## `outputCurrency` (type: `string`):

Prices are also converted into this currency with the daily ECB rate (`priceOut`). `priceEur` is always included.

## `telegramBotToken` (type: `string`):

From @BotFather (`/newbot`). Looks like `123456:ABC-DEF…`.

## `telegramChatId` (type: `string`):

Your user, group or channel ID (send a message to your bot, then ask @userinfobot). Alerts arrive with photo, price, discount and link.

## `webhookUrl` (type: `string`):

POST `{ monitorId, runId, events: [...] }` as JSON — works with Discord, Slack, Make, n8n, Zapier. Stored as a secret (many webhook URLs contain tokens).

## `webhookSecret` (type: `string`):

Optional. Adds an `X-Signature: sha256=…` header (HMAC-SHA256 of the raw body) so your endpoint can verify the sender.

## `maxAlertsPerRun` (type: `integer`):

Above this, one digest message is sent instead of many.

## `monitorId` (type: `string`):

Name of the state store (a–z, 0–9, "-"). Derived from your searches when empty — so **set it explicitly** if you plan to edit your searches and keep the history of what was already seen.

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

Starts on Apify datacenter proxy and escalates to residential (in the market's country) only when Vinted keeps blocking — sticky per market for the rest of the run.

## Actor input object example

```json
{
  "mode": "snapshot",
  "searches": [
    {
      "name": "Levi's 501 FR",
      "query": "levis 501",
      "markets": [
        "fr"
      ],
      "mustExclude": [
        "replica",
        "fake"
      ]
    }
  ],
  "markets": [
    "fr"
  ],
  "conditions": [
    "new_with_tags",
    "new_without_tags",
    "very_good",
    "good",
    "satisfactory"
  ],
  "mustExclude": [
    "replica",
    "fake"
  ],
  "maxItemsPerSearch": 20,
  "alertOn": [
    "DEAL",
    "PRICE_DROP"
  ],
  "dealThresholdPct": 30,
  "minCompsForDeal": 8,
  "priceDropThresholdPct": 10,
  "includeItemDetails": false,
  "includeSellerLogin": false,
  "outputCurrency": "EUR",
  "maxAlertsPerRun": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `summary` (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": "snapshot",
    "searches": [
        {
            "name": "Levi's 501 FR",
            "query": "levis 501",
            "markets": [
                "fr"
            ],
            "mustExclude": [
                "replica",
                "fake"
            ]
        }
    ],
    "markets": [
        "fr"
    ],
    "mustExclude": [
        "replica",
        "fake"
    ],
    "maxItemsPerSearch": 20,
    "dealThresholdPct": 30,
    "minCompsForDeal": 8,
    "priceDropThresholdPct": 10,
    "includeItemDetails": false,
    "outputCurrency": "EUR",
    "maxAlertsPerRun": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("kondasviktor/vinted-deal-monitor").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": "snapshot",
    "searches": [{
            "name": "Levi's 501 FR",
            "query": "levis 501",
            "markets": ["fr"],
            "mustExclude": [
                "replica",
                "fake",
            ],
        }],
    "markets": ["fr"],
    "mustExclude": [
        "replica",
        "fake",
    ],
    "maxItemsPerSearch": 20,
    "dealThresholdPct": 30,
    "minCompsForDeal": 8,
    "priceDropThresholdPct": 10,
    "includeItemDetails": False,
    "outputCurrency": "EUR",
    "maxAlertsPerRun": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("kondasviktor/vinted-deal-monitor").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": "snapshot",
  "searches": [
    {
      "name": "Levi'\''s 501 FR",
      "query": "levis 501",
      "markets": [
        "fr"
      ],
      "mustExclude": [
        "replica",
        "fake"
      ]
    }
  ],
  "markets": [
    "fr"
  ],
  "mustExclude": [
    "replica",
    "fake"
  ],
  "maxItemsPerSearch": 20,
  "dealThresholdPct": 30,
  "minCompsForDeal": 8,
  "priceDropThresholdPct": 10,
  "includeItemDetails": false,
  "outputCurrency": "EUR",
  "maxAlertsPerRun": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call kondasviktor/vinted-deal-monitor --silent --output-dataset

```

## MCP server setup

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

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/wcjRWwxdcLn3N7cht/builds/ASImNYWY67pDEns9Z/openapi.json
