# Marktplaats.nl $1💰 Listings, Prices, Sellers (`abotapi/marktplaats-nl-scraper`) Actor

From $1/1K. Scrape Marktplaats.nl listings: title, price, condition, location with coordinates, all photos, seller name/type/rating/reviews, shipping, views, and full per-category attributes (cars, bikes, electronics). Search by keyword or paste URLs.

- **URL**: https://apify.com/abotapi/marktplaats-nl-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Automation, Lead generation
- **Stats:** 2 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 listing scrapeds

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

## Marktplaats.nl Scraper

Extract structured listing data from Marktplaats.nl, the largest online marketplace in the Netherlands. Search by keyword and filters or paste listing and result-page URLs, and get clean JSON for every advert: title, price, condition, location with GPS coordinates, every photo, seller name/type/rating/reviews, shipping options, view and favorite counts, and the complete per-category attribute set (cars, bikes, electronics, and more).

### Why This Scraper?

- **Complete field coverage**: 150+ fields per listing including price, condition, full description, all photos, GPS coordinates, seller profile, shipping and per-category attributes.
- **Rich automotive data**: for vehicle adverts it pulls make, model, trim, construction year, mileage, fuel, transmission, body type, power, cylinder capacity, energy label, options and more.
- **Seller insight**: seller name, private vs professional, active-since, rating and number of reviews, and payment method.
- **Two input modes**: build searches from keywords plus filters, or paste any listing or result-page URL.
- **Fast and lightweight**: runs on the free tier with the default datacenter proxy.
- **Result paging with duplicate protection**: collects result pages up to your chosen limit without returning repeats.
- **Optional detail enrichment**: toggle full detail pages on for views, favorites, shipping, bids, reviews and the full description.

### Data You Get

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

| Field | Example |
| --- | --- |
| id | m0000000000 |
| title | Koga elektrische fiets |
| priceNumeric | 399.0 |
| priceType | MIN\_BID |
| currency | EUR |
| condition | Zo goed als nieuw |
| brand | Koga |
| description | Nette elektrische fiets, inclusief oplader. |
| location\_cityName | Amsterdam |
| location\_latitude | 52.3702 |
| location\_longitude | 4.8952 |
| sellerName | Jane Doe |
| sellerType | CONSUMER |
| sellerRating | 4.9 |
| sellerReviews | 37 |
| viewCount | 73 |
| savedCount | 3 |
| mileage | 89900 |
| fuelType | Benzine |
| transmission | Automaat |
| imageUrl | https://images.marktplaats.com/api/v1/.../image.jpg |
| url | https://www.marktplaats.nl/v/.../m0000000000-koga |

**Incremental mode only.** When `incrementalMode` is on, every returned record also carries:

| Field | Description |
| --- | --- |
| changeType | `NEW` | `UPDATED` | `UNCHANGED` | `REAPPEARED` | `EXPIRED` |
| changedFields | Top-level fields that changed since last seen; non-empty only for `UPDATED` |
| firstSeenAt | When this listing was first observed by this monitoring campaign |
| lastSeenAt | When this listing was last observed |

### How to Use

**1. Keyword search with detail enrichment**

```json
{
  "mode": "search",
  "queries": ["fiets"],
  "fetchDetails": true,
  "maxListings": 50,
  "proxy": { "useApifyProxy": true }
}
```

**2. Browse a category with price and seller filters**

```json
{
  "mode": "search",
  "queries": [""],
  "categoryId": 91,
  "minPrice": 5000,
  "maxPrice": 20000,
  "sellerType": "COMMERCIAL",
  "sortBy": "PRICE",
  "sortOrder": "INCREASING",
  "maxListings": 100,
  "proxy": { "useApifyProxy": true }
}
```

**3. Search near a postcode**

```json
{
  "mode": "search",
  "queries": ["bankstel"],
  "postcode": "1011AB",
  "distanceMeters": 15000,
  "maxListings": 40,
  "proxy": { "useApifyProxy": true }
}
```

**4. URL mode (listing or result pages)**

```json
{
  "mode": "url",
  "urls": [
    "https://www.marktplaats.nl/l/auto-s/",
    "https://www.marktplaats.nl/v/fietsen-en-brommers/fietsen-dames-damesfietsen/m0000000000-koga"
  ],
  "fetchDetails": true,
  "maxListings": 60,
  "proxy": { "useApifyProxy": true }
}
```

### Input Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| mode | select | `search` (keyword + filters) or `url` (paste URLs). |
| queries | array | One or more search keywords. Empty browses a whole category. |
| categoryId | integer | Numeric category id to restrict results (e.g. 91 = Auto's). |
| sortBy | select | `OPTIMIZED`, `SORT_INDEX`, `PRICE`, `DATE`. |
| sortOrder | select | `DECREASING` or `INCREASING`. |
| minPrice / maxPrice | integer | Keep listings within this euro range. |
| sellerType | select | `any`, `CONSUMER`, or `COMMERCIAL`. |
| postcode | string | Dutch postcode to centre a distance search on. |
| distanceMeters | integer | Radius in meters around the postcode. |
| urls | array | Listing or result-page URLs (URL mode). |
| fetchDetails | boolean | Visit each detail page for views, shipping, reviews, full description. |
| maxPages | integer | Optional bound on result pages walked per keyword/category/URL; leave empty to walk every result page. The run size is controlled by maxListings, which this field defers to. |
| maxListings | integer | THE run cap: total listings to return; default 20, 0 = unlimited. |
| resumeFromRunId | string | Optional. ID of a previous run of this actor (or a dataset ID). Listings already in that dataset are skipped, so this run returns only NEW listings (a delta). Continues ONE specific interrupted run — for recurring monitoring of the same search, use incrementalMode instead. |
| incrementalMode | boolean | Off by default. Turn on for daily/recurring monitoring of the same search: the first run returns everything as `NEW`; later runs return only `NEW`/`UPDATED`/`REAPPEARED` listings, each carrying `changeType`, `changedFields`, `firstSeenAt`, `lastSeenAt`. State is scoped per mode/queries/categoryId/urls/fetchDetails/filters, so two differently-filtered runs never share a baseline. |
| stateKey | string | Optional, incremental mode only. Name a monitoring campaign to keep its state stable or deliberately share it across runs. Leave empty to derive the key automatically from the search + filter settings. |
| emitUnchanged | boolean | Off by default, incremental mode only. Also return listings unchanged since the last run, marked `UNCHANGED` (bills those rows too). |
| emitExpired | boolean | Off by default, incremental mode only. Also return listings from a previous run no longer found, marked `EXPIRED`. Only produced after a complete, uncapped, non-resumed scan of the tracked search. |
| proxy | object | Proxy configuration; Apify datacenter proxy by default (works on the free tier). |
| residentialCountries | array | Countries rotated through when the residential tier is used as a fallback. Default `NL, BE, DE, FR, GB`. |
| maxResidentialRequests | integer | Cap on residential requests per run, to control cost; the run auto-downgrades to datacenter once spent. Default `200`. `0` disables residential. |
| backupProxyUrl | string | Optional last-resort proxy gateway URL. Leave blank to use the built-in default. |
| mcpConnectors | array | Optional MCP connectors to pipe a copy of each listing into (Notion, Linear, Airtable, Apify). Authorize one under Apify → Settings → Integrations, then pick it here. Leave empty to skip. |
| notionParentPageUrl | string | Notion only: URL (or id) of the page under which listing pages are created. Required to enable the Notion export; ignored by other connectors. |
| maxNotifyListings | integer | Cap on items written to each connector per run. Does not affect the dataset. Default `50`. |

### Resume & recurring updates

Two different things — pick the one that matches what you're doing:

| Need | Use |
| --- | --- |
| A crawl stopped and should continue | `resumeFromRunId` / automatic checkpoint recovery |
| Run the same search every day and receive only changes | `incrementalMode` |
| Keep separate daily campaigns for similar searches | distinct `stateKey` values |
| Run a normal full snapshot | leave both off |

**Resume** (`resumeFromRunId`) continues one specific interrupted or previous large crawl: paste a run ID or dataset ID and this run skips listings already collected there, returning only the remaining new listings. An automatic same-run checkpoint also protects against platform migrations/Resurrects without any input needed.

**Incremental mode** (`incrementalMode`) is for a schedule (for example, daily): the actor remembers the previous run of the *same* search by itself, so you never paste a run ID. The first run returns everything as `NEW`. Later runs return only `NEW`, `UPDATED`, and `REAPPEARED` listings by default — duplicates and unchanged listings are suppressed (and not charged). Turn on `emitUnchanged` or `emitExpired` only when you also want those rows returned (and billed for). State is isolated per mode/queries/categoryId/urls/fetchDetails/filter combination automatically; set `stateKey` to name or deliberately share a monitoring campaign.

Scheduled-run example — same search, run daily:

Day 1 (first run ever for this search):

```json
{ "mode": "search", "queries": ["fiets"], "incrementalMode": true }
```

→ every listing comes back with `"changeType": "NEW"`.

Day 2 (the schedule fires again, identical input):

```json
{ "mode": "search", "queries": ["fiets"], "incrementalMode": true }
```

→ listings whose price/condition/etc. changed come back as `"changeType": "UPDATED"` with `changedFields` listing what changed, brand-new listings come back as `"changeType": "NEW"`, listings that vanished and came back come back as `"changeType": "REAPPEARED"` — and listings that are still there, unchanged, are **not** returned at all (suppressed, not charged) unless `emitUnchanged` is on.

### Output Example

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

```json
{
  "id": "m0000000000",
  "title": "Koga elektrische fiets",
  "url": "https://www.marktplaats.nl/v/fietsen-en-brommers/fietsen-dames-damesfietsen/m0000000000-koga",
  "priceNumeric": 399.0,
  "priceType": "MIN_BID",
  "currency": "EUR",
  "condition": "Zo goed als nieuw",
  "brand": "Koga",
  "description": "Nette elektrische fiets, inclusief oplader en fietstassen.",
  "categoryId": 447,
  "category": { "id": 447, "name": "Damesfietsen", "parentName": "Fietsen en Brommers" },
  "location_cityName": "Amsterdam",
  "location_latitude": 52.3702,
  "location_longitude": 4.8952,
  "imageUrl": "https://images.marktplaats.com/api/v1/listing/images/0000.jpg",
  "image_urls": ["https://images.marktplaats.com/api/v1/listing/images/0000.jpg"],
  "attributes": { "condition": "Zo goed als nieuw", "brand": "Koga", "delivery": "Ophalen of Verzenden" },
  "seller": {
    "id": 0,
    "name": "Jane Doe",
    "type": "CONSUMER",
    "activeSince": "1 jaar",
    "paymentMethod": "ideal"
  },
  "sellerName": "Jane Doe",
  "sellerType": "CONSUMER",
  "sellerRating": 4.9,
  "sellerReviews": 37,
  "viewCount": 73,
  "savedCount": 3,
  "shipping": { "method": "DELIVERY", "carrierName": "PostNL", "price": "€ 5,49" },
  "date": "Vandaag",
  "scrapedAt": "2026-01-01T00:00:00+00:00"
}
```

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

Optionally pipe the scraped listings 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 listing — 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 listings should be created.
4. Use `maxNotifyListings` to cap how many listings are written to each connector per run.

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

### Plan Requirement

Runs on any Apify plan, including the free tier, using the default Apify datacenter proxy. Heavier runs with detail enrichment benefit from a paid plan for higher concurrency and proxy throughput.

### Proxy policy

The scraper uses a tiered, cost-first proxy strategy:

- **Datacenter (default).** Datacenter is the default and works on every plan, including the free tier.
- **Residential (optional, budgeted fallback).** Selecting the residential proxy group is bounded by `maxResidentialRequests`. Because residential traffic is billed at a higher rate, the run **falls back to datacenter** once that budget is spent.
- **Backup gateway (last resort).** If the Apify proxy is unavailable on your plan, the run uses a backup gateway (override with `backupProxyUrl`) instead of returning nothing.

Run logs reference only the abstract tier (datacenter / residential / backup) and never expose any geography.

# Actor input Schema

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

Choose Search to build queries from keywords/filters, or URL to scrape pasted listing or result-page URLs directly.

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

One or more keywords to search for, e.g. "fiets", "iphone 15". Leave empty to browse a whole category.

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

Optional numeric Marktplaats category id to restrict results (e.g. 91 = Auto's, 447 = Fietsen). Find it in a category URL or leave empty for all categories.

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

Result ordering.

## `sortOrder` (type: `string`):

Direction for the selected sort field. Descending shows highest/newest first; ascending shows lowest/oldest first.

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

Only keep listings priced at or above this amount (euros).

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

Only keep listings priced at or below this amount (euros).

## `sellerType` (type: `string`):

Restrict to private (consumer) sellers, professional sellers, or both.

## `postcode` (type: `string`):

Dutch postcode to centre a distance search on, e.g. "1011AB". Used together with Distance.

## `distanceMeters` (type: `integer`):

Radius in meters around the postcode to search within (e.g. 25000 = 25 km).

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

Paste Marktplaats listing URLs (/v/.../mNNNN-slug) or result-page URLs (/q/<kw>/, /l/<category>/). In URL mode the filter fields above are ignored.

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

Visit each listing page for view/favorite counts, shipping options, bids, seller reviews/rating and the full HTML description. Adds one request per listing.

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

Optional bound on result pages walked per keyword/category/URL. Leave empty to walk every result page.

## `maxListings` (type: `integer`):

THE cap on this run: total number of listings to return across all searches. Defaults to 20. Set 0 for unlimited.

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

Optional. ID of a previous run of this actor (or a dataset ID). Listings already in that dataset are skipped, so this run returns only NEW listings (a delta). Combine both runs' datasets for the full set. Max listings then counts only the new listings. This continues ONE specific interrupted run; for recurring daily monitoring of the same search, use Incremental mode below instead.

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

Turn this on for daily or recurring monitoring. The first run returns all matching listings as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED listings. Turn on "Emit unchanged" or "Emit expired" only when you also want those listings returned (and billed). State is kept separately per search/category/URL and filter combination; 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 search/category/URL and filter settings — different searches then never mix state with each other.

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

Off by default. Turn on to also return listings 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 listings that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search — not when Max listings capped it or when Resume was used. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.

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

Proxy used for all requests. Apify datacenter proxy is enabled by default and works on the free tier.

## `residentialCountries` (type: `array`):

Country codes rotated through when the residential proxy tier is used as a fallback. Leave default unless you have a reason to pin specific regions.

## `maxResidentialRequests` (type: `integer`):

Caps how many requests may use the residential proxy in one run, to control cost. Once spent, the run auto-downgrades to the cheaper datacenter tier. Set 0 to disable residential entirely.

## `backupProxyUrl` (type: `string`):

Optional. A full proxy URL used as a last-resort failover when the Apify proxy is unavailable (for example on a restricted plan). Leave blank to use the built-in default gateway.

## `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 listing (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 listing 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": [
    "fiets"
  ],
  "sortBy": "OPTIMIZED",
  "sortOrder": "DECREASING",
  "sellerType": "any",
  "urls": [],
  "fetchDetails": true,
  "maxListings": 20,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true
  },
  "residentialCountries": [
    "NL",
    "BE",
    "DE",
    "FR",
    "GB"
  ],
  "maxResidentialRequests": 200,
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (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": [
        "fiets"
    ],
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "proxy": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/marktplaats-nl-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": ["fiets"],
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "proxy": { "useApifyProxy": True },
}

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

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": [
    "fiets"
  ],
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true
  }
}' |
apify call abotapi/marktplaats-nl-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=abotapi/marktplaats-nl-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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