# Changelog of Marktplaats.nl Scraper (`redomic/marktplaats-nl`) Actor

- **URL**: https://apify.com/redomic/marktplaats-nl/changelog.md
- **Full Actor documentation**: https://apify.com/redomic/marktplaats-nl.md

## Changelog

### Unreleased (backlog 2026-11-16)

- Added auction bid metrics end-to-end: `bidCount`, `bidderCount`, `highestBidAmount`/`highestBidCents` and `minimumBidAmount` (with `minimumBidCents` in detail/search payloads when available) parsed and normalized from search/detail responses, merged through detail enrichment, carried in monitor state/fingerprints and change diffs, and exposed in dataset views and README. `MIN_BID` rows now surface the starting bid as `minimumBidAmount`; bid fields are omitted from rows when absent.

### Unreleased (backlog 2026-11-15)

- Monitor mode now detects `reappeared` listings: removed listings are kept as tombstones and emit `changeType: "reappeared"` with `removedAt` and `daysRemoved` when they return; monitor summaries include `reappeared` counts.
- Added `hasImages` input filter (default false) to keep only listings that include at least one image; search and monitor rows continue to expose `imageUrls`, `imageUrl` and `imagesCount`.

### Unreleased (backlog 2026-11-14)

- Listing overview view now shows listing fields only (no `_status`/`mode`/`rows`/`reason`/`total`/monitor columns); status and monitor fields moved to a separate Monitor view.
- Added `fuel` and `make` fields on search/detail rows and new `make`, `model`, `fuel`, `transmission` input filters for car searches.
- Persist `firstSeenAt` across search runs in a named KV store (`marktplaats-nl-seen`) and add `firstSeenWithinDays` to filter search rows by when they were first seen.
- Added detail-page engagement fields `viewCount`, `favoritedCount` and `listedSince` (from embedded page stats) to detail rows and `scrapeDetails` search enrichment, exposed in dataset details.

### Unreleased (backlog 2026-11-13)

- Moved car-only `mileage` and `constructionYear` from dataset overview columns to details view so non-car listings no longer show all-blank overview columns.

- Prefill queries with the cars category URL (`https://www.marktplaats.nl/l/auto-s/`, mapped to the Auto's search API) instead of the previous default, so default rows are car listings with construction year and mileage populated.

- Normalize list-style listing attributes (e.g. car `Opties`/features, multi-select specs) into structured arrays end-to-end instead of concatenated strings: attributes/extendedAttributes keep list values as string arrays and rows expose top-level `options` for option/feature attributes (trimmed, deduped, order preserved).

- Pasted Marktplaats search URLs now keep all original filters/facets (attributes, price, postcode/distance, sort, category, etc.) when converted to API searches, and pagination preserves them end-to-end.

- Monitor change rows now include `priceDelta` and `firstSeenAt` (and `previousPrice` alias) so buyers can track price movements over time; unchanged runs emit no change rows and bill $0 (two-run acceptance).

- Added `delivery` and `shippingAvailable` listing fields and a `delivery` input filter (`Ophalen`/`Verzenden`, case-insensitive) so buyers can find pickup or shippable items.

### Unreleased (backlog 2026-11-11)

- Detail mode extracts `vin` (vehicle identification/chassis number) from car listing detail pages when present, and passes it through in enriched rows.
- Added `constructionYearMin`, `constructionYearMax` and `mileageMax` inputs for vehicle searches. Matching rows are kept, rows outside the requested year/mileage bounds or missing those values are excluded, and defaults leave existing behavior unchanged.

### Unreleased (backlog 2026-11-11)

- `sellerIds` now fetch real seller inventory as seller-scoped searches (`userId` with empty query, accepting numeric IDs or seller profile URLs) instead of keyword fallback; runs paginate and emit seller-scoped rows/status like other search sources.
- Added `searchInTitleAndDescription` input (default false): when true, search requests include `searchOnTitleAndDescription=true` to match terms in title and description; default remains title-only.
- Requests honor the `proxy` input via `Actor.create_proxy_configuration(actor_proxy_input=input.proxy)` and a fresh proxy session per retry (datacenter only, direct when `useApifyProxy` is false).

### Unreleased (backlog 2026-11-13)

- Added typed vehicle-spec fields on search and monitor rows from search-row attributes/extendedAttributes: `transmission`, `driveTrain`, `model`, `cylinders`, `engineDisplacementCc`, `fuelConsumption`, `emptyWeightKg`, `towingWeightNoBrakesKg`, `euronorm`, `energyLabel`, `serviceHistory`, `upholstery` (null when absent). Fields are exposed in dataset details and documented in README.

### Unreleased (backlog 2026-11-12)

- Added `daysOnMarket` on listing and monitor rows (days since `postedAt`, clamped >= 0, null when unknown) and `postedWithinDays` input filter; rows without a known posted date or outside the window are excluded before emit/charge.

### Unreleased (backlog 2026-11-10)

- Added `.actor/store_task.json`: a public showcase task `dutch-used-car-listings` ("Dutch used car listings on Marktplaats.nl") for the most-searched buyer use case (used cars, the `marktplaats cars scraper` term). Input `{"query": "auto", "maxItems": 10}` was run live against the search endpoint on 2026-11-10 and returned real rows (100 fetched, totalResultCount ~1.5M); datasetView `overview`, SEO title/description included. `pipeline.store_task` creates/runs/publishes it on the next publish.
- `categoryName` is now filled for all 36 L1 verticals, not just the 20 browse names: the extra 16 IDs (Doe-het-zelf en Verbouw 239, Huizen en Kamers 1032, Diensten en Vakmensen 1098, ...) were read from the live `searchCategoryOptions` in `fixtures/search_bosch.json.gz` (captured 2026-11-10), the 20-name `CATEGORY_MAP` input map is unchanged, and the bosch capture (6 verticals, 100 rows) is covered by a test asserting every row with an `l1CategoryId` has a `categoryName`.
- Added category browsing: `category` (Dutch L1 name) plus `l1CategoryId` / `l2CategoryId` inputs. Twenty L1 verticals are mapped name→ID (Auto's 91, Fietsen en Brommers 445, ...), a unique prefix (`Fietsen`) and common spellings (`Telefoons`) resolve, and the L1 ID is sent to `/lrp/api/search` as `l1CategoryId`. Search rows are post-filtered so only the requested vertical is returned; `l2CategoryId` filters on the listing's sub-category because the API ignores it. Every row now carries `categoryName` (reverse of the L1 name map), and the README documents the input with an example. Verified on live captures `fixtures/empty_l1.json.gz` (query=\&l1CategoryId=91, all rows l1=91) and `fixtures/empty_l445.json.gz` (all rows l1=445): the guard keeps one vertical and rejects the other.
- `advertiserType` is now also inferred as `Zakelijk` when a seller carries both `PROFILE` and `ADMARKT_CONSOLE` traits (ads run through the business console), even with no website link or single business-only trait; the pair is required, a single trait stays unknown. Verified on `search_auto.json.gz`: Zakelijk rows go 9 → 12, so the `advertiserType` filter changes the row count. Monitor state stays keyed by `str(itemId)`, Dutch dates normalize to ISO, monitor stays list-only, and two unchanged monitor runs return 0 new/changed/removed at $0 with a successful run status.

### Unreleased (backlog 2026-11-09)

- Search and monitor rows now carry `query` (the keyword or URL each row was found under), and status rows carry the same `query`, so singular `query` inputs like `iphone 15` match the battery's per-query check; live fixture `fixtures/search_iphone15.json.gz` covers that input with real rows.

### 0.1 - 2026-11-09 (backlog 2026-11-09)

- Added `postcode` + `distanceKm` radius inputs: the search request sends them to Marktplaats as `postcode` + `distanceMeters`, and rows are post-filtered on `locationDistanceMeters` so only listings within the radius are returned; nationwide/no-distance rows are excluded when the filter is set.
- `MIN_BID` listings map to `priceType` `min_bid` with the minimum-bid amount; a zero `priceCents` now emits `priceAmount`/`priceCents` as `null` for every type except `free`, in search and detail parsing.
- `advertiserType` normalizes the platform `Bedrijf` spelling to `Zakelijk`, is inferred as `Zakelijk` when the seller links a website or carries a business-only trait, and otherwise stays `null`; the `advertiserType` filter accepts `Bedrijf` and `Zakelijk` interchangeably.
- `verticals` holds only real category values; hyphenated platform flags (`barcode-supported`, `smb-highvalue`) move to `traits`.
- `sellerWebsiteUrl` is renamed to `sellerWebsiteRedirect`: the raw value is an admarkt bside redirect, not the seller's site.
- README priceType table regenerated from captured output (adds `min_bid`); detail-mode field delta documented; monitor before/after proof shipped under `fixtures/monitor_before_after.json.gz` with counts new=1, changed=1, removed=1.
- Non-numeric prices (bidding/fast bid/see description/reserved) with `priceCents` 0 now emit `priceAmount`/`priceCents` as `null`; `priceType` carries the meaning (mapped `FAST_BID`/`ON_DEMAND`).
- `sellerVerified` is set true when Marktplaats `traits` includes `VERIFIED_SELLER`.
- Image URLs are deduplicated across size-variant `rule=` URLs and `imagesCount` matches the unique `imageUrls` list.
- README documents the full output schema and `sellerIds` input, and removes internal factory jargon from the footer.
- `advertiserType` is populated from listing attributes (particulier/zakelijk) when present.
- Monitor mode is list-only: it no longer carries per-listing detail enrichment even when `scrapeDetails` is set, so hourly runs stay cheap and stable.
- Monitor state and removed events keep item IDs as strings (`str(itemId)`), so a numeric-looking ID can never collide or change type between runs.
- Dutch relative dates with a clock suffix (`Vandaag, 14:05`, `Vandaag 14:05`, `Gisteren om 09:12`) and minute/second labels now normalize to ISO `postedAt`; existing day/week/month absolute dates unchanged.
- Absolute dates with an apostrophe year (`27 okt '26`) and `Net geplaatst` now normalize too; a malformed year returns `null` instead of raising.
- Added monitor regression tests: price-change and removed events, string state keys, two unchanged runs billing zero changes, and no detail-page fetches in monitor mode.

### Unreleased (backlog 2026-11-14b)

- Moved car-only columns (model, constructionYear, mileage, fuel, transmission) out of the listing overview into a dedicated Cars view so general searches no longer show constant-null overview columns.

### 2026-11-15

- Write per-source status and run summary to the run key-value store (`STATUS`, `SUMMARY`) instead of dataset rows.
- Fill car `make` from listing URL or title when missing and omit empty car-only fields on non-car rows.
