# Vinted Sold Detector (`lowlanddata/vinted-sold-detector`) Actor

Vinted hides sold listings - this actor watches one bounded search on a schedule and emits likely-sold items with the last asking price, days listed, brand, size and condition. The sold-comps signal resellers need, with zero seller data by construction.

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

## Pricing

from $1.49 / 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

## Vinted Sold Detector — real sold prices from a market Vinted hides

Vinted deletes sold listings from search and publishes no sold history — the one number a reseller needs, the price things actually sell at, is not on the site. This actor recovers it: it snapshots one bounded search on a schedule, and when a listing disappears for several runs in a row it emits a `likely_sold` event carrying the last asking price, days listed, brand, size and condition. Sold comps for Vinted, built from observation.

It is a companion to the [Vinted Scraper](https://apify.com/lowlanddata/vinted-scraper) and shares its guarantee: **no seller personal data, ever** — the output whitelist is the same, structurally.

### Quick start (30 seconds)

1. Put a **narrow** market slice into **searchQuery** — e.g. `nike air max 90`. One snapshot covers at most ~960 listings, so the query must fit; add a `priceMin`/`priceMax` band if it doesn't.
2. Pick your **domain** — `fr`, `de`, `co.uk`, `be`... (23 country sites).
3. Click **Start** once, then put the same input on an hourly or daily **Schedule** — detection works by comparing runs, so a single run only seeds the baseline.

The first run reports "tracking N live listings — sold detections start next run". From the second run on, disappeared listings arrive as sold events.

### What you can build with it

- **Price your stock from real sold prices.** Live asking prices tell you what sellers hope for; sold events tell you what buyers paid. Watch your niche for a week and price against `lastPriceCents` of what actually moved.
- **Test a sourcing thesis.** Before buying a lot of a brand, watch its query for a few days: if nothing turns `likely_sold`, the brand lists but doesn't sell.
- **Measure sell-through speed.** `daysListed` on every event shows how long items sat before selling — sort by it to find what flies off the shelf versus what lingers.
- **Feed a pricing pipeline.** Events are clean structured JSON with no personal data, ready for a spreadsheet, a dashboard or an AI agent without scrubbing.

### What you get

Each sold detection is one dataset item:

```json
{
  "listingId": "9720000001",
  "title": "Nike Air Max 90",
  "url": "https://www.vinted.fr/items/9720000001-nike-air-max-90",
  "lastPriceCents": 4500,
  "currency": "EUR",
  "brand": "Nike",
  "size": "42",
  "condition": "Très bon état",
  "firstSeenAt": "2026-08-20T10:00:00.000Z",
  "lastSeenAt": "2026-08-24T10:00:00.000Z",
  "soldDetectedAt": "2026-08-26T10:00:00.000Z",
  "daysListed": 6,
  "status": "likely_sold"
}
```

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

- `lastPriceCents` is the last asking price this actor observed before the listing vanished, in minor units. If the seller repriced along the way, the event carries the final price — the one closest to what the buyer paid.
- `url` may return a 404 — that is the point: Vinted removes sold listings, and the dead link is itself evidence of the sale.
- `daysListed` counts from the first time this actor saw the listing to detection — a sell-through speed signal, not the listing's full lifetime if it predates your watch.
- `status` is always `likely_sold`, and the word "likely" is deliberate — see the honest limits below.
- `condition` and `size` come in the site's language, as displayed. Seller identity is never collected.

#### Honest limits

- **"Likely" sold, not "certainly" sold.** From outside, a listing the seller deleted or relisted looks identical to one that sold. Most disappearances in an active niche are sales, but no outside observer can prove each one — which is why the field says `likely_sold` and not `sold`.
- **The query must fit one snapshot.** Vinted serves at most 10 pages (~960 listings) per search. If your query matches more, listings pushed out of that window look like disappearances. The run detects this and warns — "narrow the query with a price band" — and detections are only trustworthy on fully-covered queries.
- **Detection speed equals schedule speed.** The actor detects between runs, so an hourly schedule with the default threshold of 2 flags a sale within roughly 2 hours; a daily schedule, within roughly 2 days.

### How much does it cost?

$1.99 per 1,000 sold events delivered, pay-as-you-go. You pay for detections, not for watching: the baseline run and every run where nothing sold deliver nothing and cost nothing beyond the small actor-start event. A watch that catches 10 sales a day costs about $0.60 a month.

The price is all-inclusive — platform usage is covered, with no separate compute or proxy charges. Datacenter proxies are sufficient; no residential surcharge.

Free-plan runs are capped at 25 sold events per run, enough to evaluate the output against your real niche.

### 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 "Vinted Sold Detector" actor on Apify
(https://apify.com/lowlanddata/vinted-sold-detector). Guide me one step at a time.

What I want to know sold prices for: [E.G. "Nike Air Max 90 between 30 and 80 euros on vinted.fr"]

Guide me to:
1. Propose my input values: a NARROW searchQuery (one snapshot covers ~960
   listings at most — add a priceMin/priceMax band if my niche is bigger),
   domain (fr, de, co.uk, be, ...), absenceThreshold (2 is the safe default),
   and a stateStoreName unique to this watch.
2. Create a free Apify account (apify.com), open the actor page, paste the
   values into the Input form, and start the first run — it only seeds the
   baseline, that is expected.
3. Set up an HOURLY or daily Schedule in the Apify Console with the exact same
   input — this actor only detects by comparing scheduled runs — plus an email
   or Slack integration so sold events reach me automatically.
4. Show me how to export the sold events as CSV/Excel, or read them from the
   API if I code.
5. If the results are what I wanted, remind me at the end to leave a quick
   rating on the actor page, and to report anything broken on its Issues tab.
```

### Input

| Field                | Description                                                                                                                                                            |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `searchQuery`        | The market slice to watch, as typed in Vinted's search box. Keep it narrow — one snapshot covers ~960 listings at most. Required.                                      |
| `domain`             | National site: `fr` (default), `be`, `de`, `nl`, `es`, `it`, `co.uk`, `pl`, `at`, `pt`, `cz`, `sk`, `lt`, `se`, `dk`, `fi`, `ie`, `lu`, `hu`, `ro`, `gr`, `hr`, `com`. |
| `priceMin`           | Watch only listings at or above this price (site currency) — the standard way to narrow a broad query.                                                                 |
| `priceMax`           | Watch only listings at or below this price (site currency).                                                                                                            |
| `absenceThreshold`   | Consecutive runs a listing must be missing before it counts as likely sold. `2` (default) absorbs single-run hiccups; `1` detects faster with more noise. Range 1–10.  |
| `maxPages`           | Catalog pages per snapshot (1–10; the site serves at most 10). Lower is cheaper but covers less of the query.                                                          |
| `stateStoreName`     | Named store carrying the tracked-listings memory. One name per watched query — different watches must not share a store.                                               |
| `resetState`         | `true` forgets the tracked listings and re-seeds the baseline on this run. Default `false`.                                                                            |
| `maxItems`           | Upper bound on sold events delivered per run (default 500).                                                                                                            |
| `proxyConfiguration` | Proxy settings; keep Apify proxy enabled.                                                                                                                              |

A run minimally needs a `searchQuery`; invalid combinations (like an inverted price band) fail immediately with the reason in the run's status message.

### Use it from your code

The natural home for this actor is a Schedule, but every run is a normal Apify run you can start and read from code:

```bash
curl "https://api.apify.com/v2/acts/lowlanddata~vinted-sold-detector/run-sync-get-dataset-items?token=<YOUR_API_TOKEN>" \
  -X POST -H "Content-Type: application/json" \
  -d '{"searchQuery": "nike air max 90", "domain": "fr", "priceMin": 30, "priceMax": 80}'
```

Node.js:

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

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('lowlanddata/vinted-sold-detector').call({
  searchQuery: 'nike air max 90',
  domain: 'fr',
  priceMin: 30,
  priceMax: 80,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("lowlanddata/vinted-sold-detector").call(
    run_input={"searchQuery": "nike air max 90", "domain": "fr",
               "priceMin": 30, "priceMax": 80})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

Remember: one call seeds or updates the watch — sold events come from the difference between calls, so schedule them. Webhooks and the Make/Zapier/n8n integrations work out of the box; a webhook on run-succeeded is the cleanest way to pipe fresh sold events into your own system.

### Use it with AI agents (MCP)

Claude, Cursor and other MCP-capable agents can run this actor as a tool through Apify's hosted MCP server: the agent sets up the watch, reads the sold events and reasons over them — no glue code.

Claude Code:

```bash
claude mcp add apify --transport http "https://mcp.apify.com?actors=lowlanddata/vinted-sold-detector"
```

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

```text
https://mcp.apify.com?actors=lowlanddata/vinted-sold-detector
```

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:

- "Check my 'nike air max 90' watch on vinted.fr and summarize what sold since yesterday, with prices."
- "From the latest sold events, what is the median sold price and median days listed?"
- "Which brands in my watch sell within 3 days?"

### Is it legal to detect Vinted sales this way?

The actor reads the same public search results any visitor sees, and infers sales by comparing what it saw yesterday with what it sees today — no purchase records, no seller accounts, no private data touched. The output carries no usernames, user IDs or profile links, structurally: the same whitelist mapper as the Vinted Scraper decides what can exist in the dataset, and seller identity is not on the list. What you store is public commercial information about items, plus timestamps this actor generated itself.

Requests are paced, load on the site is kept negligible, and no anti-bot protection is bypassed.

### Is there a Vinted sold-items API?

No. Vinted publishes no public API at all, and the site deliberately shows no sold history — a sold listing is simply removed. There is nothing to query for past sales; the only way to know is to have been watching when the listing vanished. That is exactly what this actor does, and why it must run on a schedule rather than answer one-off questions about the past.

### Does Vinted block scrapers?

Vinted protects some routes aggressively; this actor stays on the openly served search pages with paced requests and standard datacenter proxies, and never fights a block. If a run is blocked before it can take its snapshot, it fails with an honest status message and — importantly — does not touch the watch state, so a blocked run never causes false sold detections. The next scheduled run carries on.

### How do I monitor Vinted sold items?

This actor is the monitor. The whole setup is: one narrow query, one Schedule, one state store.

1. Run once manually to seed the baseline and confirm the query fits one snapshot (no coverage warning in the run log).
2. Add a Schedule in the Apify Console — hourly for fast-moving niches, daily for slow ones — with the exact same input.
3. Attach an email or Slack integration to the runs, or a webhook on run-succeeded, and sold events arrive as they are detected.

To watch a second niche, create a second Schedule with its own `searchQuery` and its own `stateStoreName`.

### FAQ

**How do I see sold items on Vinted?** You can't on the site — Vinted removes sold listings from search and shows no sold section. The only way to see what sold is to record the market before the sale, which is what this actor does: it watches a search on a schedule and reports the listings that disappeared.

**How much did something sell for on Vinted?** Vinted never publishes sale prices. This actor gives you the closest observable number: the last asking price before the listing vanished, in `lastPriceCents`. For a niche you watch continuously, that is a usable sold-comps series.

**How does the detection actually work?** Every run takes a snapshot of your search and diffs it against the tracked listings in the state store. A listing missing for `absenceThreshold` consecutive runs becomes one `likely_sold` event; a listing that reappears has its counter reset to zero.

**Can a detection be a false positive?** Yes, and the actor is honest about it: a listing the seller deleted or relisted is indistinguishable from a sale from the outside. That is why the status is `likely_sold`. In an active niche most disappearances are sales, but treat single events as signals and the aggregate as the data.

**Why does the item URL return a 404?** Because the item is gone from Vinted — which is precisely why it was detected. The event preserves what the live page no longer can: title, brand, size, condition and the last price.

**Why did my first run produce no sold events?** By design. The first run has nothing to compare against, so it seeds the baseline and reports "tracking N live listings — sold detections start next run". Detections begin from the second scheduled run.

**What does absenceThreshold do, and how should I set it?** It is the number of consecutive runs a listing must be missing before it counts as sold. The default `2` absorbs one-run hiccups (a temporarily hidden listing, a partial page). Set `1` for the fastest detection if you accept more noise; set it higher on noisy queries.

**Why does my query have to be narrow?** One snapshot covers at most ~960 listings (10 pages). If the query matches more, listings that merely fell out of the visible window look like disappearances and would pollute the detections. The run warns when coverage is incomplete; narrow with a `priceMin`/`priceMax` band until the warning goes away.

**Can I watch several queries at once?** Yes — one Schedule per query, each with its own `stateStoreName`. The state store is the watch's memory; two queries sharing one store would corrupt each other's tracking.

**How fast are sales detected?** Within roughly `absenceThreshold` × your schedule interval. Hourly runs at the default threshold of 2 detect within about 2 hours; daily runs within about 2 days. Faster niches deserve faster schedules.

**Is lastPriceCents the actual sale price?** It is the last asking price this actor observed. If the seller lowered the price before selling, the event carries that final price — the closest public proxy for the sale price, since Vinted never reveals the transaction amount. Buyer-side offers below asking are invisible from outside.

**What happens if a seller relists an item?** The relisted copy has a new listing ID, so the old ID still counts as disappeared (one `likely_sold` event) and the new one enters tracking as a fresh listing. This is one of the known false-positive sources.

**I changed my search query — do I need to do anything?** Yes: a different query is a different watch. Either give it a new `stateStoreName` or set `resetState: true` once, so old tracked listings don't get reported as sold just because the new query no longer returns them.

**How do I schedule it?** In the Apify Console, open the actor, save your input, and create a Schedule (hourly or daily) from the Schedules tab — or let the AI-assistant prompt above walk you through it. Scheduling is not optional garnish here; it is how the actor works.

**Can I export the sold events to Excel or CSV?** Yes — each run's dataset exports as CSV, Excel, JSON or XML from the Apify Console or API. For one accumulating spreadsheet, pull each run's items via the API into your own sheet, or connect Make/Zapier/n8n to the run-succeeded webhook.

**How much does it cost?** $1.99 per 1,000 sold events delivered. Runs that detect nothing — including the baseline run — deliver nothing and cost nothing beyond the small actor-start event. You pay for answers, not for watching.

**Does it collect seller data?** No, structurally. The output is built from a fixed whitelist of item fields — the same mechanism as the Vinted Scraper — and seller usernames, IDs and profile links are not on it. There is nothing to switch off because it was never collected.

**Do I need residential proxies or a Vinted account?** Neither. Standard datacenter proxies through the default Apify proxy setting are sufficient, and the actor reads openly served pages — the only account involved is your Apify account.

**Can I measure how fast things sell?** Yes — every event carries `daysListed`, the days between first observation and detection. Averaged over a niche, it is a direct sell-through-speed metric; note it undercounts for listings that were already live when your watch started.

### Related scrapers

The same clean-output guarantee, on the same marketplace and beyond:

- [Vinted Scraper](https://apify.com/lowlanddata/vinted-scraper) — the parent: live listings with prices, fees, brands and demand signals across 23 country sites.
- [Vinted Arbitrage Scanner](https://apify.com/lowlanddata/vinted-arbitrage-scanner) — the same item priced across Vinted's country sites, to spot cross-border spreads.
- [Poshmark Sold Comps](https://apify.com/lowlanddata/poshmark-sold-comps) — actual sold listings from Poshmark, the US counterpart of this signal.

### Troubleshooting

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

- **"priceMin must not be higher than priceMax."** — swap the two values.
- **Coverage warning ("narrow the query with a price band")** — the query matches more than one snapshot covers, so detections are unreliable. Add or tighten `priceMin`/`priceMax`, or split the niche into two watches with their own state stores.
- **"tracking N live listings — sold detections start next run"** — not an error: the baseline run. Schedule the actor and detections begin on the next run.
- **A flood of sold events after changing the query or a long schedule gap** — the diff is reporting everything the new snapshot no longer contains. Set `resetState: true` once to re-seed, then resume the schedule.
- **Blocked run** — the run fails honestly without touching the watch state, so no false detections; the next scheduled run continues where the last good one left off.

### 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.

Working well for you? A **rating on this page** takes ten seconds and helps other resellers find real sold data on Vinted — it is also the clearest signal of what we should build next.

# Actor input Schema

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

The market slice to watch, exactly as you would type it in Vinted's search box, e.g. "nike air max 90". Keep it narrow enough to fit one snapshot (~960 listings) - the run warns when it does not.

## `domain` (type: `string`):

Which national Vinted site to watch.

## `priceMin` (type: `integer`):

Watch only listings at or above this price (site currency) - the standard way to narrow a broad query.

## `priceMax` (type: `integer`):

Watch only listings at or below this price (site currency).

## `absenceThreshold` (type: `integer`):

How many consecutive runs a listing must be missing before it counts as likely sold. 2 (default) absorbs single-run hiccups; 1 detects faster with more noise.

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

Catalog pages per snapshot (1-10; the site serves at most 10). Lower is cheaper but covers less of the query.

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

Named store carrying the tracked-listings memory. Use a different name per watched query to keep detections independent.

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

Forget the tracked listings and re-seed the baseline on this run.

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

Upper bound on sold detections delivered per run.

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

Proxy settings. Keep Apify proxy enabled.

## Actor input object example

```json
{
  "searchQuery": "nike air max 90",
  "domain": "fr",
  "absenceThreshold": 2,
  "maxPages": 10,
  "stateStoreName": "vinted-sold-detector-state",
  "resetState": false,
  "maxItems": 500,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `sold` (type: `string`):

Likely-sold listings, one JSON object per detection.

# 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": "nike air max 90",
    "domain": "fr",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("lowlanddata/vinted-sold-detector").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": "nike air max 90",
    "domain": "fr",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("lowlanddata/vinted-sold-detector").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": "nike air max 90",
  "domain": "fr",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call lowlanddata/vinted-sold-detector --silent --output-dataset

```

## MCP server setup

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

```

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/ufJAEo68xZ51dqDIk/builds/DwNaMFxxa4iFvnGzY/openapi.json
