# Vinted France Scraper - Clothes, Prices, Sellers & Reviews (`abotapi/vinted-fr-scraper`) Actor

Scrape Vinted.fr second-hand fashion listings by keyword, brand, price, condition, sort filters or URL. Extract titles, brands, prices, sizes, conditions, photos and seller ratings. Supports item and seller URLs, plus full seller feedback and review history.

- **URL**: https://apify.com/abotapi/vinted-fr-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 listing 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?

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.fr Scraper: Listings, Item Detail & Seller Reviews

Vinted.fr Scraper turns Vinted.fr, France's C2C secondhand fashion marketplace, into structured listing and seller data. Search by keyword with brand, price, condition and sort filters, paste any item or seller/member link directly, or pull a seller's complete feedback history. Every listing carries identity, price, brand and photos out of the box, with full description, condition, size, material, colour and seller reputation available on request. Export to JSON, CSV or Excel, or read results straight through the API.

### Why This Scraper?

- **Three ways in.** Keyword/brand/category search with real source-side filters, paste any item or seller/member URL directly, or pull a seller's full feedback history.
- **Real search filters, not guesswork.** Brand id, price range, condition and sort order map directly to Vinted.fr's own search parameters, verified to narrow results rather than being accepted and silently ignored.
- **Full item detail from the item's own page.** Condition, size, material, colour, category and full description, read from the listing's own structured data and on-page attributes, not a third-party estimate.
- **Honest pricing.** Vinted.fr shows a second, larger figure next to the listed price ("buyer protection included"), which is the current price plus a checkout fee, never a former price. This scraper reports only the real listed price and does not invent a wasPrice or discount field the marketplace itself doesn't expose.
- **Native seller feedback.** Overall rating, feedback count and every individual review (reviewer, date, star rating, comment) sourced from Vinted.fr's own feedback system.
- **Built for schedules.** Incremental mode returns only new, changed and reappeared listings on recurring runs; because a sold listing on a C2C marketplace is gone for good, a sold or removed listing comes back marked EXPIRED once a full scan confirms it.
- **Resume without re-billing.** Continue one interrupted run from its own previous dataset without re-collecting or re-charging what it already delivered.

### Use Cases

- **Resale price research:** track a keyword or brand across the marketplace to benchmark asking prices, sizes and condition mix.
- **Deal and restock alerts:** schedule a search with incremental mode on and get notified only about new or changed listings.
- **Seller due diligence:** pull a seller's profile and complete feedback history before buying from or dealing with them.
- **Marketplace analytics:** build a dataset of brand, price and condition trends for a category or set of keywords.
- **Storefront monitoring:** watch one seller's whole listing catalogue for new items through URL mode's member enumeration.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live listing.

| Field | Example |
| --- | --- |
| `itemId` | `"00000001"` |
| `title` | `"Sample knitted jumper"` |
| `url` | `"https://www.vinted.fr/items/00000001-sample-knitted-jumper"` |
| `brandTitle` | `"Sample Brand"` |
| `priceAmount` / `currency` | `15.0` / `"EUR"` |
| `promoted` | `false` |
| `isVisible` | `true` |
| `photos` | `[{"id": 1, "url": "https://images.example.com/000001.jpeg", "isMain": true}]` |
| `seller` | `{"id": "00000002", "username": "sample_seller", "business": false}` (search results leave `username` empty until **Fetch item detail** fills it) |
| `mainImage` *(fetchDetails)* | `"https://images.example.com/000001.jpeg"` |
| `description` *(fetchDetails)* | `"Full listing description text appears here when present."` |
| `condition` *(fetchDetails)* | `"Bon état"` |
| `conditionSchema` *(fetchDetails)* | `"UsedCondition"` |
| `size` *(fetchDetails)* | `"M"` |
| `material` *(fetchDetails)* | `"Coton"` |
| `color` *(fetchDetails)* | `"Bleu"` |
| `categoryPath` *(fetchDetails)* | `"Femmes Vêtements"` |
| `catalogId` *(fetchDetails)* | `"1242"` |
| `isReserved` *(fetchDetails)* | `false` |
| `sellerUsername` / `sellerFeedbackRating` / `sellerFeedbackCount` *(fetchDetails)* | `"sample_seller"` / `0.98` (reputation score from 0 to 1) / `120` |
| `scrapedAt` | `"2026-01-01T00:00:00+00:00"` |

**Extra fields on listings:** `totalPrice` and `serviceFee` (the buyer-protection figure shown next to the price, kept separate from the real listed `priceAmount`), `favouriteCount`, `photoCount`, `image` (the first photo), and flat seller fields `sellerId`, `sellerUrl`, `sellerPhoto` and `isBusinessSeller`. A field the source does not serve for a given row is returned empty (`null`) rather than guessed.

Listings read from a seller page (a `/member/<id>` link) also carry `viewCount`, `isClosed`, `isHeavyBulky` and `itemClosingAction`; results pages do not serve these, so they are empty on search, category and brand rows. With **Fetch item detail** on, every listing also gets `category`, `categoryId`, `brandId`, `colors` (every colour of the item) and `sellerBadges` (for example `["Pro"]`, empty for sellers without a badge). A listing whose item page could not be read is still returned from its listing card, carries a `detailError` note and is not charged the detail-enrichment event.

Detail enrichment also fills `isHidden`, `instantBuy`, `canBuy`, `brand` (id, title, slug), `sellerId` and `sellerBusiness` as flat fields; the seller's rating, feedback count and username appear both flat (matching the dataset's table view) and nested under `seller.feedbackRating` / `seller.feedbackCount` / `seller.username`, for the same values.

Reviews mode (`mode: "reviews"`) returns a different row shape instead: `reviewId`, `sellerId`, `itemId` (of the reviewed purchase, when available), `reviewerUsername`, `rating`, `body`, `isSystemFeedback`, `createdAt`, `reviewUrl`, and the seller's current `sellerFeedbackRating` / `sellerFeedbackCount` attached to every review row.

When `incrementalMode` is on, every returned record (either shape) also carries `changeType` (`NEW`, `UPDATED`, `UNCHANGED`, `REAPPEARED` or `EXPIRED`), `changedFields`, `firstSeenAt` and `lastSeenAt`.

#### Output views

The dataset has four tabs in the Console and through `?view=` in the API: **overview** (listings), **detail** (full item detail), **reviews** (seller feedback rows) and **changes** (`changeType`, `changedFields`, first and last seen, for Incremental runs).

### How to Use

1. Pick a **mode**: `search` (keyword/brand/category), `url` (paste item or seller/member links), or `reviews` (a seller's full feedback history).
2. Fill in the fields for that mode, and turn on **Fetch item detail** if you want full description, condition, size, material, colour and seller reputation.
3. Set **Max items total** (and optionally **Max pages per search**) to control run size and cost, then click **Start**.
4. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Basic keyword search:**

```json
{
  "mode": "search",
  "queries": ["sample keyword"],
  "maxItems": 20
}
```

**Search with brand, price range, condition and sort:**

```json
{
  "mode": "search",
  "queries": ["sample keyword"],
  "brandId": "53",
  "minPrice": 5,
  "maxPrice": 60,
  "conditions": ["3", "4"],
  "sortBy": "price_low_to_high",
  "fetchDetails": true,
  "maxItems": 20
}
```

**Paste item and seller/member links:**

```json
{
  "mode": "url",
  "urls": [
    "https://www.vinted.fr/items/00000001-sample-listing-title",
    "https://www.vinted.fr/member/00000002"
  ],
  "maxItems": 20
}
```

**URL mode: supported link shapes.** The seller, item, search, category and brand shapes below were fetched live through the Apify proxy (2026-10-27) and each returned its real content; `m.` links are accepted and mapped onto the main site but were not fetched under their own host. If you run URL mode with the URLs field untouched, it now scrapes one example of each distinct shape (a seller page, an item page, a keyword search page, a filtered search page, a sub-category page, a top-level category page and a brand page) instead of returning nothing. Clear the field or paste your own links to change that. A link in any other shape is skipped with a warning in the log, never silently.

| Link shape | Example | What you get |
| --- | --- | --- |
| Item page | `vinted.fr/items/<id>-<slug>` | That one listing, with full detail |
| Seller page | `vinted.fr/member/<id>` | Every listing the seller has |
| Search page | `vinted.fr/catalog?search_text=nike` | Search results; `search_text`, `catalog[]`, `brand_ids[]`, `status_ids[]`, `color_ids[]`, `size_ids[]`, `material_ids[]`, `price_from`, `price_to` and `order` are honoured. Any other filter parameter on the link is not applied, and the log names it in a warning for that link |
| Category page | `vinted.fr/catalog/<id>-<name>` | Listings in that category |
| Brand page | `vinted.fr/brand/<id>-<name>` | Listings of that brand |
| Mobile and tracked links | `m.vinted.fr/...`, extra `?utm_...` parameters | Treated like the same link on the main site; tracking parameters are ignored |

The default URLs value is one example of each of the seller, item, keyword search, filtered search, sub-category, top-level category and brand shapes. The `m.` host form is accepted but is not part of that default. Four more link shapes are accepted and are covered by a default entry, because they resolve to the same target (the page number is ignored and listings dedupe by id), so they are not repeated there:

| Accepted link shape | Covered by the default entry |
| --- | --- |
| Seller link with a username slug (`vinted.fr/member/<id>-<username>`) | Seller page (same seller id) |
| Item link with the id only (`vinted.fr/items/<id>`) | Item page (same item id) |
| Item link on the `vinted.com` host | Item page (same item id) |
| Search link with a page number (`...&page=2`) | Keyword search page (the page number is ignored) |

The default only applies in URL mode; in search and reviews mode an untouched URLs field is ignored and does not change your Incremental state.

A `page=N` parameter is ignored: results always start from the first page and are bounded by Max pages per search. In search mode, Brand and Category accept a plain name (for example `Nike` or `Robes`; a shared name such as `Baskets` can be written as `Hommes > Chaussures > Baskets`) or a numeric id; an unknown brand, or a category name shared by several categories, stops the run with a message that says what to enter.

**Pull a seller's full feedback history:**

```json
{
  "mode": "reviews",
  "sellers": ["00000002"],
  "maxItems": 100
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/vinted-fr-scraper").call(run_input={"mode": "search", "queries": ["sample keyword"], "maxItems": 20})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], item["priceAmount"], item["currency"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/vinted-fr-scraper').call({ mode: 'search', queries: ['sample keyword'], maxItems: 20 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### How Max items and Max pages per search shape a run

The item budget (**Max items total**) splits evenly across every query, member URL or seller in one run. **Max pages per search** additionally caps how many result pages are walked per entry; leave it at 0 to walk as many pages as needed, bounded by Max items and a 1000-page cap.

A search run with more than one keyword that gets capped by its per-keyword share of Max items partway through a result page keeps the rest of that page marked as already seen internally, even though those listings were never returned. If a later keyword in the same run matches any of those listings, they are silently skipped there as well, so they are returned by neither keyword and the run can end with fewer rows than Max items. Keep Max items high enough to clear a full page for each keyword (or use 0 for the 100,000 maximum, still bounded by Max pages per search) when several overlapping keywords run together, or split heavily-overlapping keywords into separate runs.

In Incremental mode specifically, the per-target budget of a keyword search, a seller's listing walk or a seller's review walk is drawn down as records are read, not only as they are actually returned; an unchanged, suppressed record still counts against the budget. On a recurring run where most previously-tracked records haven't changed, a walk can stop once it has read its share of the budget even though far fewer (or zero) new or changed rows were returned, while newer records further down the results stay unread; the run then reports that nothing changed. While the result order stays the same, later runs read the same first records and can keep missing those deeper changes. Raise Max items (up to the 100,000 maximum) above the size of the tracked scope on monitoring runs, and for searches consider sorting by newest first so fresh listings sit at the top of the walk.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one interrupted run: paste its run or dataset ID and the actor skips everything already collected there, so you don't pay twice.
- **Incremental mode** (`incrementalMode`) is for scheduled runs over the same scope. A listing's favourite and view counts, the fee figures (`serviceFee`, `totalPrice`), the `promoted` flag and the seller's running feedback tally still appear in every row but never make a record `UPDATED` on their own, so a tracked listing is not returned (or billed) again just because someone favourited it. Each record is classified `NEW`, `UPDATED` (with `changedFields`), `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on), `REAPPEARED` or `EXPIRED` (only after a run that scanned the whole scope, and only with `emitExpired`). `stateKey` names or shares the stored state. With Incremental mode off, output is exactly as before.
- Turning on `emitExpired` adds a synthetic `EXPIRED` row for every tracked listing no longer found, once a run has fully scanned the tracked scope. These rows are added after Max items has already been applied to the run's freshly scraped listings, so a monitoring run with a large number of sold or removed listings can return, and be billed for, more rows in total than Max items specifies. If a hard per-run cap matters more than complete expiry tracking on a given run, leave `emitExpired` off, or expect the occasional oversized run when tracking a scope that turns over heavily.
- Combining `resumeFromRunId` with Incremental mode, on a state key with no prior incremental baseline, folds every id the resumed run already delivered (listings, or reviews in reviews mode) into the new baseline as a bare placeholder (marked present, with nothing recorded to compare against yet). The first later ordinary run (without Resume) that reads each of those records then has nothing on file to diff beyond presence, so it returns, and bills, every one of them as `UPDATED` with every field listed in `changedFields`, even when nothing on the marketplace actually changed. This happens once per record, on the first run that reads it after the bootstrap (usually the next run, unless Max items stops that run before reaching it); treat those `UPDATED` rows as informational rather than a real change signal.
- A manually set `stateKey` shared by runs with different searches, URLs, sellers or modes merges them into one tracked scope. With `emitExpired` on, a complete run of one configuration then marks every record tracked only by the other configuration as `EXPIRED` (and bills it), even though it was simply outside this run's scope, and it comes back as `REAPPEARED` when the other configuration runs again. Leave `stateKey` empty, or give each configuration its own key, unless every sharing run covers the same scope.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search`, `url`, or `reviews`. |
| `queries` | array | (none) | Free-text keywords (search mode). Item budget splits evenly across them. |
| `categoryId` | string | (none) | Vinted catalog id (search mode, optional). Narrows a keyword search, or browses the whole category when Search keywords is left empty. |
| `brandId` | string | (none) | Vinted brand name or numeric id (search mode, optional). |
| `brandIds` | array | (none) | Extra brands as names or numeric ids, combined with `brandId`. |
| `colorIds` | array | (none) | Numeric Vinted colour ids, as in the `color_ids[]` filter of a search URL. A non-numeric entry (for example `blue`) stops the run with a message naming it. |
| `sizeIds` | array | (none) | Numeric Vinted size ids, as in the `size_ids[]` filter of a search URL. A non-numeric entry (for example `M`) stops the run with a message naming it. |
| `conditions` | array | (none) | Restrict to one or more condition levels (search mode, optional). Leave empty for any condition. |
| `minPrice` | integer | (none) | Minimum price in EUR (search mode, optional). |
| `maxPrice` | integer | (none) | Maximum price in EUR (search mode, optional). |
| `sortBy` | string | `relevance` | `relevance`, `newest_first`, `price_low_to_high`, or `price_high_to_low`. |
| `urls` | array | four sample links | Item, seller/member, search, category or brand links (url mode); see the link shapes table above. Filter fields above are ignored in this mode. |
| `sellers` | array | (none) | Numeric member ids or `/member/<id>` URLs to fetch feedback for (reviews mode). A plain username is not resolved to a member and is skipped. |
| `fetchDetails` | boolean | `true` | Adds full description, condition, size, material, colour, category, and the seller's feedback rating/count. Identity, price, brand and photos are already included without this toggle. |
| `maxPages` | integer | `0` | Stop after this many result pages per keyword/category/URL entry, or per seller's reviews. `0` means the 1000-page cap. Bounded by Max items. |
| `maxItems` | integer | `20` | Hard cap on total items (listings or reviews) returned across the whole run, and the review limit in reviews mode (split evenly across sellers). Maximum 100,000; `0` means that maximum. |
| `proxy` | object | Apify Proxy | The connection to Vinted.fr. The run observes which connection works for your account and falls back automatically; see Plan Requirement. |
| `mcpConnectors` | array | (none) | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which item pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. Does not affect the dataset. |
| `resumeFromRunId` | string | (none) | Continue one interrupted run without returning or charging for records already collected there. |
| `ignoreFieldsForChanges` | array | `[]` | Incremental mode only: field names (matched at any depth) that never make a record UPDATED; they are still returned. |
| `incrementalMode` | boolean | `false` | Turn on for daily or recurring monitoring; returns only new, changed and reappeared records on later runs. |
| `stateKey` | string | (none) | Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Auto-derived from mode/search/URL/seller/detail settings when left empty. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) records unchanged since the last run, marked `UNCHANGED`. |
| `emitExpired` | boolean | `false` | Also return (and bill) records no longer found since the last run, marked `EXPIRED`, once a run has fully scanned the tracked scope. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live listing.

```json
{
  "itemId": "00000001",
  "title": "Sample knitted jumper",
  "url": "https://www.vinted.fr/items/00000001-sample-knitted-jumper",
  "path": "/items/00000001-sample-knitted-jumper",
  "brandTitle": "Sample Brand",
  "priceAmount": 15.0,
  "currency": "EUR",
  "isVisible": true,
  "promoted": false,
  "photos": [
    { "id": 1, "url": "https://images.example.com/000001.jpeg", "width": 800, "height": 1200, "isMain": true }
  ],
  "seller": {
    "id": "00000002",
    "username": "sample_seller",
    "photo": "https://images.example.com/seller-000002.jpeg",
    "business": false,
    "feedbackRating": 0.98,
    "feedbackCount": 120
  },
  "description": "Full listing description text appears here when present.",
  "mainImage": "https://images.example.com/000001.jpeg",
  "conditionSchema": "UsedCondition",
  "condition": "Bon état",
  "size": "M",
  "material": "Coton",
  "color": "Bleu",
  "categoryPath": "Femmes Vêtements",
  "catalogId": "1242",
  "isReserved": false,
  "sellerId": "00000002",
  "sellerBusiness": false,
  "sellerFeedbackRating": 0.98,
  "sellerFeedbackCount": 120,
  "sellerUsername": "sample_seller",
  "scrapedAt": "2026-01-01T00:00:00+00:00"
}
```

### Plan Requirement

Vinted.fr sits behind an anti-bot check, so this actor needs a working proxy connection. An Apify plan with proxy access is required: free plans do not include the Apify proxy. Measured on 2026-10-27: the default Apify proxy connection (Datacenter, no group chosen) served every page shape this actor reads (search, category and brand pages, seller pages, item pages and the seller listing and feedback routes), so the default is the cost-efficient choice. The run starts a short browser session through that connection to clear the check, then reads pages over a lightweight connection. If the default connection is refused it falls back automatically to Apify Residential and then to backup connections, and remembers which one your account is allowed to use. You do not have to change anything; if runs still fail, choose the Residential group under Proxy & connection or supply your own proxy URLs.

If the run cannot read any page it stops with one of these messages instead of returning an empty dataset:

- *The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required.* Check your plan, or the credentials of your own proxy URLs.
- *No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Vinted pages.* Upgrade your plan or supply your own proxy URLs.
- *Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search.* Try again later.

On a free plan the log also prints a notice that the selected connection is unavailable and that the run continues on a backup connection, where some pages may still be unavailable.

### FAQ

#### How much does it cost?

Billing is pay per event, with three events. A start event is charged once per run. A per-record event is charged for each listing or review row returned. A detail-enrichment event is charged per returned listing whose item page was read, only while **Fetch item detail** is on (it is on by default). Records suppressed by Incremental mode, skipped as already collected by Resume, or not pushed are never charged. The **Pricing** tab shows the current rates. Use **Max items total** to cap the cost of any run, and turn off **Fetch item detail** for the cheapest listing rows.

#### Is it legal to scrape Vinted.fr?

This actor collects only publicly available marketplace listings and seller feedback. You are responsible for how you use it: follow Vinted.fr's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Do not use it to collect personal data beyond what a listing or public feedback entry itself shows.

#### Can I get only new or changed listings on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental changes for scheduled runs**. Each run then returns only new, updated and reappeared records, and unchanged ones are not billed unless you turn on **Emit unchanged records**.

#### What's the difference between Resume and Incremental mode?

**Resume from a previous run** continues one specific interrupted run so you don't re-collect or re-pay for what it already delivered; you paste its run or dataset ID once. **Incremental mode** is for recurring monitoring of the same search, URLs or sellers: the actor remembers its own state between scheduled runs, so you never paste an ID, and it keeps classifying records as new, changed, unchanged, reappeared or expired every time it runs.

#### Why did my run return fewer listings than Max items?

Two run shapes can do this. First, in a search with more than one keyword, a result page cut short by a keyword's share of Max items still marks the rest of that page as seen internally, so any of those listings that a later keyword also matches are skipped there too and returned by neither; keep Max items high enough to clear a full page per keyword, or run heavily-overlapping keywords separately. Second, in Incremental mode, the per-target budget is drawn from records read during the walk, not only from ones actually returned, so a recurring run where most records are unchanged (and suppressed) can stop at its budget having returned far fewer rows than Max items, or none, even with newer records left unread; raise Max items above the tracked scope on recurring runs (and sort searches by newest first) if you want every change picked up.

#### Why did the run right after a Resume plus Incremental bootstrap mark everything UPDATED?

Starting Incremental mode together with `resumeFromRunId` on a fresh state key seeds every id the resumed run already delivered into the new baseline without a stored snapshot to compare against. The next ordinary run (without Resume) that reads each of those records finds nothing on file to diff beyond "it exists", and returns (and bills) it as `UPDATED` with every field listed as changed. This happens once per record, normally on the very next run; treat those `UPDATED` rows as informational. Once a record has been read this way, its classification is accurate from then on.

#### Why did my run fail instead of returning an empty dataset?

If the site refuses every request, the run stops with a clear message so "no listings match" is never confused with "nothing could be read". Run it again in a few minutes. If only some pages could be read, you still get what was found.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more Vinted data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🏷️ <a href="https://apify.com/abotapi/vinted-marketplace-scraper"><b>Vinted Multi-Country Scraper</b></a><br>Scrape Vinted across 27 country marketplaces: keyword search with brand, price and...</td><td>🏷️ <a href="https://apify.com/abotapi/leboncoin-fr-scraper"><b>Leboncoin FR</b></a><br>Pull public leboncoin.fr listings across cars, property, jobs, electronics, furniture...</td></tr>
<tr><td>🏷️ <a href="https://apify.com/abotapi/thredup-scraper"><b>ThredUp Scraper</b></a><br>Scrape ThredUp resale listings by keyword, department, brand, size, condition, price or...</td><td>🏷️ <a href="https://apify.com/abotapi/bol-com-scraper"><b>bol.com Scraper</b></a><br>Scrape bol.com products: title, price, list price and discount, EAN, brand, images...</td></tr>
<tr><td>🏷️ <a href="https://apify.com/abotapi/daangn-scraper"><b>Daangn Scraper</b></a><br>Scrape Daangn (당근) marketplace listings by keyword and region. Returns 35+ fields per...</td><td>🏷️ <a href="https://apify.com/abotapi/chrono24-scraper"><b>Chrono24 Scraper</b></a><br>Scrape Chrono24 luxury watch listings by keyword, filters or URL. Extract 50+ fields...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/vinted-fr-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

'search' finds listings by keyword with Vinted's own filters. 'url' scrapes any item page or seller/member page you paste. 'reviews' pulls a seller's full feedback history (rating + every comment).

## `queries` (type: `array`):

One or more free-text keywords, e.g. 'nike air force' or 'robe vintage'. The item budget (Max items below) is split evenly across keywords.

## `categoryId` (type: `string`):

A category name (e.g. 'Robes') or a Vinted catalog id, e.g. '1242'. A name shared by several categories (e.g. 'Baskets') stops the run and lists the choices; write it as 'Hommes > Chaussures > Baskets' to pick one. Find ids in a category page URL: vinted.fr/catalog/<id>-<name>. Narrows a keyword search, or browses the whole category when Search keywords is left empty.

## `brandId` (type: `string`):

A brand name (e.g. 'Nike') or a Vinted brand id, e.g. '53'. An unknown brand stops the run with a clear message. Ids are in a brand page URL: vinted.fr/brand/<id>-<name>.

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

Only return items in these conditions. Leave empty for any condition.

## `brandIds` (type: `array`):

Extra brands as names or numeric Vinted brand ids; combined with Brand. Leave empty for any brand.

## `colorIds` (type: `array`):

Numeric Vinted colour ids (as in the color_ids\[] filter of a Vinted search URL). A non-numeric entry stops the run with a message naming it.

## `sizeIds` (type: `array`):

Numeric Vinted size ids (as in the size_ids\[] filter of a Vinted search URL). A non-numeric entry stops the run with a message naming it.

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

Only return items priced at or above this amount.

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

Only return items priced at or below this amount.

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

Order of the returned results.

## `urls` (type: `array`):

Paste Vinted links: item pages (/items/<id>-<slug>), seller pages (/member/<id>), search pages (/catalog?search_text=...), category pages (/catalog/<id>-<name>) and brand pages (/brand/<id>-<name>). Seller, search, category and brand links enumerate every listing they show. Left untouched, this scrapes one example of each distinct shape: seller, item, keyword search, filtered search, sub-category, top-level category and brand. Filter fields above are ignored in this mode.

## `sellers` (type: `array`):

Seller usernames, numeric member ids, or full /member/<id> URLs to fetch feedback for.

## `fetchDetails` (type: `boolean`):

Adds full description, condition, size, material, colour, upload date, and the seller's feedback rating/count. Identity, price, brand, and photos are already included without this toggle.

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

Stop after this many result pages per keyword/category/URL entry or per seller's reviews. 0 means the built-in hard cap of 1000 pages. Bounded by Max items below.

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

Hard cap on total items (listings or reviews) returned across the whole run. Capped at 100,000 per run; 0 means that cap.

## `proxy` (type: `object`):

Vinted.fr is served over the default Apify proxy connection (Datacenter), which stays the cost-efficient default. If that connection is refused, the run falls back automatically to Apify Residential and then to backup connections; free plans have no Apify proxy. Choose the Residential group or supply your own proxy URLs only if runs still fail.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue an interrupted crawl without returning or charging for records already collected there. For recurring daily monitoring of the same search/URLs/sellers, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns everything as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED records; a sold/removed listing on this marketplace comes back as EXPIRED. Turn on "Emit unchanged" or "Emit expired" only when you also want those records returned (and billed). State is kept separately per mode and search/URL/seller/detail setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the mode, search/URL/seller and detail settings — different searches then never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return records that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return records that were present in a previous run but are no longer found (sold or removed), marked EXPIRED. Only produced once a run has fully scanned the tracked scope — not when Max items capped it or when Resume was used. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.

## `ignoreFieldsForChanges` (type: `array`):

Output field names that should NOT make a record count as UPDATED, even when they changed. Matches a field name at any nesting depth (for example price inside nested objects). Ignored fields are still returned in the output. Photo links are already compared without their rotating host and signature. Favourite and view counts, the fee figures, the promoted flag and the seller's running feedback tally are always ignored this way.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify -> Settings -> Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON; the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "queries": [
    "nike"
  ],
  "sortBy": "relevance",
  "urls": [
    "https://www.vinted.fr/member/245246277",
    "https://www.vinted.fr/items/10549512167-nike-sportjacke",
    "https://www.vinted.fr/catalog?search_text=nike",
    "https://www.vinted.fr/catalog?search_text=nike&order=newest_first&price_from=10&price_to=50&status_ids[]=2&brand_ids[]=53&catalog[]=1242",
    "https://www.vinted.fr/catalog/1242-sneakers",
    "https://www.vinted.fr/catalog/5-mens",
    "https://www.vinted.fr/brand/53-nike"
  ],
  "fetchDetails": true,
  "maxPages": 0,
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `reviews` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `changes` (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": "search",
    "queries": [
        "nike"
    ],
    "urls": [
        "https://www.vinted.fr/member/245246277",
        "https://www.vinted.fr/items/10549512167-nike-sportjacke",
        "https://www.vinted.fr/catalog?search_text=nike",
        "https://www.vinted.fr/catalog?search_text=nike&order=newest_first&price_from=10&price_to=50&status_ids[]=2&brand_ids[]=53&catalog[]=1242",
        "https://www.vinted.fr/catalog/1242-sneakers",
        "https://www.vinted.fr/catalog/5-mens",
        "https://www.vinted.fr/brand/53-nike"
    ],
    "maxPages": 0,
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

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

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

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

```

## Python example

```python
from apify_client import ApifyClient

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

# Prepare the Actor input
run_input = {
    "mode": "search",
    "queries": ["nike"],
    "urls": [
        "https://www.vinted.fr/member/245246277",
        "https://www.vinted.fr/items/10549512167-nike-sportjacke",
        "https://www.vinted.fr/catalog?search_text=nike",
        "https://www.vinted.fr/catalog?search_text=nike&order=newest_first&price_from=10&price_to=50&status_ids[]=2&brand_ids[]=53&catalog[]=1242",
        "https://www.vinted.fr/catalog/1242-sneakers",
        "https://www.vinted.fr/catalog/5-mens",
        "https://www.vinted.fr/brand/53-nike",
    ],
    "maxPages": 0,
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

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

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

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

```

## CLI example

```bash
echo '{
  "mode": "search",
  "queries": [
    "nike"
  ],
  "urls": [
    "https://www.vinted.fr/member/245246277",
    "https://www.vinted.fr/items/10549512167-nike-sportjacke",
    "https://www.vinted.fr/catalog?search_text=nike",
    "https://www.vinted.fr/catalog?search_text=nike&order=newest_first&price_from=10&price_to=50&status_ids[]=2&brand_ids[]=53&catalog[]=1242",
    "https://www.vinted.fr/catalog/1242-sneakers",
    "https://www.vinted.fr/catalog/5-mens",
    "https://www.vinted.fr/brand/53-nike"
  ],
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/vinted-fr-scraper --silent --output-dataset

```

## MCP server setup

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

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

## OpenAPI specification

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