# Vinted Scraper — Listings, Prices & Seller Data (`vonsensey/vinted-scraper-api`) Actor

Scrape Vinted listings, prices, sellers and reviews across 27 country markets. Goes past Vinted's hard 960-item search cap and reports exactly how complete each run was. Export to CSV, Excel, JSON or API.

- **URL**: https://apify.com/vonsensey/vinted-scraper-api.md
- **Developed by:** [Blackcube Agency AB](https://apify.com/vonsensey) (community)
- **Categories:** E-commerce, Developer tools, Integrations
- **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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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 Scraper — Listings, Prices & Seller Data

Scrape **Vinted** listings, prices, sellers and reviews across **27 country markets**, and get past the hard **960-item cap** that silently truncates every other Vinted tool.

Paste a Vinted URL or type a keyword. Get clean JSON, CSV or Excel — with a per-query report telling you exactly how complete the result was.

- **Past the 960-item cap** — 6.4× more items than any single Vinted query can return, measured.
- **Coverage you can audit** — every run says what it reached and what is genuinely unreachable.
- **`condition` in English on every market** — Vinted returns `Très bon état` / `Sehr gut` / `Bardzo dobry`; a multi-market dataset is unfilterable without this, and no other Vinted tool does it.
- **Listings, sellers, closets and reviews** in one actor, with the two real sale signals the API leaks.

> **Failed inputs are free.** A row that could not be scraped is written to the dataset with a reason and is never charged. If a run returns nothing, it costs nothing.

*Unofficial and not affiliated with, endorsed by, or connected to Vinted. This tool reads only publicly visible marketplace data.*

### How this compares

| | This actor | Typical Vinted actor |
|---|:---:|:---:|
| More than 960 items from a single query | ✅ | ❌ |
| Reports how complete each query actually was | ✅ | ❌ |
| Separates "we stopped at your limit" from "Vinted cannot reach it" | ✅ | ❌ |
| Any number of markets in one run | ✅ | ❌ |
| Item condition in English on every market | ✅ | ❌ |
| Seller's completed-sales count next to their live listings | ✅ | ❌ |
| Marks Vinted's automatic review entries, so cancellations aren't read as sales | ✅ | ❌ |
| No actor-start fee — a run that returns nothing costs nothing | ✅ | ❌ |

<sub>Compared against the 12 largest Vinted actors on Apify Store on 2026-08-13, read from their public input
and dataset schemas rather than their marketing. "Typical" means the clear majority, not every one: a couple of
recent entrants do attempt to break the item cap, and one supports up to five countries per run. Every ✅ above is
something this actor is measured doing — the numbers behind each are in the sections below.</sub>

***

### The 960-item cap, and why your current export is incomplete

Vinted's catalog API returns **96 items per page and stops at page 10**. Page 11 answers `HTTP 400`. So **any single Vinted search returns at most 960 items**, however much stock actually matches.

Worse, the API reports `total_entries: 960` for *every* saturated query — so a tool that trusts that number tells you it found "960 of 960" and looks complete when it is not.

We measured it. Every one of these broad queries reports exactly 960:

| Query | `total_entries` reported |
|---|---|
| `nike` | 960 |
| `zara` | 960 |
| `dress` | 960 |
| `a` | 960 |

You will see it claimed that this field is simply a display constant with no meaning. It is not, and that matters: narrow queries report a **true** count, which is exactly what makes the cap detectable and the fix possible.

| Query | `total_entries` reported |
|---|---|
| `supreme box logo hoodie red` | 298 |
| `chanel haute couture vintage 1987` | 4 |
| `zxcvbnmqwerty` | 1 |

**Deep mode** uses that: it splits a saturated query into price bands, checks each one, and keeps splitting until every band fits under the cap.

Measured on `nike` / vinted.fr on 2026-08-13:

| | Result |
|---|---|
| Distinct items collected | **6,125** |
| What one Vinted query can ever return | 960 |
| Multiplier | **6.4×** |
| Requests used | 129 (0.021 per item) |
| Duplicates in output | 0 |

That run stopped because it hit its 6,000-item target — **not** because the data ran out. It had covered only €1.00–€2.20 of the price range with 11 bands still queued, so 6.4× is a floor, not a ceiling.

#### It tells you when it did not get everything

Every run writes a `RUN_SUMMARY` record with per-query coverage, and the log says so plainly:

```
"nike" (fr): returned 2501 items but the result set was not exhausted
  - 17 price band(s) were left unwalked because maxItems was reached - raise it for more.
```

Three different things are reported separately, because conflating them is how tools mislead you:

- **`unexploredBands`** — reachable; the run stopped at a budget. `stopReason` says which: `targetItems` means raise `maxItems`, `probeBudget` means narrow the query or set a price range.
- **`truncatedBands`** — genuinely unreachable; more than 960 items share one exact price, so no price split can separate them. An honest limit, stated rather than hidden.
- **`failedBands`** — a price range whose size could not be read because the request failed. Those items are unaccounted for, and the run says so instead of counting the band as empty.

***

### How to scrape Vinted search results

Type keywords, pick markets, run.

```json
{
  "searchQueries": ["nike air max", "adidas samba"],
  "markets": ["fr"],
  "maxItems": 500
}
```

For a complete pull of a large query, turn on deep mode and raise the ceiling:

```json
{
  "searchQueries": ["nike"],
  "markets": ["fr"],
  "deep": true,
  "maxItems": 20000
}
```

### How to scrape a Vinted URL

Paste any Vinted URL — filtered catalog, item, or member page. Filters in the URL are carried over, and the country comes from the domain.

```json
{
  "startUrls": [
    "https://www.vinted.fr/catalog?search_text=levis&price_to=25&brand_ids=7",
    "https://www.vinted.pl/items/9651715467-nike-air-max-720",
    "https://www.vinted.de/member/3175818454-someseller"
  ]
}
```

### How to scrape a Vinted seller's closet and reviews

```json
{
  "startUrls": ["https://www.vinted.fr/member/3175818454-someseller"],
  "includeSellerProfile": true,
  "includeSellerReviews": true,
  "maxReviewsPerSeller": 50
}
```

Returns the seller's profile, their listings, and the reviews buyers left — three row types most Vinted tools do not return at all.

**The two real sale signals.** Vinted has no sold-listings search, but two fields leak actual transactions:

- `soldItemCount` on a seller row is their lifetime completed sales, next to `itemCount` (still listed). Together they are a genuine sell-through ratio.
- Each review carries `itemId` and `createdAt` — a review only exists after a transaction on that item. **Check `isAutomatic` first:** Vinted posts system feedback for cancellations too (`"Évaluation automatique : l'acheteur a annulé la transaction"`), so only `isAutomatic: false` rows evidence a completed sale.

### How to scrape Vinted item descriptions

Descriptions are not in the search response. Turn on `includeItemDetail` and each item gets one extra fetch:

```json
{ "searchQueries": ["carhartt jacket"], "includeItemDetail": true, "maxItems": 50 }
```

***

### Output

One row per listing. Every field is either a real value or `null` — never a guess.

```json
{
  "type": "listing",
  "id": "9650298818",
  "url": "https://www.vinted.fr/items/9650298818-nike-air-max-blanche",
  "title": "Nike air max blanche",
  "brand": "Nike Air",
  "size": "44",
  "condition": "New with tags",
  "price": 64,
  "currency": "EUR",
  "buyerProtectionFee": 3.9,
  "totalPrice": 67.9,
  "favourites": 5,
  "views": 0,
  "photoCount": 7,
  "photos": ["https://images1.vinted.net/t/05_0209f_.../f800/1786616038.jpeg"],
  "listedAt": "2026-08-13T10:13:58.000Z",
  "sellerId": "3168303451",
  "sellerLogin": "arkalimal",
  "sellerUrl": "https://www.vinted.fr/member/3168303451-arkalimal",
  "sellerIsBusiness": false,
  "isPromoted": false,
  "market": "fr",
  "description": null,
  "color": null,
  "scrapedAt": "2026-08-13T13:28:29.908Z"
}
```

Three details worth knowing:

- **`condition` is in English on every market.** Vinted localizes it (`Très bon état`, `Sehr gut`, `Bardzo dobry`), which makes a multi-market dataset impossible to filter. The session requests English, so you get `Very good` everywhere.
- **`totalPrice` is what the buyer actually pays** — `price` plus Vinted's Buyer Protection fee. Comparing raw `price` across listings understates the real cost.
- **`listedAt` is an approximate listing date.** Vinted's search response has no creation timestamp at all; this is the upload time of the item's first photo, which is the closest proxy the API exposes. Accurate to the day, not the second.

***

### Which markets exist, and the one thing nobody tells you about them

27 markets, verified live on 2026-08-13 — the domain resolved, returned HTTP 200, and yielded listings:

`fr` `de` `uk` `us` `pl` `lt` `es` `it` `nl` `be` `cz` `sk` `at` `lu` `pt` `se` `dk` `fi` `ro` `hu` `gr` `ie` `hr` `si` `lv` `ee` `au`

Currencies observed: EUR, GBP, USD, PLN, CZK, SEK, DKK, RON, HUF, AUD.

Not included, and why: `vinted.no`, `vinted.ch` and `vinted.com.tr` do not resolve; `vinted.ca` returns HTTP 403; **`vinted.bg` serves the Swedish catalog in SEK** (98% of its listings are identical to `vinted.se`, versus 0% overlap with `vinted.fr`), so it is not a Bulgarian market and is excluded.

#### Vinted is one catalog, not 27 marketplaces

This matters for your bill, so here is the measurement rather than the marketing.

| Comparison | Common item IDs | Same price *and* currency |
|---|---|---|
| `nl` vs `be` | 91 | **91** |
| `fr` vs `de` | 68 | **68** |

Identical listings, identical prices. And where the currency differs, the price is a straight conversion:

| Item | Sweden | Denmark |
|---|---|---|
| `9651853828` | 285.42 SEK | 193.28 DKK |
| `9651868165` | 181.63 SEK | 123.00 DKK |

Median implied rate 0.6772 across 93 shared items — the actual SEK→DKK rate, with a 2.4% spread that is rounding.

**Two consequences:**

1. **Adding EU markets multiplies your cost far more than it adds unique listings.** Scrape `fr` and you have most of what `de`, `nl` and `be` would return. We put this in the input schema too, rather than selling you a bigger number.
2. **A "cross-country price gap" computed on the same item ID is an FX artifact, not arbitrage.** Any tool comparing raw numbers across markets without normalizing currency reports noise as opportunity.

   To be precise about what this does and does not say: cross-border reselling is a real business. What is not real is a *same-listing* price gap. Genuine dispersion is **within** a market (two sellers pricing the same model differently) and **across platforms** (Vinted versus eBay sold). Both need the full price distribution for a query — which is what deep mode exists to give you, and what a 960-item sample cannot.

***

### What this actor does not do, and why

These are commonly advertised. We could not make them work against the public API, so we do not ship them rather than shipping something that returns nothing.

- **A searchable sold-listings feed.** Vinted deletes sold listings from search and exposes no sold flag, so there is no sold-price history to scrape from the public API. We checked the endpoints this actor uses and found no sold filter that returns data. What this actor *does* give you is the two genuine sale signals the API leaks, both documented below rather than dressed up as a sold feed: a seller's lifetime `soldItemCount`, and per-review `itemId` + timestamp.
- **Cross-country arbitrage signals.** See the measurement above: within the shared EU pool the same item is the same listing at a converted price. We return `price`, `currency` and `totalPrice` so you can normalize deliberately, rather than shipping a gap number that is mostly FX.
- **Item measurements and full attribute sets.** Not exposed by the endpoints this actor uses.

***

### Using it from Python

There are good open-source Vinted wrappers. They break when Vinted changes its session handling or tightens Datadome, and you maintain that yourself. If you would rather not:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("vonsensey/vinted-scraper-api").call(run_input={
    "searchQueries": ["nike air max"],
    "markets": ["fr"],
    "deep": True,
    "maxItems": 5000,
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], item["price"], item["currency"])
```

Node.js, direct HTTP, CSV/Excel download, webhooks and scheduling all work the same way — the dataset is a standard Apify dataset.

***

### Notes on reliability

**Residential proxy is required, and it is the default.** Here is the measurement rather than a vague warning: Vinted is fronted by Datadome, and from an Apify datacenter IP the very first API call is answered `HTTP 403` — with browser-shaped TLS and Chrome headers, not just with a plain HTTP client. The identical build through Apify Proxy `RESIDENTIAL` returns data on the first try.

| Configuration (run on Apify, 2026-08-13) | Result |
|---|---|
| Datacenter IP, plain `fetch` | HTTP 403, 0 rows |
| Datacenter IP, browser TLS + Chrome headers | HTTP 403, 0 rows |
| **Apify Proxy `RESIDENTIAL`** | **20 rows, first request, no retries** |

That is our measurement on Apify's infrastructure, and it is why the residential default is set for you — you do not need to configure anything. If you run this code somewhere else, measure it there before assuming a proxy is optional.

- **A blocked run costs you nothing.** Blocked requests produce free error rows, never charges. The 403 runs above billed $0.00.
- **Blocks are detected by content, not status code.** An `HTTP 200` carrying a Datadome interstitial is treated as a block and the session is rebuilt, rather than being recorded as zero results.
- **Migration-safe.** Apify restarts containers routinely. Delivered rows are checkpointed, so a resumed run does not re-deliver or re-charge them.

### What a run costs

Pricing is per event. Rows that arrive in bulk from one request are cheap; rows that need their own request cost more.

| Event | What it is | FREE tier | Volume tiers down to |
|---|---|---|---|
| `listing` | A listing row, arriving 96 per request | $0.0030 | $0.0012 |
| `seller-review` | A review row, also arriving in bulk | $0.0030 | $0.0012 |
| `seller` | A seller profile — its own request | $0.0060 | $0.0024 |
| `item-detail` | A listing **upgraded** with its description | $0.0200 | $0.0140 |

**Why `item-detail` is the expensive one.** Vinted publishes descriptions only on the item page, never in the search API, and that page is ~350 KB over a residential proxy — roughly 70× the bandwidth of a listing row. We measured it at **$5.50 per 1,000 rows** to serve, against **$0.071 per 1,000** for plain listings. The price reflects that honestly rather than burying it. Leave `includeItemDetail` off unless you need descriptions.

**There is no actor-start fee**, so a run that returns nothing costs nothing — that promise only works without one.

`item-detail` **replaces** the `listing` charge for that row, it is not added on top. You are never billed twice for one row.

Deep mode costs nothing extra. It is the product, not an upsell.

### Input reference

| Field | Type | Default | What it does |
|---|---|---|---|
| `searchQueries` | array | `["nike"]` | Keywords, run against every selected market |
| `startUrls` | array | — | Any Vinted catalog, item or member URL |
| `markets` | array | `["fr"]` | Country markets for keyword searches |
| `maxItems` | integer | `100` | Hard ceiling on billable rows. `0` = unlimited |
| `deep` | boolean | `false` | Split past the 960-item cap |
| `priceMin` / `priceMax` | integer | — | Price filter, in the market's currency |
| `sortBy` | enum | `relevance` | `relevance`, `newest_first`, `price_low_to_high`, `price_high_to_low` |
| `includeItemDetail` | boolean | `false` | Fetch descriptions and colour |
| `includeSellerProfile` | boolean | `false` | Emit seller rows for member URLs |
| `includeSellerReviews` | boolean | `false` | Emit review rows for member URLs |
| `maxReviewsPerSeller` | integer | `25` | Cap on reviews per seller |
| `maxConcurrency` | integer | `5` | Targets in flight at once |
| `requestDelayMs` | integer | `250` | Pause between requests per market |

### Legal

Reads only publicly visible Vinted pages — no login, no private data, no personal data behind authentication. You are responsible for how you use the output, including GDPR obligations where seller information counts as personal data. Not affiliated with Vinted.

# Actor input Schema

## `searchQueries` (type: `array`):

Keywords to search for. Each keyword is run against every market you select. Leave empty if you are pasting URLs instead.

## `startUrls` (type: `array`):

Paste any Vinted URL and it is handled automatically: a filtered catalog or search URL (its filters are carried over), an item URL (/items/123...), or a member URL (/member/123...). The country is taken from the domain, so a vinted.pl URL is scraped on the Polish market.

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

Which country markets to search. Applies to keywords only; pasted URLs keep their own domain. Note that most European markets share a largely common inventory pool with prices converted per currency, so adding more EU markets multiplies cost far more than it adds unique listings. See the README for the measured overlap.

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

Hard ceiling on billable rows for the whole run. Set 0 for no limit. This is the single most important cost control, especially with Deep mode on.

## `deep` (type: `boolean`):

A single Vinted search returns at most 960 items (96 per page, 10 pages, then HTTP 400) no matter how much stock matches. Deep mode splits the query into price bands so it can reach far more, and reports exactly how complete the result was. Costs extra requests, so raise Max items to make use of it.

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

Only return items at or above this price.

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

Only return items at or below this price.

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

Result ordering. Newest first is the right choice for monitoring; relevance is best for research. Note: with Deep mode on, price bands are walked from cheapest upward, so a run that stops at Max items returns the cheapest matches regardless of this setting. Turn Deep mode off if ordering matters more than coverage.

## `includeItemDetail` (type: `boolean`):

Fetch each item's full description and colour. Vinted only exposes these on the item page, which is a ~350 KB fetch per item through a residential proxy - roughly 70x the bandwidth of a listing row. It is billed as the item-detail event (about 7x a listing) and that charge REPLACES the listing charge for the row rather than adding to it. Leave it off unless you actually need descriptions.

## `includeSellerProfile` (type: `boolean`):

For member URLs, also emit a seller row with feedback counts, reputation, location and item count.

## `includeSellerReviews` (type: `boolean`):

For member URLs, also emit the reviews buyers left on that seller.

## `maxReviewsPerSeller` (type: `integer`):

Caps how many reviews are pulled per seller.

## `maxConcurrency` (type: `integer`):

How many targets to work on at once. Lower this if you see blocking.

## `requestDelayMs` (type: `integer`):

Pause between requests on the same market. Raising it trades speed for a lower chance of being throttled.

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

Vinted is fronted by Datadome, which blocks Apify's datacenter IP ranges with HTTP 403 on the first API call. Residential proxy is required for this actor to return data and is the default. If you change this, expect the run to return zero rows (which costs you nothing - blocked requests are never charged).

## Actor input object example

```json
{
  "searchQueries": [
    "nike"
  ],
  "markets": [
    "fr"
  ],
  "maxItems": 100,
  "deep": false,
  "sortBy": "relevance",
  "includeItemDetail": false,
  "includeSellerProfile": false,
  "includeSellerReviews": false,
  "maxReviewsPerSeller": 25,
  "maxConcurrency": 5,
  "requestDelayMs": 250,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `listings` (type: `string`):

No description

## `runSummary` (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 = {
    "searchQueries": [
        "nike"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("vonsensey/vinted-scraper-api").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 = { "searchQueries": ["nike"] }

# Run the Actor and wait for it to finish
run = client.actor("vonsensey/vinted-scraper-api").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 '{
  "searchQueries": [
    "nike"
  ]
}' |
apify call vonsensey/vinted-scraper-api --silent --output-dataset

```

## MCP server setup

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

```

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/vYMeJCbHvr7ZZhHcg/builds/jtw1t8XMq3nJz6hm1/openapi.json
