# bol.com EAN Price & Seller Alerts | NL/BE (`nikita-dev/bol-offer-change-watch`) Actor

bol.com price & seller alerts for exact EANs in NL/BE. Full offer lists checked against bol.com's count; scheduled history, two-check seller removal, JSON/API. No bol.com Retailer API key.

- **URL**: https://apify.com/nikita-dev/bol-offer-change-watch.md
- **Developed by:** [Nikita Frolov](https://apify.com/nikita-dev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $30.00 / 1,000 complete ean offer checks

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## bol.com Price & Seller Monitor by EAN — Netherlands & Belgium

**See every verified seller offer for an exact EAN, then see what changed on the next check.** Track prices, new sellers, disappearing sellers, and the offer displayed on the product page across bol.com NL/BE. No bol.com Retailer API credentials are needed.

**Full-list check · Price and seller changes · Scheduled history · Agent-ready JSON**

Enter EAN barcodes, choose the Netherlands or Belgium storefront, and run. The Actor compares the offers it collected with bol.com's displayed offer count. It publishes a dataset row only for a complete check, so a partially loaded page cannot overwrite a good snapshot or create a false seller-removal alert.

### Why use this monitor?

| Need | What this Actor does |
| --- | --- |
| See more than the product-page price | Returns the verified seller offer list with seller, condition, price, delivery text, and offer UID when available. |
| Know what changed | Compares each complete check with the previous complete snapshot for the same EAN and storefront. |
| Avoid false seller-loss alerts | Reports a removed seller only after two consecutive complete checks without that seller. |
| Run ongoing checks | Save the input as an Apify Task, schedule it, and filter results by `hasChanges: true`. |
| Use the data in code or agents | Read structured dataset rows through Apify API, JSON/CSV export, webhooks, or the Apify MCP server. |

This Actor is built for marketplace sellers, repricing teams, and analysts tracking **known EANs**. It handles up to 50 EANs per monitor input. NL Dutch, BE Dutch, and BE French storefronts are supported; offers and prices may differ between them.

### Common use cases

- **Repricing research:** compare your own price with the other seller offers for the same EAN.
- **Seller movement:** spot a new seller, a confirmed removal, or a changed displayed seller.
- **Catalogue monitoring:** schedule the same set of EANs and feed verified snapshots into a dashboard or alert workflow.

### Start monitoring in four steps

1. Replace the prefilled sample with your EANs, one per line. Keep leading zeros.
2. Choose **Netherlands · Dutch**, **Belgium · Dutch**, or **Belgium · French**.
3. Start the Actor. The first complete check creates a baseline (`isBaseline: true`).
4. Save the input as an Apify Task and schedule it. Later complete checks report changes against that Task's own history.

For manual or API runs, reuse the same **Monitor name** to continue one history. Different names create separate histories. Run one check at a time for a monitor; overlapping runs can race when saving a snapshot.

```json
{
  "eans": ["0195949821967"],
  "market": "NL_NL",
  "monitorName": "phone-prices"
}
```

### What you receive

Each complete EAN check creates **one dataset row**, even when nothing changed. This makes a quiet run distinguishable from a failed or incomplete check.

| Field | Meaning |
| --- | --- |
| `ean`, `productId`, `productUrl`, `title` | Exact matched product and source page. |
| `country`, `language`, `currency` | Storefront and price context. |
| `offerCount`, `offers[]` | Verified offer count and seller offers. Each offer includes seller, condition, price, delivery text, and `offerUid` when present. |
| `displayedSeller` | Seller of the offer shown on the product page when it can be matched unambiguously. |
| `isBaseline`, `hasChanges`, `changeCount`, `changes[]` | Monitoring status and change records. |
| `observedAt`, `previousObservedAt` | Timestamps of the current and previous complete checks. |

Illustrative change record; prices and sellers below are examples, not a live listing:

```json
{
  "recordType": "OFFER_CHECK",
  "ean": "0195949821967",
  "isBaseline": false,
  "hasChanges": true,
  "changeCount": 1,
  "changes": [
    { "type": "PRICE_CHANGED", "seller": "Example seller", "before": 799, "after": 789 }
  ],
  "offerCount": 1,
  "offers": [
    { "seller": "Example seller", "condition": "NEW", "price": 789, "delivery": null, "offerUid": null }
  ]
}
```

Possible change types include `SELLER_ADDED`, `SELLER_REMOVED`, `PRICE_CHANGED`, `OFFER_SET_CHANGED`, and `DISPLAYED_SELLER_CHANGED`. Enable **Track delivery text changes** only if you also want `DELIVERY_CHANGED`; relative delivery phrases can change with the date and this option is off by default.

### Run it from JavaScript

Use the [official Apify API client](https://docs.apify.com/api/client/js/docs/introduction/quick-start) to start a check and read changed rows. Keep your Apify token in an environment variable.

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('nikita-dev/bol-offer-change-watch').call({
    eans: ['0195949821967'],
    market: 'NL_NL',
    monitorName: 'phone-prices',
});
if (!run) throw new Error('Actor run did not complete');

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const row of items) {
    if (row.hasChanges) console.log(row.ean, row.changes);
}
```

Reuse `monitorName` to compare with the previous complete manual or API run. An empty changed-row loop can mean no changes; check the complete dataset and `RUN_SUMMARY` before assuming every requested EAN succeeded.

### Use it with AI agents and MCP

The [Apify MCP server](https://docs.apify.com/integrations/mcp) lets an authenticated AI agent run this public Actor and read its dataset. Add `https://mcp.apify.com` to your MCP client, find `nikita-dev/bol-offer-change-watch`, and pass the same input shown above.

After `call-actor`, read the run status and dataset ID. If the run is still active, check its status before requesting `get-dataset-items`; `call-actor` itself does **not** return the offer rows. Filter returned rows by `hasChanges: true` for alerts, while retaining quiet rows as evidence that an EAN was checked. If some EANs are missing, inspect `RUN_SUMMARY` in the run's key-value store for incomplete, unresolved, or budget-skipped checks. Put `maxTotalChargeUsd` in MCP **call options**, not in Actor input.

Example task for an agent: *“Run `nikita-dev/bol-offer-change-watch` for EAN `0195949821967` on `NL_NL` using monitor `phone-prices`. Read the complete dataset rows, report seller or price changes, and mention any incomplete checks from `RUN_SUMMARY`.”*

The Actor exposes data through Apify; it does not independently send messages or connect to external apps. Your agent or workflow decides where alerts go.

### Schedule alerts and export data

- **Scheduled history:** Save the input as an Apify Task, then use [Apify Schedules](https://docs.apify.com/actors/running/schedules) to repeat it. Each Task keeps its own comparison history.
- **Automation:** Trigger a downstream workflow when a run finishes using [Apify webhooks](https://docs.apify.com/integrations/webhooks), read its dataset, and act only on rows where `hasChanges` is true.
- **Export:** Download the dataset as JSON or CSV, or read it through the Apify API. The `changes[]` and `offers[]` arrays contain the full detail.

### Price before you run

Pay per event includes Apify platform usage and proxy traffic. At the default **2 GB** memory, the start costs **$0.04** ($0.02 per GB). Each **complete EAN check** costs **$0.03**.

| Complete EANs in one run | Total at default memory |
| ---: | ---: |
| 1 | $0.07 |
| 10 | $0.34 |
| 20 | $0.64 |

The start event applies even if no EAN completes. Incomplete and unresolved EANs do not create a billed result row. Set an Apify maximum run charge when needed; if it cannot cover every EAN, the remainder appears in `RUN_SUMMARY.skippedForBudget`. The minimum supported maximum charge is $0.07. Check the [Pricing tab](https://apify.com/nikita-dev/bol-offer-change-watch/pricing) for current event rates.

### Reliability and interpretation

- **Complete means verified against bol.com's displayed count.** If the list cannot be completed, the Actor preserves the previous snapshot and records the reason in `RUN_SUMMARY`. If no EAN completes, the run fails instead of presenting an empty success as current market data.
- **Seller names are labels, not stable seller IDs.** A rename can look like a removal and an addition. `offerUid` identifies an offer when present, but is not assumed stable across repricing.
- **Displayed seller is not an official Buy Box guarantee.** The Actor reports the seller shown on the product page only when it can match exactly one offer.
- **Access can change.** bol.com page changes, access challenges, or proxy issues can prevent a complete check. The default residential Netherlands proxy is included in the event price; changing it may reduce reliability.

#### Does bol.com's official API already provide competing offers?

Yes. The [Retailer API competing-offers endpoint](https://api.bol.com/retailer/public/Retailer-API/v10/functional/retailer-api/competing-offers-api.html) is a strong option for eligible retailers with partner credentials. This Actor provides a no-bol-API-key Apify workflow with recurring snapshots and ready-to-use change records from the public storefront.

#### Where is my history stored?

Snapshots live in a named key-value store in **your Apify account**, isolated by Task or monitor name. The Actor does not read another user's history. The run's `RUN_SUMMARY` lists skipped, incomplete, and unresolved EANs.

#### Need help?

Open an [Issue](https://apify.com/nikita-dev/bol-offer-change-watch/issues) with the run ID, EAN, and storefront. Do not include passwords or API tokens.

This is an independent tool. It is not affiliated with, sponsored by, or endorsed by bol.com.

# Actor input Schema

## `eans` (type: `array`):

One barcode per line, including leading zeros. Only exact EAN matches are checked. Up to 50 EANs per monitor.

## `market` (type: `string`):

Prices and available offers can differ between the Netherlands and Belgium.

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

Optional for manual runs; use the same name to compare with earlier manual runs. Apify Tasks automatically keep separate histories.

## `trackDelivery` (type: `boolean`):

Optional. Delivery phrases can change daily even when the seller and price stay the same.

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

Residential Netherlands proxy is selected by default. Its traffic is included in the Actor event price; changing it may reduce reliability.

## `maxRequestsPerCrawl` (type: `integer`):

Optional safety limit. Leave empty for an automatic limit based on EAN count.

## Actor input object example

```json
{
  "eans": [
    "5702018035016"
  ],
  "market": "NL_NL",
  "monitorName": "default",
  "trackDelivery": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "NL"
  }
}
```

# Actor output Schema

## `checks` (type: `string`):

One row per EAN with complete seller offers. hasChanges marks updates; isBaseline marks the first check.

## `runSummary` (type: `string`):

Counts of checked, changed, incomplete, and unresolved EANs.

# 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 = {
    "eans": [
        "5702018035016"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "NL"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("nikita-dev/bol-offer-change-watch").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 = {
    "eans": ["5702018035016"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "NL",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("nikita-dev/bol-offer-change-watch").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 '{
  "eans": [
    "5702018035016"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "NL"
  }
}' |
apify call nikita-dev/bol-offer-change-watch --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nikita-dev/bol-offer-change-watch"
        }
    }
}
```

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/JblC4cedLPU4m8foJ/builds/JL2P7dxULIlEPTwqH/openapi.json
