# Marktplaats.nl Scraper (`haketa/marktplaats-scraper`) Actor

Marktplaats scraper & API (Netherlands classifieds): search listings by keyword & category and export title, price, description, location, seller, attributes, photos, date and URL. Dutch marketplace, resale and second-hand market data plus seller lead gen — fast, no login.

- **URL**: https://apify.com/haketa/marktplaats-scraper.md
- **Developed by:** [Haketa](https://apify.com/haketa) (community)
- **Categories:** Developer tools, Automation, Lead generation
- **Stats:** 57 total users, 6 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Marktplaats.nl Scraper — Dutch Classifieds Data Extractor for Cars, Real Estate, Electronics, Fashion & Used Items

> **The most complete Marktplaats.nl data extraction tool on Apify.** Pull live listings from **the Netherlands' largest classifieds marketplace** — plus its Belgian sister sites **2dehands.be** (Flemish) and **2ememain.be** (French) — with prices in EUR, normalized condition flags, seller type (particulier / zakelijk), category-specific attributes, location, images and full descriptions. Built for price benchmarking, used-car arbitrage, rental real-estate comps, reseller market intel, dropshipping research and demand-trend analytics across the Benelux region.

[![Apify Actor](https://img.shields.io/badge/Apify-Actor-blue)](https://apify.com/haketa/marktplaats-scraper)
[![Live Data](https://img.shields.io/badge/Data-Live%20at%20Run%20Time-orange)]()
[![Engine](https://img.shields.io/badge/Engine-Internal%20JSON%20API-green)]()
[![Pay Per Event](https://img.shields.io/badge/Pricing-Pay%20Per%20Event-yellow)]()
[![Coverage](https://img.shields.io/badge/Coverage-10M%2B%20Listings-purple)]()
[![Region](https://img.shields.io/badge/Region-Netherlands%20%F0%9F%87%B3%F0%9F%87%B1%20%C2%B7%20Belgium%20%F0%9F%87%A7%F0%9F%87%AA-red)]()
[![No Auth](https://img.shields.io/badge/Authentication-None%20Required-success)]()
[![Categories](https://img.shields.io/badge/Categories-Auto%27s%20%C2%B7%20Huis%20%C2%B7%20Telecom%20%C2%B7%20Mode-lightgrey)]()

***

### What This Actor Does

The **Marktplaats.nl Scraper** is a production-ready Apify Actor that extracts live classified listings from **marktplaats.nl** — the Netherlands' dominant consumer-to-consumer marketplace with **over 10 million active listings** at any moment — and from the two Belgian sister platforms that share its backend: **2dehands.be** (Flemish-speaking Belgium) and **2ememain.be** (French-speaking Belgium / Wallonia). The three sites are operated by the same group and expose the same internal JSON API, so a single actor covers the entire Benelux second-hand market.

Marktplaats has been the default place Dutch consumers go to buy and sell used cars, apartments, bicycles (`fietsen` are a whole vertical of their own), electronics, furniture, fashion, jobs, services and antiques since the late 1990s. The actor hands you back the same data a buyer sees — but as clean, normalized JSON ready for a warehouse, a dashboard, a price-monitoring system or an ML training pipeline. In a single run you can pull thousands of listings filtered by keyword, category, postcode radius, price band and sort order, with optional detail-page enrichment for full descriptions and category-specific attribute tables.

Each record can include:

- **Listing core** — `listingId` (Marktplaats `m-` identifier), `title`, `price` in EUR (parsed from the API's `priceCents` integer field), normalized `priceType` (`fixed` / `bidding` / `free` / `asking` / `trade` / `negotiable` / `on_request` / `see_description`), the original Dutch `priceTypeOriginal` (`Vaste prijs`, `Bieden`, `Gratis`, `Vraagprijs`, `Ruilen`, `Nader overeen te komen`), `currency` (always `EUR`)
- **Condition** — normalized `condition` (`new` / `like_new` / `used` / `not_applicable`) and the original Dutch `conditionOriginal` (`Nieuw`, `Zo goed als nieuw`, `Gebruikt`, `Niet van toepassing`) — with Belgian French fallbacks (`Neuf`, `Comme neuf`, `Utilisé`) where applicable
- **Taxonomy** — L1 `category` and L2 `subcategory` names (e.g. `Auto's` → `BMW`, `Telecommunicatie` → `Mobiele telefoons`, `Huis en Inrichting` → `Banken`)
- **Seller** — `sellerName`, `sellerId`, and `sellerType` (`private` for particulieren / `business` for zakelijke verkopers and dealers flagged with the SOI-498 trade label)
- **Geo** — `location` city/district string (Amsterdam, Rotterdam, Den Haag, Utrecht, Eindhoven, Groningen, Tilburg, Almere and every Dutch and Belgian gemeente in between)
- **Recency** — `date` in ISO format (the Marktplaats publish/refresh timestamp)
- **Media** — `images` array with the largest-available image URLs from the listing gallery
- **Logistics** — `shippingAvailable` boolean indicating whether the seller supports Marktplaats's verzendservice
- **Category-specific attributes** — a flexible `attributes` object that surfaces vehicle specs (`kilometerstand`, `bouwjaar`, `brandstof`, `transmissie`), property details (`oppervlakte`, `slaapkamers`, `huurprijs`), phone specs (`merk`, `model`, `opslag`) and whatever else the vertical exposes
- **Description** — partial from the API search response, full from the detail page when `scrapeDetails: true`
- **Metadata** — the originating `platform` (`marktplaats.nl` / `2dehands.be` / `2ememain.be`), the `searchQuery` and `searchCategory` used, full canonical `url`, and a `scrapedAt` ISO timestamp on every row

Whether you run a Rotterdam used-car dealership, an Amsterdam sneaker reseller, a Den Haag rental-comp analytics product or a market-intel team tracking second-hand pricing across the Benelux, this actor is the **fastest path from Marktplaats.nl to a usable dataset** — without writing a line of scraping code.

#### Why scrape Marktplaats yourself when this exists?

Marktplaats looks like a simple classifieds site. Scraping it at any meaningful scale is not. Teams who try DIY hit the same wall:

- The public site is a Next.js SPA — `requests.get()` against a search URL returns a thin HTML shell with no listings; the data only appears after JavaScript executes
- The internal JSON API at `/lrp/api/search` is undocumented and the schema is not stable; field names change without notice (`sellerInformation` vs `seller`, `imageUrls` vs `pictures`, `vipUrl` vs `selfLink`, `categorySpecificDescription` vs `description`)
- Prices come back as `priceCents` integers (a €1,499 BMW is `149900`) and must be divided cleanly — naive string parsing of the rendered `€ 1.499,00` Dutch number format breaks on the thousands-separator dot
- `priceType` is an opaque uppercase enum (`FIXED`, `BID`, `FAST_BID`, `MIN_BID`, `FREE`, `ASKING`, `TRADE`, `SEE_DESCRIPTION`, `RESERVED`) that has to be mapped to both human English and the Dutch labels shown on the site
- Condition is buried inside the `attributes` array under `key: "condition"`, with Dutch values (`Zo goed als nieuw`) that need translation to a stable English enum
- Pagination is capped at **5000 results per query** (`offset × limit < 5000`) — exceed it and the API silently returns empty `listings`
- Private vs business sellers are not a clean flag — the API uses a `showSoi498Label` boolean derived from the EU's Sales of Items 498 commercial-trader label
- Belgian variants (`2dehands.be`, `2ememain.be`) share the schema but switch the `Accept-Language` requirement and the price-type labels into Flemish and French — easy to break a parser that hard-codes Dutch
- Cloudflare and bot-fingerprinting checks kick in for unbranded user agents — the API returns `403` to anything that doesn't look like a real Chrome on Windows with `nl-NL` locales
- Detail-page descriptions live inside the `__NEXT_DATA__` JSON blob, not the rendered DOM — extracting them requires parsing the Next.js hydration payload and falling back to JSON-LD `<script type="application/ld+json">` when missing
- Listings disappear — sold or expired ads return 404 within hours of removal; scraping must be tolerant
- Image URLs come in multiple resolutions (`mediumUrl`, `largeUrl`, `extraExtraLargeUrl`) inside nested arrays (`imageUrls` and/or `pictures`) — you have to walk both and pick the highest-resolution variant

This actor solves every one of those: direct internal-API calls via `got-scraping` with realistic Chrome fingerprints bypass bot detection without needing a browser, the parser normalizes EUR prices and price-type enums, condition flags and seller signals are translated into stable English values while preserving the Dutch originals for display, category-aware logic populates vehicle and property fields automatically, and the pipeline pushes deduplicated records straight to the Apify dataset.

***

### Quick Start

#### One-Click Run

1. Open the actor page on [Apify Store](https://apify.com/haketa/marktplaats-scraper) and click **"Try for free"**
2. Type a search term in **Query** (e.g. `iphone 15`, `bmw 320d`, `vouwfiets`, `bank tweezits`) or leave it empty and pick a **Category ID** to browse a whole vertical
3. Optionally add a Dutch **Postcode** (e.g. `1012AB` Amsterdam) plus a **Distance** in metres to constrain results to a radius
4. Hit **Start** — listings stream into your dataset and can be exported as JSON, CSV, Excel, HTML, XML or RSS straight from the Apify dataset view

#### API Run (Python)

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run = client.actor("haketa/marktplaats-scraper").call(run_input={
    "platform":      "marktplaats.nl",
    "query":         "bmw 320d",
    "categoryId":    "91",          # Auto's
    "postcode":      "3011AA",       # Rotterdam centrum
    "distanceMeters": 50000,         # 50 km
    "minPrice":      500000,         # €5,000  (in cents)
    "maxPrice":      2000000,        # €20,000 (in cents)
    "sortBy":        "PRICE",
    "sortOrder":     "INCREASING",
    "scrapeDetails": True,
    "maxListings":   500,
})

for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(listing["title"], listing["price"], listing["location"], listing["sellerType"])
```

#### API Run (Node.js / TypeScript)

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

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });

const run = await client.actor('haketa/marktplaats-scraper').call({
    platform:      'marktplaats.nl',
    query:         'iphone 15',
    categoryId:    '31',           // Telecommunicatie
    subcategoryId: '1953',         // Mobiele telefoons | Apple iPhone
    sortBy:        'SORT_INDEX',
    sortOrder:     'DECREASING',
    scrapeDetails: true,
    maxListings:   300,
    requestDelay:  500,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(`Pulled ${items.length} iPhone 15 listings from Marktplaats.nl`);
```

#### API Run (cURL)

```bash
curl -X POST "https://api.apify.com/v2/acts/haketa~marktplaats-scraper/runs?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "platform":      "marktplaats.nl",
    "query":         "vouwfiets",
    "categoryId":    "1655",
    "postcode":      "1012AB",
    "distanceMeters": 25000,
    "minPrice":      10000,
    "maxPrice":      80000,
    "sortBy":        "SORT_INDEX",
    "sortOrder":     "DECREASING",
    "scrapeDetails": true,
    "maxListings":   200
  }'
```

#### API Run (Belgian platforms)

```python
## Same actor, different platform — Belgian Flemish 2dehands.be
run = client.actor("haketa/marktplaats-scraper").call(run_input={
    "platform":    "2dehands.be",
    "query":       "fiets",
    "postcode":    "1000",          # Brussels
    "maxListings": 200,
})

## Belgian French 2ememain.be
run = client.actor("haketa/marktplaats-scraper").call(run_input={
    "platform":    "2ememain.be",
    "query":       "voiture",
    "postcode":    "4000",          # Liège
    "maxListings": 200,
})
```

***

### How It Works

Marktplaats and its Belgian sister sites are server-rendered Next.js applications backed by a public-facing JSON search API at `/lrp/api/search`. This actor goes straight at that endpoint with realistic Chrome fingerprints — no headless browser, no Puppeteer, no Playwright for the search phase. Detail-page enrichment (when enabled) uses a separate lightweight HTML fetch that parses the embedded `__NEXT_DATA__` hydration payload.

| Source endpoint | URL pattern | Returns |
|---|---|---|
| Search API (NL) | `https://www.marktplaats.nl/lrp/api/search?query={Q}&l1CategoryId={C}&postcode={P}&distanceMeters={D}&priceFrom={F}&priceTo={T}&limit=30&offset={O}` | `{ listings: [...], totalResultCount: N }` |
| Search API (BE-NL) | `https://www.2dehands.be/lrp/api/search?...` | Same schema |
| Search API (BE-FR) | `https://www.2ememain.be/lrp/api/search?...` | Same schema |
| Detail page | `https://www.{platform}/v/...{listingId}.html` | Next.js HTML with `__NEXT_DATA__` blob and JSON-LD `Product`/`Offer` |

#### Engineering details

- **Direct API engine** — `got-scraping` with Chrome 120-126 fingerprints (`nl-NL` / `nl-BE` locales, desktop devices, Windows/macOS) hits the search endpoint without browser overhead, returning JSON in well under a second per page
- **Pagination** — `offset` increments by `PAGE_SIZE = 30` per page; the loop stops at `MAX_OFFSET = 5000` (the platform's hard cap), at `maxListings`, when `listings` is empty, or when no new IDs are added on a page
- **Retry with proxy rotation** — `403`/`429` responses trigger up to three retries with exponential backoff (3s × attempt) and a fresh Apify proxy URL each round
- **Price normalization** — `priceInfo.priceCents` is divided by 100 to produce a clean EUR float; the actor never tries to parse the rendered Dutch `€ 1.499,00` string
- **Price-type enum mapping** — the API's uppercase enum (`FIXED`, `BID`, `FAST_BID`, `MIN_BID`, `FREE`, `ASKING`, `TRADE`, `SEE_DESCRIPTION`, `NEGOTIABLE`, `ON_REQUEST`, `RESERVED`) plus Dutch display strings (`Vaste prijs`, `Bieden`, `Gratis`, `Vraagprijs`, `Ruilen`, `Nader overeen te komen`) and Belgian-French equivalents (`Prix fixe`, `Faire offre`, `Gratuit`, `Prix demandé`, `Échange`) all collapse into a stable English `priceType` value while the original is preserved as `priceTypeOriginal`
- **Condition normalization** — extracted from the `attributes` array under `key: "condition"`, then mapped from Dutch (`Zo goed als nieuw` / `Gebruikt` / `Nieuw` / `Niet van toepassing`) and French (`Comme neuf` / `Utilisé` / `Neuf`) to a clean English enum
- **Seller-type inference** — `showSoi498Label` (the EU commercial-trader badge) is checked first; presence flips `sellerType` from `private` to `business`
- **Image walker** — both `imageUrls` and `pictures` nested arrays are walked, picking `extraExtraLargeUrl` → `largeUrl` → `mediumUrl` → `url` in that order so the largest-available image always wins
- **Category-specific attributes** — every entry in the listing's `attributes` array (excluding `condition`, which is normalized separately) and every `verticals` entry is flattened into a single `attributes` object — surfacing vehicle specs, property details, phone specs and any other vertical-specific fields the platform exposes
- **Detail-page enrichment** — when `scrapeDetails: true`, the canonical listing URL is fetched, `__NEXT_DATA__` is parsed for the full `pageProps.listing` blob, and JSON-LD `Product`/`Offer` blocks act as a fallback for missing descriptions and images
- **Deduplication** — a `Set` of `listingId`s prevents the same ad being pushed twice when API pages overlap
- **Multi-platform support** — switching `platform` between `marktplaats.nl`, `2dehands.be`, `2ememain.be` automatically swaps the base URL, the proxy locale hint and the price/condition label fallbacks

***

### Input Parameters

```json
{
  "platform":       "marktplaats.nl",
  "query":          "iphone 15",
  "categoryId":     "31",
  "subcategoryId":  "",
  "postcode":       "1012AB",
  "distanceMeters": 25000,
  "minPrice":       10000,
  "maxPrice":       80000,
  "sortBy":         "SORT_INDEX",
  "sortOrder":      "DECREASING",
  "scrapeDetails":  false,
  "maxListings":    50,
  "requestDelay":   500,
  "proxyConfiguration": { "useApifyProxy": true }
}
```

#### Parameter reference

| Parameter | Type | Default | Description |
|---|---|---|---|
| `platform` | `string` | `marktplaats.nl` | Which Benelux platform to scrape. Choices: `marktplaats.nl` (Netherlands), `2dehands.be` (Belgium Flemish), `2ememain.be` (Belgium French). All three share the same backend and schema. |
| `query` | `string` | `iphone` | Keyword search term. Searches across title and description (`searchInTitleAndDescription=true`). Examples: `iphone 15`, `bmw 320`, `fiets`, `bank`, `vouwfiets`. Leave empty to browse a whole category. |
| `categoryId` | `string` | empty | Marktplaats L1 category ID. Leave empty for all categories. Common IDs: `31` (Telecommunicatie), `91` (Auto's), `504` (Computers en Software), `322` (Huis en Inrichting), `621` (Kleding Dames), `728` (Sport en Fitness), `1953` (Watches), `1655` (Fietsen en Brommers), `135` (Huizen en Kamers). See the Category Reference table below. |
| `subcategoryId` | `string` | empty | L2 subcategory ID for a narrower search (e.g. `Auto's` → `BMW`). Leave empty for the entire L1 category. |
| `postcode` | `string` | empty | Dutch or Belgian postcode that becomes the centre of the radius search. Examples: `1012AB` (Amsterdam), `3011AA` (Rotterdam), `2511DV` (Den Haag), `3511AA` (Utrecht), `5611AA` (Eindhoven), `1000` (Brussels). |
| `distanceMeters` | `integer` | `0` | Search radius from the postcode in metres. Only takes effect when `postcode` is set. Examples: `10000` (10 km), `25000` (25 km), `50000` (50 km), `100000` (100 km). `0` = no radius filter. |
| `minPrice` | `integer` | `0` | Minimum price in **cents** (so `10000` = €100). `0` = no lower bound. |
| `maxPrice` | `integer` | `0` | Maximum price in **cents** (so `100000` = €1,000). `0` = no upper bound. |
| `sortBy` | `string` | `SORT_INDEX` | Sort key: `SORT_INDEX` (newest first), `PRICE` (price), `OPTIMIZED` (Marktplaats relevance ranking). |
| `sortOrder` | `string` | `DECREASING` | Direction: `DECREASING` or `INCREASING`. Combine with `sortBy: PRICE` and `INCREASING` for cheapest-first. |
| `scrapeDetails` | `boolean` | `false` | When `true`, fetch each listing's detail page for the full description and category-specific attribute tables (car specs, property details, etc.). Slower but more complete. |
| `maxListings` | `integer` | `50` | Maximum total listings to scrape. `0` = unlimited. Note: Marktplaats hard-caps results at ~5,000 per query — use category + postcode + price filters to slice large searches into per-segment runs. |
| `requestDelay` | `integer` | `500` | Delay between API calls in milliseconds (`0`–`10000`). Higher values are gentler on the platform and reduce the risk of `429` throttling. |
| `proxyConfiguration` | `object` | Apify Proxy | Apify proxy settings. The internal API works without proxy from most IPs, but residential proxies improve reliability for very large runs. |

***

### Output Schema

Every record uses the same flat JSON schema so downstream consumers (databases, CRMs, BI tools) can ingest the entire dataset without per-category branching.

#### Core fields

| Field | Type | Description |
|---|---|---|
| `listingId` | `string` | Marktplaats listing identifier in `m-`/numeric form |
| `platform` | `string` | Source platform: `marktplaats.nl` / `2dehands.be` / `2ememain.be` |
| `title` | `string` | Listing title as displayed |
| `price` | `number` | Price in EUR (already divided from `priceCents`) — `null` for `Bieden`, `Gratis`, `Vraagprijs` listings without an asking number |
| `priceType` | `string` | Normalized: `fixed` / `bidding` / `free` / `asking` / `trade` / `negotiable` / `on_request` / `see_description` / `reserved` |
| `priceTypeOriginal` | `string` | Original Dutch (or Flemish/French) label: `Vaste prijs`, `Bieden`, `Gratis`, `Vraagprijs`, `Ruilen`, `Nader overeen te komen`, `Op aanvraag`, `Zie omschrijving`, `Gereserveerd` |
| `currency` | `string` | Always `EUR` |
| `condition` | `string` | Normalized: `new` / `like_new` / `used` / `not_applicable` |
| `conditionOriginal` | `string` | Original Dutch (or French): `Nieuw` / `Zo goed als nieuw` / `Gebruikt` / `Niet van toepassing` / `Neuf` / `Comme neuf` / `Utilisé` |
| `category` | `string` | L1 category name (e.g. `Auto's`, `Telecommunicatie`, `Huis en Inrichting`) |
| `subcategory` | `string` | L2 subcategory name (e.g. `BMW`, `Mobiele telefoons`, `Banken`) |

#### Seller fields

| Field | Type | Description |
|---|---|---|
| `sellerName` | `string` | Seller display name |
| `sellerId` | `string` | Marktplaats internal seller ID |
| `sellerType` | `string` | `private` (particulier) / `business` (zakelijk, SOI-498 trade badge present) |

#### Location / recency fields

| Field | Type | Description |
|---|---|---|
| `location` | `string` | City or district name (e.g. `Amsterdam`, `Rotterdam-Zuid`, `Antwerpen`) |
| `date` | `string` | ISO timestamp of when the listing was posted/refreshed |

#### Content fields

| Field | Type | Description |
|---|---|---|
| `description` | `string` | Partial from search API; full when `scrapeDetails: true` |
| `attributes` | `object` | Category-specific attribute key/value pairs (vehicle specs, property details, phone specs, etc.) — `null` when none surfaced |
| `images` | `array<string>` | Largest-available image URLs from the listing gallery |
| `shippingAvailable` | `boolean` | `true` when the seller supports Marktplaats verzendservice |

#### Metadata fields

| Field | Type | Description |
|---|---|---|
| `url` | `string` | Full canonical listing URL on the source platform |
| `searchQuery` | `string` | The keyword used to find this listing |
| `searchCategory` | `string` | The category ID used in the search |
| `scrapedAt` | `string` | ISO-8601 timestamp of extraction |

#### Example: Used iPhone listing (Amsterdam, particulier)

```json
{
  "listingId":         "m2098765432",
  "platform":          "marktplaats.nl",
  "title":             "Apple iPhone 15 Pro 256GB Natural Titanium",
  "price":             899.00,
  "priceType":         "fixed",
  "priceTypeOriginal": "Vaste prijs",
  "currency":          "EUR",
  "condition":         "like_new",
  "conditionOriginal": "Zo goed als nieuw",
  "category":          "Telecommunicatie",
  "subcategory":       "Mobiele telefoons | Apple iPhone",
  "sellerName":        "Jeroen V.",
  "sellerId":          "48275621",
  "sellerType":        "private",
  "location":          "Amsterdam",
  "date":              "2026-05-14T09:42:00.000Z",
  "description":       "Nette iPhone 15 Pro 256GB, sinds december 2024 in bezit, altijd met hoesje en screenprotector gebruikt. Doos en originele kabel aanwezig. Accu 98%. Geen krasjes.",
  "attributes": {
    "merk":     "Apple",
    "model":    "iPhone 15 Pro",
    "opslag":   "256 GB",
    "kleur":    "Natural Titanium",
    "garantie": "Ja, nog 7 maanden Apple-garantie"
  },
  "images": [
    "https://images.marktplaats.com/api/v1/listing-mp-p/images/9a/9aabc.../?rule=ecg_mp_eps$_84.jpg",
    "https://images.marktplaats.com/api/v1/listing-mp-p/images/9a/9aabd.../?rule=ecg_mp_eps$_84.jpg"
  ],
  "shippingAvailable": true,
  "url":               "https://www.marktplaats.nl/v/telecommunicatie/mobiele-telefoons-apple-iphone/m2098765432-apple-iphone-15-pro-256gb-natural-titanium",
  "searchQuery":       "iphone 15",
  "searchCategory":    "31",
  "scrapedAt":         "2026-05-16T20:00:00.000Z"
}
```

#### Example: Used BMW (Eindhoven, zakelijk dealer)

```json
{
  "listingId":         "m1987654321",
  "platform":          "marktplaats.nl",
  "title":             "BMW 320d Touring Sport Line | Pano | LED | Trekhaak",
  "price":             18950.00,
  "priceType":         "fixed",
  "priceTypeOriginal": "Vaste prijs",
  "currency":          "EUR",
  "condition":         "used",
  "conditionOriginal": "Gebruikt",
  "category":          "Auto's",
  "subcategory":       "BMW",
  "sellerName":        "Autobedrijf Van der Berg B.V.",
  "sellerId":          "11223344",
  "sellerType":        "business",
  "location":          "Eindhoven",
  "date":              "2026-05-15T11:15:00.000Z",
  "description":       "BMW 320d Touring Sport Line uit 2019. NL auto, 1 eigenaar, dealer onderhouden, NAP. Panoramadak, LED-koplampen, trekhaak, navigatie professional. Inruil mogelijk.",
  "attributes": {
    "bouwjaar":         "2019",
    "kilometerstand":   "98.450 km",
    "brandstof":        "Diesel",
    "transmissie":      "Automaat",
    "carrosserie":      "Stationwagon",
    "vermogen":         "190 pk",
    "kleur":            "Mineraalgrijs metallic",
    "btw_marge":        "Marge",
    "apk":              "Tot 03-2027"
  },
  "images": [
    "https://images.marktplaats.com/api/v1/listing-mp-p/images/aa/.../?rule=ecg_mp_eps$_84.jpg"
  ],
  "shippingAvailable": false,
  "url":               "https://www.marktplaats.nl/v/autos/bmw/m1987654321-bmw-320d-touring-sport-line-pano-led-trekhaak",
  "searchQuery":       "bmw 320d",
  "searchCategory":    "91",
  "scrapedAt":         "2026-05-16T20:00:00.000Z"
}
```

***

### Price-Type Reference

| `priceType` | `priceTypeOriginal` (NL) | French (`2ememain.be`) | Meaning |
|---|---|---|---|
| `fixed` | `Vaste prijs` | `Prix fixe` | Seller's listed price is firm |
| `bidding` | `Bieden` | `Faire offre` | Buyers submit bids; no fixed price |
| `free` | `Gratis` | `Gratuit` | Item is free — typically furniture, building leftovers |
| `asking` | `Vraagprijs` | `Prix demandé` | Asking price — small negotiation expected |
| `trade` | `Ruilen` | `Échange` | Seller wants to swap, not sell |
| `negotiable` | `Nader overeen te komen` | — | Open to discussion, no number given |
| `on_request` | `Op aanvraag` | — | Price disclosed on contact only (common for B2B) |
| `see_description` | `Zie omschrijving` | — | Price details inside the description body |
| `reserved` | `Gereserveerd` | — | Reserved for another buyer; still listed |

### Condition Reference

| `condition` | `conditionOriginal` (NL) | French (`2ememain.be`) | Meaning |
|---|---|---|---|
| `new` | `Nieuw` | `Neuf` | Brand-new, often still sealed |
| `like_new` | `Zo goed als nieuw` | `Comme neuf` | Used briefly, no wear |
| `used` | `Gebruikt` | `Utilisé` | Normal second-hand condition |
| `not_applicable` | `Niet van toepassing` | — | Category where condition is irrelevant (services, real estate) |

### Category Reference (popular L1 IDs)

| `categoryId` | Dutch L1 category | English | Notes |
|---|---|---|---|
| `91` | Auto's | Cars | Largest used-car marketplace in NL |
| `92` | Auto-onderdelen | Car parts | Mechanic and DIY supply chain |
| `1655` | Fietsen en Brommers | Bikes & mopeds | Bicycles dominate Dutch traffic |
| `135` | Huizen en Kamers | Houses & rooms | Sales + rentals, includes vakantiehuizen |
| `322` | Huis en Inrichting | Home & furnishing | Sofas, tables, lamps, decoration |
| `31` | Telecommunicatie | Telecom | Phones, smartwatches, accessories |
| `504` | Computers en Software | Computers & software | Laptops, desktops, components |
| `322-1133` | Witgoed en Apparatuur | White goods | Washers, dryers, fridges |
| `1525` | Audio, Tv en Foto | Audio, TV & photo | Cameras, hi-fi, televisions |
| `1953` | Sieraden, Tassen en Uiterlijk | Jewellery, bags, beauty | Watches, designer bags |
| `621` | Kleding | Dames Clothing — women's | Apparel, shoes, accessories |
| `728` | Sport en Fitness | Sport & fitness | Bikes, gym, outdoor gear |
| `1099` | Kinderen en Baby's | Kids & babies | Strollers, toys, clothing |
| `1100` | Dieren en Toebehoren | Animals & accessories | Pet listings (regulated separately) |
| `367` | Antiek en Kunst | Antiques & art | Collectibles, paintings |
| `2017` | Tickets en Kaartjes | Tickets | Events, concerts, public transport |
| `380` | Boeken | Books | Second-hand books |
| `356` | Vakantie | Holidays | Vakantiehuizen, last-minute deals |
| `33` | Werk en Personeel | Jobs & staff | Vacancies and CVs |
| `34` | Diensten en Vakmensen | Services & trades | Plumbers, painters, cleaning |

***

### Use Cases

#### Price benchmarking & dynamic pricing

Pricing analysts, e-commerce operators and reseller platforms use Marktplaats data to anchor their pricing to the live Dutch second-hand market:

- Pull the median asking price for any model + condition combination (`iPhone 15 Pro 256GB` `like_new`) refreshed daily
- Track price decay curves — how fast does a new iPhone or PlayStation lose value on the Dutch market?
- Compare zakelijk-dealer pricing vs particulier pricing on the same SKU to size the dealer margin
- Build city-level price heatmaps (Amsterdam vs Groningen vs Eindhoven) for the same product

#### Used-car arbitrage & dealer intelligence

Independent dealers, leasing companies and auto-fintech startups use Marktplaats's `Auto's` (categoryId `91`) and `Auto-onderdelen` (`92`) verticals to:

- Identify undervalued private-party listings (`particulier`, `Vaste prijs` < market median) for buy-and-flip
- Track every BMW 3-series, Volkswagen Golf, Tesla Model 3 listed in the past 24 hours across NL
- Pull `kilometerstand`, `bouwjaar`, `brandstof`, `transmissie`, `APK` attributes for full vehicle history records
- Monitor competing dealers' inventory and price changes in real time
- Build BPM-tax and import calculators with live NL pricing benchmarks
- Spot trends: EV vs ICE listing volume by month, Tesla Model Y average price over time

#### Real estate & rental comps (Benelux)

Property analytics teams, agents and PropTech startups use Marktplaats's `Huizen en Kamers` category as a complementary signal to Funda and Pararius:

- Pull every rental listing in Amsterdam, Utrecht, Rotterdam, Den Haag with the `huurprijs` attribute
- Compare Marktplaats kamer-te-huur prices against student-housing supply
- Detect new vakantiehuis listings in coastal NL (Zandvoort, Scheveningen, Texel) within hours
- Track the long-tail of for-sale-by-owner (`particulier`) listings that bypass MLS-style portals
- Cross-reference rental prices across Dutch postcodes for rental-yield analysis

#### Reseller & dropshipping market intel

Amazon FBA sellers, bol.com merchants, and dropshippers researching the NL/BE market use the actor to:

- Discover hot product categories by tracking listing volume and median price over time
- Identify white-space opportunities — high search demand on Google Trends NL but low Marktplaats listing density
- Source second-hand stock at scale by monitoring `Gratis`, `Bieden` and underpriced `Vaste prijs` listings
- Benchmark new-product retail pricing against the parallel Marktplaats used market
- Spot brand-counterfeit risks by sampling listings for premium brands (Stone Island, Moncler, Apple)

#### Demand-trend & consumer research

Market research firms, FMCG brands and trend agencies use the dataset to:

- Track week-over-week listing volume per category as a leading indicator of consumer sentiment
- Identify the most searched-for items by sampling top-of-page `OPTIMIZED` sort across keywords
- Build "what Dutch consumers are letting go of" reports — rising listings of treadmills, espresso machines, gaming consoles
- Cross-reference with weather and seasonality data (e.g. fietsen sales spikes in March)
- Power academic studies of second-hand economy growth in the Benelux

#### Brand monitoring & IP protection

Brands, distributors and IP-protection teams use the actor to:

- Search for counterfeit listings of premium brands (Louis Vuitton, Nike, Apple, Rolex) for takedown actions
- Detect grey-market sales of branded goods outside authorized channel
- Monitor warranty fraud — products listed with claims of `garantie` that exceed manufacturer terms
- Track stolen-goods patterns by category and region (high-value bicycles are a perennial NL theme)
- Build evidence packs of unauthorized seller activity with `sellerId`, `sellerName`, listing URL and screenshots

#### Lead generation for B2B services

Marketing agencies, fintech startups and B2B SaaS vendors use the `business` seller filter to:

- Build prospect lists of Dutch zakelijke verkopers (car dealers, electronics shops, fashion resellers, jewellery merchants)
- Enrich CRM with the seller's category mix, location and listing volume as firmographic signals
- Identify high-volume dealers as ideal-customer-profile (ICP) targets for inventory-management software
- Power lookalike audiences for B2B advertising platforms

#### Insurance, valuation & second-hand market research

Insurers, actuaries and second-hand valuation platforms use Marktplaats as a live price-discovery layer:

- Anchor home-contents insurance payouts to real second-hand replacement costs
- Build automotive market-value engines for used-car insurance and finance underwriting
- Power "what is this worth?" consumer-facing apps with live Marktplaats medians
- Track depreciation curves for electronics, appliances, bicycles, instruments

#### Journalism & data reporting

Investigative journalists, public-interest media and data reporters use Marktplaats as a Dutch consumer-market mirror:

- Cover the rise (or fall) of specific consumer trends — e-bikes, heat pumps, gaming consoles, work-from-home equipment
- Investigate underground trades or regulated-goods listings (firearms, certain pet breeds, restricted plants)
- Map second-hand market activity to economic cycles, inflation, energy crises
- Source citizen-reporter listings for regional reporting

***

### Sample Queries & Recipes

#### Recipe 1: All used iPhones under €700 in the Randstad

```json
{
  "platform":       "marktplaats.nl",
  "query":          "iphone",
  "categoryId":     "31",
  "postcode":       "1012AB",
  "distanceMeters": 60000,
  "maxPrice":       70000,
  "sortBy":         "PRICE",
  "sortOrder":      "INCREASING",
  "maxListings":    500
}
```

#### Recipe 2: Cheapest BMW 3-series across NL — full vehicle specs

```json
{
  "platform":      "marktplaats.nl",
  "query":         "bmw 320",
  "categoryId":    "91",
  "sortBy":        "PRICE",
  "sortOrder":     "INCREASING",
  "scrapeDetails": true,
  "maxListings":   1000
}
```

#### Recipe 3: Free items in Amsterdam — `Gratis` listings only

```json
{
  "platform":       "marktplaats.nl",
  "query":          "",
  "postcode":       "1012AB",
  "distanceMeters": 20000,
  "maxPrice":       1,
  "maxListings":    200
}
```

(Use `maxPrice: 1` to capture `Gratis` items where `priceCents` is `0` or `1`.)

#### Recipe 4: All zakelijke (business) sellers in the Telecom category

Run the actor with `categoryId: "31"` and filter `sellerType === "business"` downstream:

```python
business_dealers = [r for r in records if r["sellerType"] == "business"]
```

#### Recipe 5: Rental rooms (`kamers te huur`) in Utrecht student belt

```json
{
  "platform":       "marktplaats.nl",
  "query":          "kamer huur",
  "categoryId":     "135",
  "postcode":       "3511AA",
  "distanceMeters": 15000,
  "minPrice":       30000,
  "maxPrice":       100000,
  "sortBy":         "SORT_INDEX",
  "maxListings":    300
}
```

#### Recipe 6: Vouwfietsen (folding bikes) in the Belgian Flemish region

```json
{
  "platform":       "2dehands.be",
  "query":          "vouwfiets",
  "categoryId":     "1655",
  "postcode":       "2000",
  "distanceMeters": 50000,
  "scrapeDetails":  true,
  "maxListings":    150
}
```

#### Recipe 7: Dropshipping research — every PlayStation 5 listed today

```json
{
  "platform":   "marktplaats.nl",
  "query":      "playstation 5",
  "categoryId": "504",
  "sortBy":     "SORT_INDEX",
  "sortOrder":  "DECREASING",
  "maxListings": 500
}
```

Then in Python:

```python
from datetime import datetime, timezone, timedelta
today = datetime.now(timezone.utc).date()
todays = [r for r in records if datetime.fromisoformat(r["date"].replace("Z","+00:00")).date() == today]
```

***

### Integration Examples

#### Google Sheets (via Apify Integration)

1. Schedule the actor to run daily at 08:00 CET with your favourite keyword + category combination
2. Add the **"Export to Google Sheets"** Apify integration to the schedule
3. Get a fresh Marktplaats CSV in your Sheet every morning — ready for pivot tables and conditional formatting

#### Make.com / Zapier / n8n

Use the **Apify** connector available on all major automation platforms. Trigger downstream workflows on:

- New listings (latest run minus previous run) matching a watch-list query
- Price drops (compare today's price vs yesterday's by `listingId`)
- New zakelijk dealer accounts entering a category
- Specific keyword hits (e.g. trigger Slack/Telegram alert on any `Rolex Submariner` listing under €5,000)

#### Power BI / Tableau / Looker / Metabase

Connect the Apify REST API as a data source, refresh on schedule, and build dashboards covering:

- Median price by category and city, week over week
- Listing-volume heat maps across Dutch provinces
- Sankey diagrams of category → subcategory → condition → price
- Time-series of new vs business listings per vertical

#### Postgres / Snowflake / BigQuery

Use the [Apify webhook integration](https://docs.apify.com/platform/integrations/webhooks) to POST run results directly to your data-warehouse ingestion endpoint after every scheduled run. Recommended table schema:

```sql
CREATE TABLE marktplaats_listings (
  listing_id           TEXT PRIMARY KEY,
  platform             TEXT,
  title                TEXT,
  price_eur            NUMERIC(12,2),
  price_type           TEXT,
  condition            TEXT,
  category             TEXT,
  subcategory          TEXT,
  seller_name          TEXT,
  seller_id            TEXT,
  seller_type          TEXT,
  location             TEXT,
  date                 TIMESTAMPTZ,
  shipping_available   BOOLEAN,
  attributes           JSONB,
  images               TEXT[],
  url                  TEXT,
  scraped_at           TIMESTAMPTZ
);
CREATE INDEX ON marktplaats_listings (category, subcategory);
CREATE INDEX ON marktplaats_listings (seller_id);
CREATE INDEX ON marktplaats_listings (date);
CREATE INDEX ON marktplaats_listings USING GIN (attributes);
```

#### Salesforce / HubSpot CRM enrichment

Schedule the actor with a `business`-only category sweep, then upsert into Salesforce Accounts keyed on `sellerId`. Trigger Workflow Tasks when seller listing-volume crosses thresholds (e.g. "Dealer added 20+ new car listings this week — sales rep follow-up").

#### Webhooks & alerting

Use Apify webhook events to fire HTTP POSTs on every run completion. Combine with a small Lambda/Cloud Function to:

- Push new matching listings to Slack, Telegram or Discord watch channels
- Email subscribers when their saved-search criteria match
- Update internal price-alert tables in real time

***

### Major Dutch & Belgian Markets at a Glance

| Metro area | Country | Population | Marktplaats significance |
|---|---|---|---|
| Amsterdam | NL | 1.2M (metro 2.5M) | Largest concentration of listings — premium electronics, fashion, cars, bikes |
| Rotterdam | NL | 660K (metro 1.5M) | Port-city character — heavy on tools, machinery, vehicles, imports |
| Den Haag (The Hague) | NL | 560K | Government and expat hub — international diplomats relocating drive churn |
| Utrecht | NL | 365K | Centrally located student city — strong rental kamer-te-huur volume |
| Eindhoven | NL | 240K | Tech and Philips region — high electronics and component listing activity |
| Groningen | NL | 235K | Northern student city — bicycle and furniture market |
| Tilburg | NL | 225K | Brabant industrial belt — strong cars and tools verticals |
| Almere | NL | 220K | Fast-growing Flevoland city — heavy family-goods volume |
| Breda | NL | 185K | Brabant — vacation property and second-hand sport gear |
| Nijmegen | NL | 180K | University city — high bicycle and student-furniture turnover |
| Antwerpen | BE (Flemish) | 530K | Main Flemish hub for `2dehands.be` |
| Gent | BE (Flemish) | 265K | Flemish student/cultural city |
| Brussels | BE (bilingual) | 1.2M | Cross-listed on both `2dehands.be` and `2ememain.be` |
| Liège | BE (French) | 195K | Main Walloon hub for `2ememain.be` |
| Charleroi | BE (French) | 200K | Walloon industrial city — heavy vehicles and tools market |

***

### Cost & Performance

| Metric | Value |
|---|---|
| Engine | Direct internal JSON API via `got-scraping` (no browser) |
| Runtime (50 listings, no detail enrichment) | ~10–20 seconds |
| Runtime (500 listings, no detail enrichment) | ~60–120 seconds |
| Runtime (500 listings, `scrapeDetails: true`) | ~5–10 minutes (one HTML fetch per listing) |
| Cost per run | Pay-per-event — varies with listing count |
| Pricing model | Pay-per-event (transparent per-record pricing) |
| Data freshness | Live at run time — Marktplaats has no daily snapshot, the API reflects the platform state at the moment of each call |
| Authentication | None required |
| Proxy required | Apify Proxy enabled by default for resilience; the API accepts unproxied traffic from clean IPs too |
| Concurrency | Safe to run multiple parallel configurations (different category/postcode slices) |
| Memory footprint | 256 MB sufficient; 512 MB recommended for `scrapeDetails: true` |
| Hard cap per search | ~5,000 results (Marktplaats platform limit) — work around with category + postcode + price slicing |

***

### Compliance, Privacy & Legal Notes

- **Public data only** — every field returned by this actor is publicly visible to any anonymous visitor of marktplaats.nl, 2dehands.be or 2ememain.be
- **No PII collection beyond what the seller chose to publish** — only the display name and seller ID as shown in the listing card
- **No emails or phone numbers** — Marktplaats hides direct contact data behind its messaging system; this actor does not attempt to unmask it
- **No login or account data** — the actor only reads public listings; it does not authenticate, post, message or otherwise interact with the platform
- **GDPR / EU privacy** — sellers post on Marktplaats with the reasonable expectation that their listings are public; aggregating that public data for legitimate business purposes (price benchmarking, market research, fraud detection) is generally permitted, but you remain the data controller and must respect data-subject rights (Articles 15-17 GDPR). Do not store or process personal data beyond what is necessary for your stated purpose, and honour deletion requests
- **Robots & ToS** — Marktplaats's robots.txt allows crawling of search pages with reasonable rate limits; the actor's default 500 ms inter-request delay sits well within polite-crawler territory. Always respect the platform's Terms of Service in your specific use case
- **No CAPTCHA bypass that would imply automated account abuse** — the actor only fetches anonymous public endpoints

> **Important:** Marktplaats data may not be used for unlawful purposes including identity fraud, stalking, harassment, or unsolicited spam (CAN-SPAM, the Dutch Telecommunicatiewet, EU ePrivacy Directive). The user is responsible for downstream compliance.

***

### Frequently Asked Questions

#### How fresh is the data?

Live at run time. Marktplaats does not publish a daily snapshot — the internal API reflects the state of the platform at the exact moment of each call. New listings appear in the dataset within seconds of being posted on the site; sold or removed listings vanish on the next run.

#### How many records will I get?

It depends on your query. Marktplaats hard-caps a single search at **~5,000 results** (`offset × limit < 5000`). A broad search (`Auto's` in all of NL) hits that cap; a narrow search (`bmw 320d` in Eindhoven, 20 km radius) might return 50–200. For very large pulls, split your run into segments by category + postcode + price band.

#### Does this scraper require a login or API key to Marktplaats?

No. The actor calls only public, anonymous endpoints. You only need an Apify account to run it.

#### Does this work for Belgium too?

Yes. Set `platform: "2dehands.be"` for Flemish-speaking Belgium or `platform: "2ememain.be"` for French-speaking Belgium / Wallonia. All three platforms share the backend, so the schema is identical — only the price-type labels and condition values switch language.

#### Does it support Cloudflare-protected pages?

Marktplaats's internal search API does not sit behind a CAPTCHA, but it does check user-agent and locale headers. The actor uses `got-scraping` with realistic Chrome 120-126 fingerprints in `nl-NL`/`nl-BE` locales, which clears the platform's checks reliably.

#### Can I filter by price?

Yes — pass `minPrice` and `maxPrice` in **cents** (so €100 = `10000`, €1,500 = `150000`). For "free items only" set `maxPrice: 1`.

#### Can I filter by city?

Yes — use a Dutch or Belgian `postcode` plus a `distanceMeters` radius. The actor does **not** support raw city-name filtering at the API level (Marktplaats expects postcodes), but you can post-filter on the returned `location` field downstream.

#### Can I distinguish private sellers from business sellers?

Yes — the actor returns `sellerType` set to either `private` (particulier) or `business` (zakelijk, derived from the EU SOI-498 commercial-trader badge). Filter downstream as needed.

#### Can I get the seller's email or phone number?

No. Marktplaats hides contact information behind its in-platform messaging system. This actor only returns what is publicly visible on listing cards — `sellerName` and `sellerId`. Contacting sellers must be done through Marktplaats itself.

#### How do I get the full description and category-specific attributes?

Set `scrapeDetails: true`. The actor will then fetch each listing's detail page, parse the embedded `__NEXT_DATA__` JSON and fall back to JSON-LD, returning the full description plus the complete `attributes` object (vehicle specs, property details, phone specs, etc.). This roughly doubles or triples runtime.

#### Can I schedule this to run automatically?

Yes — Apify's built-in **Scheduler** lets you trigger the actor hourly, daily, weekly or on any cron expression. Combine with the webhook or Google Sheets integration for a fully automated pipeline.

#### Does it work with the Apify free plan?

Yes — full functionality on the free tier. A run of 200 listings without detail enrichment typically uses a few cents of compute.

#### What output formats are supported?

JSON, CSV, Excel (XLSX), HTML, XML and RSS — directly from the Apify dataset view. The API also supports JSON Lines for streaming consumers.

#### Why is `price` sometimes `null`?

Because the listing uses a `priceType` other than `fixed`/`asking` — i.e. `Bieden` (bidding), `Gratis` (free), `Ruilen` (trade), `Nader overeen te komen` (negotiable), `Op aanvraag` (on request) or `Zie omschrijving` (see description) listings often do not carry a numeric price. The `priceType` and `priceTypeOriginal` fields tell you why.

#### Why is `condition` sometimes `null`?

Marktplaats sellers are not required to fill in the condition attribute, and certain categories (services, real estate, jobs) don't expose it. When the attribute is absent the field is `null` rather than guessed.

#### Why is my `attributes` object empty for some listings?

The attribute table varies by category and is partially populated even by the seller. A vintage book listing might only carry `taal` (`language`); a car listing typically carries 6–12 attributes. Enable `scrapeDetails: true` to pull the maximum available set from each listing's detail page.

#### Does the actor deduplicate?

Yes — a `Set` of `listingId`s prevents duplicates across paginated API pages. Across multiple runs you can deduplicate downstream using the `listingId` field as a primary key.

#### Can I scrape more than 5,000 results for a single broad query?

Not in a single search. Marktplaats's API hard-caps at ~5,000 results per query. To exceed this, slice your search into segments — for example, by L2 subcategory (`l2CategoryId`), price band (`minPrice` + `maxPrice`), or postcode radius — and merge the resulting datasets.

#### Can I run this for related Benelux marketplaces (Vinted, Bolcom, 2dehands.be)?

`2dehands.be` and `2ememain.be` are supported natively via the `platform` parameter. Vinted, Bol.com and Funda are separate platforms with different APIs — for those, see the dedicated actors in the **Related Apify Actors** section below.

#### What if the actor returns zero listings?

Most common causes: the search query yielded no matches (try broader keywords or remove `categoryId`), the `minPrice`/`maxPrice` band is too narrow, the postcode + distance combination is too tight, or Marktplaats's platform-side filters returned no results. Check the actor's run log — the line `[API] Total results: N` tells you exactly what Marktplaats reported.

#### How do I report a bug or request a feature?

Open an issue on the Apify Store actor page or contact the developer directly through the Apify Console.

***

### Related Apify Actors by Haketa

Building a multi-country marketplace dataset? These sibling actors cover other major regional classifieds platforms:

- [Kleinanzeigen.de Scraper (Germany)](https://apify.com/haketa/kleinanzeigen-scraper) — Germany's largest classifieds platform (formerly eBay Kleinanzeigen), 50M+ listings, full vehicle and real-estate attributes
- [OfferUp Scraper (US)](https://apify.com/haketa/offerup-scraper) — US local-marketplace giant, ideal sibling for US used-goods coverage
- [Kijiji.ca Scraper (Canada)](https://apify.com/haketa/kijiji-scraper) — Canada's largest classifieds platform across all provinces
- [TradeMe Scraper (New Zealand)](https://apify.com/haketa/trademe-scraper) — New Zealand's dominant marketplace for goods, cars and property
- [Lelong.my Scraper (Malaysia)](https://apify.com/haketa/lelong-my-scraper) — Malaysia's veteran online marketplace (electronics, fashion, auto parts)
- [Chotot.com Scraper (Vietnam)](https://apify.com/haketa/chotot-scraper) — Vietnam's leading classifieds marketplace
- [Mourjan Scraper (MENA)](https://apify.com/haketa/mourjan-scraper) — Pan-Arab classifieds across the Middle East and North Africa
- [Immoweb.be Belgium Property Scraper](https://apify.com/haketa/immoweb-scraper) — Belgium's #1 real-estate portal — perfect complement to Marktplaats's Belgian property listings
- [Domain.com.au Property Scraper (Australia)](https://apify.com/haketa/domain-com-au-scraper) — Australian real-estate sibling
- [VivaReal Brazil Real Estate Scraper](https://apify.com/haketa/vivareal-scraper) — Brazilian real-estate listings
- [Lamudi Philippines Real Estate Scraper](https://apify.com/haketa/lamudi-scraper) — Philippine property listings
- [YallaMotor Scraper (GCC)](https://apify.com/haketa/yallamotor-scraper) — Gulf-region used-car marketplace for cross-region automotive intel

***

### Comparison vs. Alternatives

| Approach | Setup time | Data freshness | Cost (1,000 listings) | Cloudflare/locale handling | Multi-platform (NL + BE) |
|---|---|---|---|---|---|
| **This actor** | < 1 minute | Live at run time | < $1 typical | Built-in | Built-in (NL/BE-NL/BE-FR) |
| Manual copy-paste | hours/day | Manual | Free + time | None | Each country manually |
| Custom Python + `requests` | 1–2 weeks dev | Live | Free + infra + maintenance | DIY (often blocked) | DIY per platform |
| Custom Playwright + proxies | 2–4 weeks dev | Live | Free + infra + proxy bill | Manual fingerprinting | DIY per platform |
| Generic SaaS scraper | Hours to set up per template | Live | $100–500+/mo | Often weak on `nl-NL` headers | Each domain billed separately |

***

### Why Pay-Per-Event Pricing?

Most data scrapers either charge a flat monthly subscription (you pay even when idle) or per-Compute-Unit (unpredictable). This actor uses **pay-per-event** pricing, which means:

- You only pay when the actor runs and only for the data you receive
- Charges scale linearly with how many listings you actually consume — no surprise CU bills
- Transparent, line-item billing inside Apify
- No monthly minimums, no annual contracts, no seat fees
- Free to evaluate — sample with `maxListings: 20` for pennies before committing to a larger sweep
- Per-event metering aligns the cost model with the value (`one extra listing = one extra event`)

***

### Changelog

| Version | Date | Notes |
|---|---|---|
| 1.0.0 | 2026-05 | Initial public release — internal JSON API engine, NL + BE-NL + BE-FR multi-platform support, price/condition/seller-type normalization, optional detail-page enrichment with `__NEXT_DATA__` parser, Apify Proxy integration, pay-per-event pricing |

***

### Keywords

Marktplaats scraper · Marktplaats.nl data · Marktplaats API · Dutch classifieds API · Netherlands marketplace extractor · NL used items data · Marktplaats price scraper · Marktplaats lead generation · Marktplaats car house scraper · Marktplaats listing scraper · marktplaats.nl JSON API · marktplaats car scraper · marktplaats real estate scraper · 2dehands scraper · 2dehands.be data · 2ememain scraper · 2ememain.be Belgium classifieds · Benelux marketplace data · Dutch used car data · Netherlands second hand data · Marktplaats fietsen scraper · Marktplaats huizen kamers data · Marktplaats wonen inrichting scraper · Marktplaats telecom iphone data · Amsterdam classifieds data · Rotterdam classifieds data · Den Haag marketplace data · Utrecht marketplace scraper · Eindhoven classifieds data · Groningen marketplace data · Tilburg marketplace data · Almere classifieds data · Brussels 2dehands data · Antwerp 2dehands scraper · Liège 2ememain scraper · particulier zakelijk seller scraper · Marktplaats verzendservice data · Marktplaats price benchmarking · used car arbitrage Netherlands · NL rental comps scraper · dropshipping research Netherlands · reseller market intel Benelux · Marktplaats brand monitoring · Apify Marktplaats actor · Netherlands C2C marketplace API · Dutch second hand market data · Benelux classifieds extraction · Marktplaats demand trend data

***

### Support

- **Bug reports:** Use the **Issues** tab on the [Apify Store actor page](https://apify.com/haketa/marktplaats-scraper)
- **Feature requests:** Same place — please include your use case and ideally a sample query
- **Direct contact:** Through the Apify developer profile

If this actor saves you engineering time, a **5-star rating** on the Apify Store helps other Benelux marketplace, e-commerce and analytics teams discover it. Dank je wel! / Merci beaucoup!

### 📅 Changelog & Maintenance

**Last updated:** 2026-07-02 — Actor verified and maintained. Data pipeline tested for quality, structure and freshness; selectors/endpoints confirmed against the live site.

# Actor input Schema

## `platform` (type: `string`):

Which marketplace to scrape. All three cover the Benelux second-hand market.

## `query` (type: `string`):

Type what you are looking for, e.g. 'iphone 15', 'bmw 320', 'vouwfiets', 'bank'. Leave empty to browse a whole category, or leave everything empty for a mixed sample of popular listings.

## `category` (type: `string`):

Pick a category to browse an entire vertical. Works with or without a search keyword. Choose 'All categories' to search across the whole marketplace.

## `sellerTypeFilter` (type: `string`):

Keep only private sellers (particulieren) or only business sellers / dealers (zakelijk), or return both.

## `conditionFilter` (type: `array`):

Keep only listings in the selected condition(s). Leave empty to include every condition.

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

Center your search on a Dutch or Belgian postcode, e.g. '1012AB' (Amsterdam) or '3011AA' (Rotterdam). Combine with a distance below to search a local area.

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

How far around the postcode to search, in meters. Only used together with a postcode. Example: 10000 = 10 km, 50000 = 50 km. Leave 0 for the whole country.

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

Lowest price to include, in euro cents. Example: 10000 = €100. Leave 0 for no lower limit.

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

Highest price to include, in euro cents. Example: 100000 = €1000. Leave 0 for no upper limit.

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

How to order the listings that come back.

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

Ascending or descending order for the sort above (e.g. price low-to-high vs high-to-low).

## `scrapeDetails` (type: `boolean`):

Also open each listing page for the full description and category-specific attributes (car specs, property details, etc.). More complete, but slower.

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

How many listings to collect in total. Set 0 for unlimited (Marktplaats caps a single search at ~5000).

## `subcategoryId` (type: `string`):

Power users only: narrow a category to one subcategory using its Marktplaats L2 ID (e.g. 1953 = Apple iPhone inside Telecom). Leave empty to include the whole category.

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

Proxy settings used to reach the marketplace reliably. The default works well for most runs.

## `requestDelay` (type: `integer`):

Pause between page requests, in milliseconds. Higher values are gentler on the site.

## Actor input object example

```json
{
  "platform": "marktplaats.nl",
  "query": "iphone",
  "category": "",
  "sellerTypeFilter": "all",
  "conditionFilter": [],
  "distanceMeters": 0,
  "minPrice": 0,
  "maxPrice": 0,
  "sortBy": "SORT_INDEX",
  "sortOrder": "DECREASING",
  "scrapeDetails": false,
  "maxListings": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "requestDelay": 500
}
```

# Actor output Schema

## `listingId` (type: `string`):

Marktplaats listing ID (m-format)

## `platform` (type: `string`):

marktplaats.nl / 2dehands.be / 2ememain.be

## `title` (type: `string`):

Listing title

## `price` (type: `string`):

Price in EUR

## `priceType` (type: `string`):

fixed / bidding / free / asking / trade

## `priceTypeOriginal` (type: `string`):

Original Dutch price type

## `currency` (type: `string`):

EUR

## `condition` (type: `string`):

new / like\_new / used / not\_specified

## `conditionOriginal` (type: `string`):

Original Dutch condition text

## `category` (type: `string`):

L1 category name

## `subcategory` (type: `string`):

L2 subcategory name

## `sellerName` (type: `string`):

Seller display name

## `sellerId` (type: `string`):

Seller unique ID

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

private / business

## `location` (type: `string`):

City or area name

## `date` (type: `string`):

Listing date ISO format

## `description` (type: `string`):

Full listing description

## `attributes` (type: `string`):

Category-specific attributes

## `images` (type: `string`):

Image URLs array

## `shippingAvailable` (type: `string`):

Shipping available

## `url` (type: `string`):

Full listing URL

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

Query used to find this listing

## `searchCategory` (type: `string`):

Category ID used in search

## `scrapedAt` (type: `string`):

ISO timestamp when scraped

# 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 = {
    "query": "iphone",
    "postcode": "",
    "maxListings": 100,
    "subcategoryId": "",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("haketa/marktplaats-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 = {
    "query": "iphone",
    "postcode": "",
    "maxListings": 100,
    "subcategoryId": "",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("haketa/marktplaats-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 '{
  "query": "iphone",
  "postcode": "",
  "maxListings": 100,
  "subcategoryId": "",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call haketa/marktplaats-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,haketa/marktplaats-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/1CzC5VIzWAYnfJSgv/builds/JVX7MTRBP2ApQl6f3/openapi.json
