# Marktplaats Scraper: Ads, Cars, Sellers & Alerts (`automation_craft/marktplaats-scraper`) Actor

Marktplaats.nl ads by keyword, category, search link, seller or ad link, no login; a search over the 5,000 ad cap is split by category. Rows hold price, place, km from your postcode, condition, photos, Dagtopper flags; ad pages add views, bids, car data. New ad alerts, pay per ad, JSON, CSV, API.

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

## Pricing

from $0.85 / 1,000 listings

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

### Marktplaats Scraper: Ads, Cars, Sellers & Alerts

**Marktplaats scraper** for marktplaats.nl, the Dutch classifieds site: search ads by keyword, by any of its 36 top and 1,982 sub
categories, by a Marktplaats search link with its filters, by seller or by ad link, and get one row per ad with its title, price
and price type, place, distance from your postcode, the day it shows, condition, delivery, category, photos, the site's attributes
and its promotion flags (Topadvertentie, Dagtopper, Admarkt). Turn on full ad details for the whole description, views,
favourites, the exact time placed, bids, shipping, the seller's type and years on Marktplaats, and car data (brand, model,
construction year, mileage, fuel, licence plate, APK). No login, no cookies, no captcha. A monitor delivers only the ads it never
delivered before and can send them to Telegram, Slack, Discord, WhatsApp or your own webhook.

Who uses it: resellers and bargain hunters watching new ads, car traders following private car ads, analysts comparing second hand
prices by category and region, and teams feeding Marktplaats ads into their own sheets, apps or AI agents.

### Quick start

1. Open the **Input** tab and type what you look for in **Keywords**, for example `gazelle fiets`, `iphone 15` or `lego technic`.
2. Or pick **Categories** (`fietsen-en-brommers`, `auto-s/volkswagen`, `445`, or a name such as `Fietsen en Brommers`), paste
   Marktplaats links into **Search links** (`https://www.marktplaats.nl/l/auto-s/f/benzine/473/`), seller pages into **Sellers**
   or ad links into **Ad links**.
3. Add filters if you like: placed within today, yesterday or the last week, condition, delivery, seller type, price, postcode and
   radius, construction year and mileage for cars, words that must or must not appear, promoted ads left out.
4. Set **Maximum ads** and click **Start**. Download the rows as JSON, CSV or Excel, or read them through the Apify API.

To receive new ads only, give the run a **Monitor name** and schedule it (daily or more often): each later run delivers only the ads
this monitor never delivered before.

### What you get

One row per ad (`type: "listing"`), one coverage row per search (`type: "search"`), free status rows for anything that could not be
delivered (`type: "status"`) and a run summary (`type: "summary"`). The dataset has three views: Ads, Ads with details, Searches
and status.

Fill rates below are measured on platform runs of 2026-10-20 (build 0.1.2). Search rows: 200 ads for the keyword `fiets` (run
BT6QyooQ0qR5xLPJZ), and for the distance 15 ads for `gazelle` from postcode 1012AB (run qzLs3X2RLWCGdu9IG). Rows with details: 13
ads, 10 private petrol cars from a car search (run iOoWHAbDZcCmVGyeE) and 3 bicycles from ad links (run fpwlYtMsbyB9F6a83).

| Field | What it is | Filled on (measured) |
|---|---|---|
| `adId`, `url`, `title` | Marktplaats's ad id (`m...` for a regular ad, `a...` for an Admarkt ad of a web shop or trader), the ad's link, its title | every row |
| `snippet`, `snippetLong` | the short and the longer text the search page shows | every search row (200 of 200) |
| `price`, `listedPrice`, `priceCents`, `currency`, `priceType`, `priceTypeLabel`, `priceTypeEn` | price in euros (cents too; `listedPrice` is the price the search listed, `price` the ad page's when it was read), the site's price type (fixed, bidding from a minimum, bidding, free, see description, to be agreed ...) with its Dutch and English name | price type every row; a price figure 164 of 200 (82 percent): bidding ads without a minimum and "see description" ads carry none |
| `condition`, `conditionEn`, `delivery`, `deliveryEn` | condition and delivery as Marktplaats labels them (`Gebruikt`, `Ophalen of Verzenden`) and in English | condition 191 of 200 (95.5 percent), delivery 187 of 200 (93.5 percent) |
| `categoryId`, `categoryName`, `parentCategoryId`, `parentCategoryName` | the ad's sub category and top category | every row |
| `city`, `countryName`, `countryCode`, `abroad` | where the ad is | city 198 of 200 (99 percent), country name 186 of 200 (93 percent); the country code with the country name (search rows) or the ad page (13 of 13 rows with details) |
| `distanceKm` | the distance Marktplaats measures from your **Postcode** (see the note on Admarkt ads below) | 15 of 15 with a postcode; empty without one |
| `latitude`, `longitude` | the ad's map point | business sellers only with personal data off: 14 of 200 (7 percent) |
| `dateShown`, `shownDate` | the date label the search shows (`Vandaag`, `Gisteren`, `17 okt 26`) and that day as YYYY-MM-DD; a Dagtopper ad shows the day of its boost | every search row |
| `postedAt` | the exact time the ad was placed | every row with details (13 of 13) |
| `sellerType`, `sellerId`, `sellerName`, `sellerUrl`, `sellerVerified`, `sellerWebsite` | business or private, and the seller (see Personal data) | seller type every row with details (13 of 13); on search rows Marktplaats names the kind only for business sellers (14 of 200), so `sellerType` is null for the others; seller id, name and link 14 of 200 (the business sellers) |
| `sellerPhone`, `sellerActiveSince`, `sellerActiveYears`, `sellerCity` | the phone number the ad page shows, years on Marktplaats, the seller's city | years and city every row with details (13 of 13); phone: none of the 13 (all private sellers, personal data off) |
| `imageUrls`, `imageCount`, `hasVideo` | photo links (never downloaded), how many, a video | photos every row; the video flag every search row |
| `priorityProduct`, `isTopAd`, `isDagtopper`, `isAdmarkt`, `reserved`, `urgent`, `traits`, `verticals` | Marktplaats's promotion product, top block, daily boost, Admarkt ad, reserved, "Moet nu weg", the site's own tags | every search row; 147 of the 200 `fiets` rows were Dagtoppers |
| `attributes` | the attributes the search page shows (frame height, brake type, construction year, mileage, fuel ...) | every search row |
| `description`, `descriptionHtml`, `attributeList` | with details: the whole description as text and HTML, every attribute of the ad page | description 13 of 13; the attribute list 3 of 3 non car ads (car ads carry `carAttributes`) |
| `year`, `mileageKm`, `fuel`, `transmission`, `body`, `brand`, `model` | cars and other vehicles | 10 of 10 cars (`brand` needs details) |
| `licensePlate`, `carAttributes`, `carQualityMarks`, `napAvailable` | with details on a car: licence plate (a business seller's, or any with **Include personal data**), every car attribute group the ad page shows, quality marks such as "APK tot 1 december 2027", NAP | licence plate 9 of 10 cars before the plate of private sellers was withheld; car attributes and quality marks 10 of 10 |
| `views`, `favourites` | the ad's view and favourite counters | every row with details (13 of 13) |
| `biddingEnabled`, `currentMinimumBid`, `bidCount`, `bids` | bidding, with each bid's amount and time (bidders' ids and names only with personal data on) | every row with details; bids on 1 of 13 |
| `buyItNow`, `buyersProtection`, `shippingOptions` | Direct Kopen, buyer protection, the shipping methods offered | shipping options 2 of 13 |
| `detailsIncluded`, `source`, `searchKey`, `searchLabel`, `position`, `monitorName`, `deliveryId`, `actorRunId`, `actorTaskId`, `scrapedAt` | whether the ad page was read, which input and search delivered the row, its place in the search | every row |
| `raw` | with **Include the raw data**: Marktplaats's own objects for the row | with the switch on (business sellers, or with personal data on) |

A search row (shortened; personal data off):

```json
{
  "type": "listing",
  "adId": "m2455501962",
  "url": "https://www.marktplaats.nl/v/fietsen-en-brommers/fietsen-dames-damesfietsen/m2455501962-gazelle-montreux-damesfiets-8-nexusnaaf",
  "title": "Gazelle montreux damesfiets 8 nexusnaaf",
  "price": 175, "priceType": "MIN_BID", "priceTypeEn": "bidding from a minimum",
  "condition": "Gebruikt", "conditionEn": "used", "delivery": "Ophalen", "deliveryEn": "pickup",
  "categoryName": "Damesfietsen", "parentCategoryName": "Fietsen en Brommers",
  "city": "Zwanenburg", "distanceKm": 10, "dateShown": "Gisteren", "shownDate": "2026-10-19",
  "sellerType": null, "sellerName": null, "imageCount": 1,
  "priorityProduct": "NONE", "isTopAd": false, "isDagtopper": false, "isAdmarkt": false,
  "detailsIncluded": false, "source": "search", "searchLabel": "\"gazelle\""
}
```

A row with details (shortened; a private seller with personal data off):

```json
{
  "type": "listing",
  "adId": "m2450621463",
  "title": "Peugeot 3008 1.2 PureTech Allure Pack Business|1/2 LEDER|AUT",
  "price": 17995, "priceType": "FIXED", "condition": "Gebruikt",
  "categoryName": "Peugeot", "parentCategoryName": "Auto's", "city": "Beek en Donk", "countryCode": "NL",
  "dateShown": "Vandaag", "shownDate": "2026-10-20", "postedAt": "2026-10-06T09:53:19Z",
  "sellerType": "private", "sellerId": null, "sellerName": null, "sellerPhone": null, "sellerActiveYears": 1,
  "year": 2023, "mileageKm": 87375, "fuel": "Benzine", "transmission": "Automaat", "body": "SUV of Terreinwagen",
  "brand": "Peugeot", "model": "3008", "carQualityMarks": ["APK tot 1 december 2027", "Toegang tot alle milieuzones"],
  "views": 382, "favourites": 1, "biddingEnabled": false, "bidCount": 0, "buyItNow": false,
  "detailsIncluded": true, "source": "search", "searchLabel": "in Auto's"
}
```

The coverage row of each search says how many ads Marktplaats counts for it (`adsOnMarktplaats`), how many it served
(`reachRows`), how many ads were met and delivered, how many were left out and by which filter (`filteredOutBy`), whether the search
was read to its end (`complete`) and why it stopped (`stoppedBy`). From a real run (ladies' bicycles placed today, at most 25 euros):

```json
{ "type": "search", "searchLabel": "in Fietsen en Brommers > Damesfietsen", "dateWindow": "today", "adsOnMarktplaats": 625,
  "reachRows": 625, "readsMade": 7, "distinctAdsMet": 624, "adsDelivered": 25, "filteredOut": 599,
  "filteredOutBy": { "price": 599 }, "complete": true, "stoppedBy": "end" }
```

### How much does it cost to scrape Marktplaats?

You pay per event, with Apify's store discounts by plan:

| Event | Free plan | Bronze | Silver | Gold |
|---|---|---|---|---|
| Listing (one ad row) | $1.19 per 1,000 | $1.09 per 1,000 | $0.99 per 1,000 | $0.85 per 1,000 |
| Listing details (an ad page read for a search row) | $1.40 per 1,000 | $1.30 per 1,000 | $1.20 per 1,000 | $1.00 per 1,000 |
| Results page (monitor runs only, one read of up to 100 ads) | $1.10 per 1,000 | $1.00 per 1,000 | $0.90 per 1,000 | $0.80 per 1,000 |
| Actor start (once per run) | $0.0075 | $0.0075 | $0.0075 | $0.0075 |

Platinum and Diamond plans pay the Gold price. The Actor start is a flat fee per run, with no plan discount.

The Store pricing card shows these prices per 1,000 events: "$0.85 / 1,000" on the Listing row means one ad row costs $0.00085 on
the Gold plan, less than a tenth of a cent.

- **Listing** is charged once per ad delivered as a row, from a search, a seller page or an ad link. An ad link row includes its
  ad page and pays only Listing.
- **Listing details** is charged per delivered search row whose ad page was read and merged: with **Full ad details** on, or with a
  **Seller type** the search cannot filter for itself (the seller is then judged on each ad page).
- **Results page** is charged only in a monitor run (**Monitor name** set): one per search page read, up to 100 ads, whatever it
  held, so a monitor pays for checking even on a day with nothing new. A read that failed or that Marktplaats did not apply is free.
  Runs without a monitor never pay it.
- Never billed: repeats in the run, ads left out by a filter or by the search's scope, ads a monitor already delivered, removed or
  unknown ads, invalid input, coverage rows, status rows, the summary and every notification.
- Example on the Bronze plan: 1,000 search rows cost $0.0075 (start) + 1,000 listings ($1.09) = $1.0975. On the Free plan the same
  run costs $1.1975, on the Gold plan $0.8575.
- Example with details on the Gold plan: 100 ads cost $0.0075 + 100 listings ($0.085) + 100 listing details ($0.10) = $0.1925.
- Example of 50 ad links on the Gold plan: $0.0075 + 50 listings ($0.0425) = $0.05.
- Example of a daily monitor run on the Gold plan whose search holds 250 ads since yesterday and 20 new ones: start ($0.0075) + 3
  results pages ($0.0024) + 20 listings ($0.017) = $0.0269. A day with nothing new: $0.0099.
- A run without a monitor that delivers nothing (a keyword with no ads, a filter no ad passes) pays only the start: $0.0075.

### Input

| Setting | What it does |
|---|---|
| `keywords` | Up to 200 keywords, each its own search, as you would type them on Marktplaats. Marktplaats's search also finds close spellings and searches descriptions; `titleOnly` keeps only titles holding every word. |
| `categories` | Up to 200 categories by address words (`fietsen-en-brommers`, `kleding-heren/schoenen`), name or id: 36 top and 1,982 sub categories. With keywords, every keyword is searched in every category. A name several categories share is a free row naming the candidates. |
| `searchUrls` | Up to 200 Marktplaats search, category or seller links with their filters, date window and their "#" part (price, construction year and mileage ranges, postcode and distance, sort). A part this Actor cannot apply is a free row, never silently dropped. |
| `sellerUrls` | Up to 200 seller pages or seller ids: every ad of each seller. |
| `adUrls` | Up to 2,000 ad links or ids with their letter (`m2455721910`, `a1529353272`), each delivered with its ad page. A removed or unknown ad is a free row. |
| `postedWithin` | Marktplaats's own date window: today, since yesterday or the last week. |
| `condition`, `delivery`, `sellerType`, `buyItNowOnly`, `urgentOnly`, `attributeFilters` | Filters sent to Marktplaats: condition, pickup or shipping, private or business, Direct Kopen, "Moet nu weg", and any other filter the site shows for the search by its key or Dutch label (`{"fuel": ["Benzine"], "transmission": ["Automaat"]}` for cars). A filter or value the search does not have is a free row listing those it has. |
| `minPrice`, `maxPrice`, `priceTypes` | Price range in euros and price types, checked on every row. Ads without a price figure are left out when a price range is set. |
| `postcode`, `radiusKm` | A Dutch postcode: every row gets its distance from it; with a radius of 1 to 300 km, the rows outside are left out free. |
| `minYear`, `maxYear`, `minMileageKm`, `maxMileageKm` | Construction year and mileage ranges for cars and other vehicles, checked on every row. |
| `postedAfter`, `postedBefore` | A day (YYYY-MM-DD) compared with the day each ad shows. |
| `includeKeywords`, `excludeKeywords`, `titleOnly` | Whole words the title or text must hold (one of them) or must not hold (the title and snippets of a search row; with full ad details, the ad page's title, description and attributes); search words in the title only. |
| `promotedAds` | All ads, no top ads (leaves out the top block and Admarkt ads), or organic only (also leaves out Dagtoppers and other boosted ads). |
| `excludeAdIds` | Ad ids or links you already have, skipped free. |
| `sort` | As Marktplaats shows it (the default), newest, oldest, price low to high, price high to low, or nearest to your postcode. |
| `maxItems`, `maxItemsPerSearch`, `maxPagesPerSearch` | Caps per run (default 100), per search, and reads of 100 ads per search. |
| `readPasses`, `splitLargeSearches` | Re-read passes for keyword searches (default 2), and reading past the 5,000 ads Marktplaats serves of one search (on by default). |
| `includeDetails`, `includePersonalData`, `includeRawData` | Full ad details (billed as Listing details), the personal data switch, and Marktplaats's own objects. |
| `monitorName`, `resetMonitor` | The monitor (below). |
| `telegramBotToken`, `telegramChatId`, `slackWebhookUrl`, `discordWebhookUrl`, `whatsappAccessToken`, `whatsappPhoneNumberId`, `whatsappTo`, `webhookUrl`, `webhookSecret`, `webhookHeaders`, `notificationLimit` | Free notifications of the ads a run delivered (below). |
| `failIfIncomplete`, `maxFailedRequestsPercent`, `requestsPerSecond`, `proxyConfiguration` | Fail the run when a search is incomplete or too many reads failed; the pace (at most 0.14 requests a second, the default); Apify's datacenter proxy (the default, included in the price) or your own proxy links. |

A run reads at most 200 searches (keywords times categories, plus search links and sellers). Every filter that can be checked on a
row is checked again before the row is billed; a row that does not match is left out free and counted on the coverage row.

#### Every search, up to 5,000 ads and past it

Marktplaats serves at most 5,000 ads of one search, and often fewer for a filtered search or a date window. This Actor reads 100
ads at a time. A keyword search is ordered a little differently on each read, so a search that falls short of the count Marktplaats
gives is read again, up to **Read passes** times (only while a pass still finds new ads). With **Read past 5,000 ads** on (the
default, not for monitors), a larger search is split into its top categories, then sub categories, then the values of one of the
site's own filters, and every ad is delivered once. Ads that carry no value of that filter stay beyond reach; the coverage row counts
them (`adsBeyondReach`) and says whether the search is complete.

Paid placements (the top block and Admarkt ads of web shops and traders) are mixed into Marktplaats's results whatever they match. A
placement that cannot show by its own data that it fits your search (category, filters, date window, every search word) is left
out free and counted (`filteredOutBy.sponsoredUnproven`: 56 of the 297 rows seen for `fiets`). An ad that is both a top ad and a listing
row comes once.

#### Full ad details

With **Full ad details** on, each delivered search row's ad page is read and merged into the row: the whole description, every
attribute, all photos, views, favourites, the exact time placed, bids, shipping options, the seller's type, years on Marktplaats
and city, and for cars the licence plate, the APK date, owners and the site's car data. It is billed as Listing details, per ad
whose page was read and merged. **Each ad page read adds one view to the ad's view counter** (once per run per ad). Ad links are
always delivered with their ad page, at the Listing price only.

#### Sort

Marktplaats offers no sort on the pages this Actor reads, so the Actor sorts each search itself: it reads the search (or its date
window) to the end, then delivers the rows in your order (newest, oldest, price, nearest). "Newest" without a date window reads
today, then since yesterday, then the week, delivers each window before it reads the next, and stops widening once the ads
delivered fill your maximum. A sorted search holds at
most 6,000 ads and takes as long as reading it whole. A price sort orders by the price the search lists (`listedPrice`); with
**Full ad details** the row's `price` is the one on the ad's page, which is newer and in rare cases differs.

#### Monitor: only new ads, with alerts

Give the run a **Monitor name** and schedule it. Each run reads the date window of every search (since yesterday unless you set
**Placed within**), pays a Results page per read, and delivers only the ads this monitor never delivered before; an ad is delivered
and billed once per monitor, ever, and the ads it knows are free. **Start the monitor again** forgets what it delivered. One monitor
name belongs to one set of settings: a run with other settings under the same name delivers nothing and says why. A monitor
remembers up to 150,000 ads.

Notifications are free and list the ads the run delivered, in a monitor run or any other: a Telegram chat through your own bot, a
Slack or Discord channel webhook and WhatsApp through your own WhatsApp Cloud API number each get a message of up to **Ads per
message** ads (default 10, at most 30), and a webhook receives up to 1,000 ads per run in JSON batches of 100, signed with HMAC
SHA-256 when you give a secret. A failed notification never fails the run.

#### Personal data

With **Include personal data** off (the default), a private seller's id, name, profile link and phone number, the ad's map point,
a private car's licence plate (also in `carAttributes`), and bidders' ids and names are null, also inside `raw`. A seller whose kind the run cannot tell (a search row without its ad page)
is treated as private. A business seller's data (traders, Admarkt ads) is business data and always delivered. Titles, snippets and
descriptions are delivered exactly as Marktplaats serves them, so an ad's own text can name a person or give a phone number. You are
responsible for a lawful use of personal data, GDPR included.

#### How long a run takes

Marktplaats refuses one address after about 130 to 150 requests in ten minutes. This Actor sends one request at a time, at most one
every 7.1 seconds (**Requests per second** 0.14, the default and the most allowed; lower is slower), and reads 100 ads per search
page:

- 1,000 search rows take about 10 reads, about 1.5 minutes. Measured: 200 rows for `fiets` in 26 seconds (3 reads), the 20 row
  prefill in about 6 seconds.
- 100 ads with details take about 12 minutes, one ad page each. Measured: 10 private cars with details in 84 seconds (12 requests).
- An ad link can redirect up to twice before its page: 5 ad links (3 ads, 2 unknown) took 75 seconds.
- A sorted search reads the whole search before it delivers: about 6 minutes for 5,000 ads.
- In one hour (the default run timeout) the Actor makes about 500 reads; raise the timeout in the run options for larger runs.

When Marktplaats refuses a request, the Actor pauses 90 seconds and asks once more on the same address; a second refusal in a row
stops the run's requests with a free status row. It never switches to fresh addresses to get past a refusal. What was delivered
stays delivered, and every search or ad link not reached gets a free row.

### What this Actor does NOT do

- **No Belgian sites.** Only marktplaats.nl; 2dehands.be and 2ememain.be are not read.
- **No price bands.** Marktplaats applies price ranges only on a door this Actor does not use, so a search past 5,000 ads is split
  by category and site filter, not by price. Price ranges are checked on each row instead.
- **No parallel requests and no concurrency setting.** One request at a time, at most one every 7.1 seconds (see How long a run
  takes); more requests at once on one address only reach Marktplaats's refusal sooner.
- **No distance for Admarkt ads.** Marktplaats gives Admarkt ads (web shop ads, ids starting with `a`) a distance of 0 km whatever
  their place, so the Actor treats it as no distance: such ads have `distanceKm` null, a radius search leaves them out free, and the
  nearest first sort puts them last.
- **No exact time placed without details.** Search rows carry the day the ad shows (`shownDate`); a Dagtopper shows the day of its
  boost. `postedAt` comes with details.
- **No hidden phone numbers.** A seller's phone comes only when the ad page shows it; never a number behind a click or a login.
- **No messages, bids or login.** Nothing is posted or bid, and no account data (saved searches, favourites, messages) is read.
- **No photo downloads.** Photo links only.
- **No price change or removal rows.** A monitor delivers new ads; an ad it already delivered is never delivered again.
- **No unpaid crawling.** A run stops with a status row when its unpaid work (pages whose ads were all filtered out, answers asked
  again) passes a small budget (USD 0.001 plus a little per billed event): narrow the search or run it again.

### API and integrations

Run it and get the rows in one call (replace `YOUR_TOKEN`):

```bash
curl -X POST "https://api.apify.com/v2/acts/automation_craft~marktplaats-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keywords": ["gazelle fiets"], "postcode": "1012AB", "maxPrice": 300, "condition": ["used"], "maxItems": 100}'
```

Node.js:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('automation_craft/marktplaats-scraper').call({ categories: ['auto-s'], sellerType: 'private', attributeFilters: { fuel: ['Benzine'] }, minYear: 2015, includeDetails: true, maxItems: 50 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((x) => x.type === 'listing'));
```

Python:

```python
from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("automation_craft/marktplaats-scraper").call(run_input={"keywords": ["lego technic"], "monitorName": "lego-technic", "postedWithin": "today"})
ads = [x for x in client.dataset(run["defaultDatasetId"]).iterate_items() if x["type"] == "listing"]
```

The Actor also runs from n8n, Make and Zapier through their Apify integrations, from AI agents through Apify's MCP server, and on a
schedule in the Apify Console.

### FAQ

#### How can I scrape Marktplaats listings?

Type a keyword, pick a category or paste a Marktplaats search link, set **Maximum ads** and start the run: no code, no login and no
proxy setup. Each ad comes as one row with its price, place, condition, photos and promotion flags, as JSON, CSV or Excel or through
the Apify API.

#### Is there a Marktplaats API for listings?

Marktplaats documents no public API for its listings. This Actor works as one: call it through the Apify API with keywords,
categories, search links, sellers or ad links and read the rows back as JSON, in one call with `run-sync-get-dataset-items`.

#### How do I get alerts for new Marktplaats ads?

Give the run a **Monitor name** and schedule it: each run delivers only the ads this monitor never delivered before and can send them
to Telegram, Slack, Discord, WhatsApp or your own webhook at no extra charge. A monitor run pays a Results page per search page it
checks, so a day with nothing new costs the start fee plus those reads.

#### How many ads can I get from one Marktplaats search?

Marktplaats serves at most 5,000 ads of one search. With **Read past 5,000 ads** on (the default), a larger search is split into
categories and one of the site's filters, every ad delivered once, up to your **Maximum ads**; the coverage row says how many ads
Marktplaats counts, how many were delivered, and whether the search is complete.

#### Can I export Marktplaats ads to Excel?

Yes. Download the run's dataset as Excel, CSV, JSON or XML from the Output tab or the API; the Ads view keeps the most useful
columns, and Ads with details adds the description, views, seller and car data.

#### Why does this Actor run with limited permissions?

It needs nothing more: it writes only to its own run's dataset and key-value store, and a monitor keeps its memory in a named
key-value store it creates itself. Limited permissions keep your other Apify data out of its reach.

### Changelog

- 0.1 (2026-10-20): first release: keywords, categories, search links, sellers and ad links; the site's filters and date windows;
  filters checked on every row; reading past 5,000 ads by category and site filter; full ad details with car data; sorting in code;
  a monitor with free notifications; private sellers' data behind the personal data switch.

### More data tools by Automation Craft

- [Kleinanzeigen Scraper & API: Ads, Prices, Places](https://apify.com/automation_craft/kleinanzeigen-scraper)
- [Leboncoin Scraper: Ads, Prices, Real Estate & Cars](https://apify.com/automation_craft/leboncoin-scraper)
- [Kijiji Scraper: Listings, Cars, Rentals & Prices](https://apify.com/automation_craft/kijiji-scraper)
- [Craigslist Scraper: Listings, Prices & Alerts](https://apify.com/automation_craft/craigslist-scraper)
- [OfferUp Scraper: Local Listings, Prices, Sellers](https://apify.com/automation_craft/offerup-scraper)
- [Mercari US Scraper: Listings, Sold Prices](https://apify.com/automation_craft/mercari-us-scraper)
- [AutoScout24 Scraper: EU Car Listings & Prices](https://apify.com/automation_craft/autoscout24-scraper)
- [Mobile.de Scraper: Cars, Prices, Dealers & Ratings](https://apify.com/automation_craft/mobile-de-scraper)

# Changelog

This Actor's version history is a separate document: https://apify.com/automation_craft/marktplaats-scraper/changelog.md

# Actor input Schema

## `keywords` (type: `array`):

What to search for, as you would type it on Marktplaats ("gazelle fiets", "iphone 15", "lego technic"). Each keyword is its own search; Marktplaats's own search also finds close spellings. Not allowed: / \ # ? % +.

## `categories` (type: `array`):

Marktplaats categories by address words ("fietsen-en-brommers", "auto-s", "kleding-heren/schoenen"), name ("Fietsen en Brommers") or id ("445"). A sub category is "top/sub". The table of 36 top and 1,982 sub categories is the site's own, read 2026-10-20; a name several categories share returns a free row with the candidates.

## `searchUrls` (type: `array`):

Links copied from Marktplaats: a search ("/q/fiets/"), a category ("/l/auto-s/"), with its filters ("/f/benzine/473/", "/ta/offeredSince/Vandaag/") and its "#" part (price, construction year and mileage ranges, postcode and distance, sort). A seller page ("/u/name/123/") works too. A link keeps its own filters.

## `sellerUrls` (type: `array`):

Seller pages ("https://www.marktplaats.nl/u/name/12345678/") or seller ids ("12345678"): every ad of each seller.

## `adUrls` (type: `array`):

Ad links ("https://www.marktplaats.nl/v/.../m2455721910-...") or ad ids with their letter ("m2455721910", "a1529353272"). Each ad page is read (one view on the ad's counter) and delivered with its full details as one Listing. A removed or unknown ad is a free row.

## `postedWithin` (type: `string`):

The site's own date window ("Aangeboden sinds"). A monitor uses "yesterday" unless set. Without it, sort "newest" reads today first, then yesterday, then the week, until it holds enough ads.

## `condition` (type: `array`):

One or more conditions, matched against each search's own condition filter.

## `delivery` (type: `string`):

Pickup or shipping, as the seller offers it.

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

Where the search has the site's own seller filter (cars and some other categories) it is sent to Marktplaats; elsewhere each ad page is read to see the seller type, so each delivered ad also bills Listing details and adds one view to the ad.

## `buyItNowOnly` (type: `boolean`):

Only ads that can be bought at once on Marktplaats.

## `urgentOnly` (type: `boolean`):

Only ads marked "Moet nu weg".

## `attributeFilters` (type: `object`):

Any other filter Marktplaats shows for the search, by its key or Dutch label, with values as the site labels them: {"fuel": \["Benzine", "Hybride Elektrisch/Benzine"], "transmission": \["Automaat"], "body": \["SUV of Terreinwagen"]} for cars. A filter or value the search does not have returns a free row listing those it has.

## `minPrice` (type: `number`):

Euros, cents allowed. Ads without a price figure are left out when a price filter is set.

## `maxPrice` (type: `number`):

Euros, cents allowed.

## `priceTypes` (type: `array`):

Only ads with these price types (the site's own list).

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

A Dutch postcode ("1012AB" or "1012"): every row gets its distance in km, measured by Marktplaats from this postcode.

## `radiusKm` (type: `integer`):

Only ads within this many km of the postcode (needs "postcode"). The whole search is read and the rows outside are left out free.

## `minYear` (type: `integer`):

Cars and other vehicles: the row's construction year.

## `maxYear` (type: `integer`):

Cars and other vehicles: the row's construction year.

## `minMileageKm` (type: `integer`):

Cars and other vehicles: the row's mileage.

## `maxMileageKm` (type: `integer`):

Cars and other vehicles: the row's mileage.

## `postedAfter` (type: `string`):

A day (YYYY-MM-DD), compared with the day each ad shows (a boosted Dagtopper ad shows the day of its boost).

## `postedBefore` (type: `string`):

A day (YYYY-MM-DD), compared with the day each ad shows.

## `includeKeywords` (type: `array`):

Whole words; an ad is kept when its title or text holds at least one (the full description too when details are read).

## `excludeKeywords` (type: `array`):

Whole words; an ad whose title or text holds any of them is left out free.

## `titleOnly` (type: `boolean`):

Keep only ads whose title holds every search word (Marktplaats searches titles and descriptions and close spellings).

## `promotedAds` (type: `string`):

Marktplaats puts paid ads first: the top block (Topadvertentie), Admarkt ads of web shops and daily boosted Dagtoppers. Left out ads are free.

## `excludeAdIds` (type: `array`):

Ad ids ("m2455721910") or links you already have; they are left out free.

## `sort` (type: `string`):

Marktplaats sorts only through a door its robots.txt disallows, so the Actor sorts each search itself: it reads the search (or its date window) to the end and delivers the rows in this order. "As Marktplaats shows it" delivers while it reads.

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

The most ad rows the run delivers. Default 100.

## `maxItemsPerSearch` (type: `integer`):

The most rows one search delivers.

## `maxPagesPerSearch` (type: `integer`):

Reads of 100 ads per search (a monitor reads this many of each search every run). Default: to the end.

## `readPasses` (type: `integer`):

Marktplaats orders a keyword search differently on each read, so one pass can miss a few ads; a search short of the site's count is read again, up to this many passes (only while a pass finds new ads). Default 2.

## `splitLargeSearches` (type: `boolean`):

On by default (not for monitors): a search with more ads than Marktplaats serves of one search is split into its sub categories and the site's own filter values.

## `includeDetails` (type: `boolean`):

Off by default. Reads each delivered ad's page: full description (text and HTML), every attribute, all images, views, favourites, the exact time it was placed, bids, shipping options, the seller's type, years on Marktplaats and city; for cars also the licence plate, inspection (APK) date, owners and the site's car data. Billed as Listing details. Each read adds one view to the ad's counter (once per run).

## `includePersonalData` (type: `boolean`):

Off by default. On: a private seller's id, name, profile link and phone number (when the ad page shows one), the ad's map point, and bidders' ids and names. Off: those fields are null for private sellers and for sellers whose kind is not known; a business seller's are always delivered. You are responsible for a lawful use, GDPR included.

## `includeRawData` (type: `boolean`):

Adds "raw": the site's own objects for the row (with personal data on, or for a business seller).

## `monitorName` (type: `string`):

A short name ("gazelle-amsterdam"). One name belongs to one set of settings; the memory never forgets an ad it delivered.

## `resetMonitor` (type: `boolean`):

Forget what this monitor delivered before this run.

## `telegramBotToken` (type: `string`):

The token BotFather gave your bot.

## `telegramChatId` (type: `string`):

Your chat id (a number) or a channel name starting with @.

## `slackWebhookUrl` (type: `string`):

A Slack incoming webhook (https://hooks.slack.com/...).

## `discordWebhookUrl` (type: `string`):

A Discord channel webhook.

## `whatsappAccessToken` (type: `string`):

Your own WhatsApp Cloud API access token.

## `whatsappPhoneNumberId` (type: `string`):

The id of your WhatsApp Cloud API number (digits).

## `whatsappTo` (type: `string`):

The number to message, international form without + (31612345678).

## `webhookUrl` (type: `string`):

Rows are POSTed here in batches of 100 (JSON).

## `webhookSecret` (type: `string`):

Signs each POST (X-Signature-Sha256, HMAC SHA-256 of the body); 16 to 200 letters, digits and - \_ . ~.

## `webhookHeaders` (type: `object`):

Extra headers for the webhook, at most 10.

## `notificationLimit` (type: `integer`):

The most ads listed in one Telegram, Slack, Discord or WhatsApp notification. Default 10.

## `failIfIncomplete` (type: `boolean`):

Off by default: the run ends as succeeded with coverage rows that say what was not read.

## `maxFailedRequestsPercent` (type: `integer`):

Fail the run when more than this percent of its reads failed.

## `requestsPerSecond` (type: `number`):

The pace of the run's requests: at most 0.14 a second (one every 7 s), the default, which keeps under the rate Marktplaats allows one address; lower is slower.

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

Apify's datacenter proxy (the default: Marktplaats answers it), or your own proxy links.

## Actor input object example

```json
{
  "keywords": [
    "gazelle fiets"
  ],
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

No description

## `report` (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 = {
    "keywords": [
        "gazelle fiets"
    ],
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/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 = {
    "keywords": ["gazelle fiets"],
    "maxItems": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("automation_craft/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 '{
  "keywords": [
    "gazelle fiets"
  ],
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call automation_craft/marktplaats-scraper --silent --output-dataset

```

## MCP server setup

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