Price Monitor & MAP Monitor for Online Stores avatar

Price Monitor & MAP Monitor for Online Stores

Pricing

from $0.70 / 1,000 url checkeds

Go to Apify Store
Price Monitor & MAP Monitor for Online Stores

Price Monitor & MAP Monitor for Online Stores

Price monitor and MAP monitor for lists of product URLs. Reads JSON-LD, schema.org and Open Graph data with no LLM, diffs against your previous run, flags MAP violations and posts changes to your webhook. Pay per event: $1 per 1,000 URLs, $0.50 per 1,000 changes.

Pricing

from $0.70 / 1,000 url checkeds

Rating

0.0

(0)

Developer

Andrew Boiko

Andrew Boiko

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

19 hours ago

Last modified

Categories

Share

Price Monitor & MAP Monitor checks a list of product page URLs and tells you what changed since the last run. It reads the structured data that stores already publish in their HTML (JSON-LD schema.org Product/Offer, microdata, Open Graph product tags), so it is fast, cheap and never "hallucinates" a price. It keeps the previous result in a named storage, flags prices below your minimum advertised price (MAP), and can POST the changes to your webhook.

There is no LLM, no browser and no subscription. You pay per URL checked and per change found.

What does Price Monitor & MAP Monitor do?

  • Price tracking for any list of product URLs. For every URL it returns price, currency, availability (in stock, out of stock, preorder, backorder, limited), product title, brand, SKU and GTIN when the page publishes them.
  • Diff against the previous run. Every row says whether the product is new, unchanged or changed, with the previous price, the absolute change and the percentage change. Availability and currency changes are reported too.
  • MAP violation monitor. Give it a MAP list (URL + minimum advertised price). Rows priced below MAP are flagged, with the amount below MAP, the date the violation was first seen and a newViolation flag. They are also written to a separate violations dataset and to a VIOLATIONS record.
  • Webhook alerts. Set a webhook URL and the Actor sends one JSON POST at the end of the run when something changed or a MAP violation was found.
  • Polite and scalable. Per-domain concurrency and delay, retries with backoff, Apify Proxy (datacenter by default) with an optional residential fallback for domains that block datacenter IPs.
  • Honest about what it cannot read. If a page has no structured price data in its static HTML (for example a JavaScript-only shop), the row is returned with status needs-javascript and a clear reason. These rows are free.

How extraction works, in priority order: JSON-LD (including @graph, @id references, AggregateOffer, priceSpecification and ProductGroup variants), microdata, Open Graph / product:price:* meta tags, other price meta tags, and finally a few common CSS price selectors (marked confidence: low). The source and confidence fields tell you which one produced each price.

Why use Price Monitor & MAP Monitor?

  1. No AI cost per page. Extraction is deterministic code, so a check costs one HTTP request and about $0.001 instead of an LLM call per page. The same page gives the same answer every time, which is what a diff needs.
  2. Change tracking and MAP built in. General purpose e-commerce scrapers give you today's price only. Here the state of the last run is kept for you (per stateKey), so a scheduled run produces change rows, violation rows and a webhook without any extra tooling.
  3. Pay per result instead of per seat. Hosted price trackers are priced per month; Prisync, for example, lists plans from about $99 (October 2026, check current prices). Here 1,000 URLs checked costs $1.00 and each detected change adds $0.0005. A list you check once a week costs cents.

How much does it cost to scrape price monitor data?

Pay per event. Prices per single event in USD:

EventWhat is billedFREEBRONZESILVERGOLD
url-checkOne URL fetched and a price found$0.001$0.0009$0.0008$0.0007
change-eventOne URL whose price, availability or currency changed since the previous run$0.0005$0.00045$0.0004$0.00035

Rows with status needs-javascript, blocked, not-found or error are never billed. The first run with a new State key is a baseline and has no change events.

Examples at FREE prices:

  • 1,000 URLs, first run (baseline): 1,000 x $0.001 = $1.00
  • 1,000 URLs, next run with 50 changes: $1.00 + 50 x $0.0005 = $1.025
  • Daily check of 200 products for a month with about 5 changes a day: 6,000 x $0.001 + 150 x $0.0005 = $6.08

Apify Free plan users are limited to 25 URLs per run; the run then ends normally with the message "Free tier limit reached (25 results). Upgrade to a paid Apify plan for unlimited results." Set maxTotalChargeUsd on the run (or maxItems in the input) to cap spending; the Actor stops gracefully when the limit is reached. Platform usage (compute and proxy) is included in the event prices.

Input

FieldTypeDescription
urlsarray of stringsProduct page URLs to check.
mapListarray of objects{"url", "minimumAdvertisedPrice", "label"?, "currency"?}. These URLs are checked too; prices below MAP are flagged.
stateKeystringName of the watch list. Previous prices are stored in a named key-value store pmm-state-<stateKey> (lowercase letters, digits, hyphens). Use one key per list. Default default.
resetStatebooleanForget the saved prices and start a new baseline.
includeUnchangedbooleanIf off, only new, changed, violating and failed rows are written. Checked URLs are still billed. Default on.
priceChangeThresholdPercentnumberIgnore price moves smaller than this percentage. Default 0.
webhookUrlstring (secret)URL that receives a JSON POST when something changed.
webhookOnstringchanges-and-violations (default), changes or violations.
maxItemsintegerMaximum number of URLs to process in one run.
maxConcurrencyintegerTotal parallel requests. Default 20.
maxConcurrencyPerDomainintegerParallel requests per website. Default 2.
minDelayPerDomainMsintegerMinimum delay between request starts to one domain. Default 500.
requestTimeoutSecs, maxRetriesintegerPer-request timeout (30) and extra attempts on 429/5xx/network errors (2).
proxyConfigurationobjectApify Proxy by default. Custom proxy URLs are supported.
useResidentialFallbackbooleanRetry a blocking domain through residential proxies. Off by default because residential traffic is expensive.

Example 1, quick competitor price tracker:

{
"urls": [
"https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
"https://scrapeme.live/shop/Bulbasaur/"
],
"stateKey": "competitors-weekly"
}

Example 2, MAP violation monitor for a reseller list with a webhook:

{
"mapList": [
{ "url": "https://reseller-a.example/products/widget-pro", "minimumAdvertisedPrice": 49.99, "label": "WIDGET-PRO", "currency": "USD" },
{ "url": "https://reseller-b.example/p/widget-pro", "minimumAdvertisedPrice": 49.99, "label": "WIDGET-PRO", "currency": "USD" }
],
"stateKey": "widget-map",
"webhookUrl": "https://hooks.example.com/price-alerts",
"webhookOn": "violations"
}

Example 3, large list, only changes, slow and polite:

{
"urls": ["https://shop.example/p/1", "https://shop.example/p/2"],
"stateKey": "shop-example",
"includeUnchanged": false,
"priceChangeThresholdPercent": 1,
"maxConcurrencyPerDomain": 1,
"minDelayPerDomainMs": 2000
}

Output example

Illustrative output (values are examples, not from a live run). One row per URL in the default dataset:

{
"url": "https://reseller-a.example/products/widget-pro",
"domain": "reseller-a.example",
"label": "WIDGET-PRO",
"status": "ok",
"title": "Widget Pro 3000",
"brand": "Acme",
"sku": "WP-3000",
"price": 44.99,
"currency": "USD",
"availability": "in_stock",
"listPrice": 59.99,
"source": "json-ld",
"confidence": "high",
"change": "changed",
"changeTypes": ["price-drop"],
"previousPrice": 49.99,
"priceChange": -5,
"priceChangePct": -10,
"minimumAdvertisedPrice": 49.99,
"mapViolation": true,
"mapDifference": 5,
"mapDifferencePct": 10,
"violationSince": "2026-10-19T08:00:12.000Z",
"newViolation": true,
"checkedAt": "2026-10-19T08:00:12.000Z"
}

Status values: ok, needs-javascript (no price in the static HTML), blocked (HTTP 403/429 or a captcha page), not-found (404/410) and error. Rows that are not ok carry a reason that says what to change.

Other outputs: the key-value store record OUTPUT (counts, billed events, per-domain stats, warnings), VIOLATIONS (the MAP violations of this run) and a named dataset pmm-violations-<stateKey> that collects violation rows run after run.

How to use Price Monitor & MAP Monitor (tutorial)

  1. Collect the URLs. Paste your competitor or reseller product URLs into urls. For MAP checks add them to mapList with the minimum advertised price.
  2. Pick a State key. Use a short name such as widget-map. Runs with the same key are compared with each other.
  3. Run once to create the baseline. Every row comes back as new. Check the status column: rows with needs-javascript mean that site publishes no price in static HTML.
  4. Save a Task and schedule it (daily or weekly). From the second run on, rows show changed with the previous price, and MAP violations appear in the map dataset view.
  5. Connect alerts. Add a webhookUrl, or attach an Apify integration (Slack, email, Google Sheets, Make) to the Task and filter the dataset by change = changed or mapViolation = true.

Run via API

curl -X POST "https://api.apify.com/v2/acts/tiger19935~price-map-monitor/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"urls":["https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"],"stateKey":"api-demo"}'

Node.js:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('tiger19935/price-map-monitor').call({
mapList: [{ url: 'https://reseller-a.example/products/widget-pro', minimumAdvertisedPrice: 49.99 }],
stateKey: 'widget-map',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((r) => r.mapViolation));

MCP: the input is a plain array of URLs, so the Actor works as a tool in Apify MCP server for assistants (for example "check these 20 product URLs and tell me which ones dropped in price").

Integrations

  • Webhook: set webhookUrl to receive a JSON body with changes, violations and a summary (up to 200 rows each, with a truncated flag).
  • n8n, Make, Zapier: use the Apify module, run the Actor from a schedule and branch on change or mapViolation.
  • Google Sheets: attach the Apify Google Sheets integration to the Task to append rows each run.
  • Slack and email: use Apify Task integrations or route the webhook through a Slack-compatible endpoint.
  • MCP: call the Actor from any MCP client through the Apify MCP server.

The Actor reads publicly visible product pages and does not log in, use cookies or bypass paywalls. You are responsible for using it in line with the target websites' terms of service and applicable law. Check prices at a reasonable rate (the defaults are conservative), and do not use the data to collude on prices: monitoring a reseller for MAP compliance should follow your own agreements and local competition law. Product pages normally contain no personal data; if you store brand or seller information, handle it under GDPR or your local rules. This is not legal advice.

FAQ and support

Which stores does it support? Any store whose product page publishes schema.org / JSON-LD, microdata or Open Graph price data in the HTML it sends. That covers a large share of Shopify, WooCommerce, Magento and custom stores. It does not run a browser, so shops that load prices only with JavaScript return needs-javascript rows (free) instead of a guess.

How do MAP violations work? Add URLs with minimumAdvertisedPrice in mapList. A row is a violation when the observed price is below MAP. If you also set currency and the page reports another currency, the row is not flagged (mapCheck: currency-mismatch). Violations appear in the main dataset, in the VIOLATIONS record and in the named dataset pmm-violations-<stateKey>.

Can I schedule it? Yes. Save the Actor as a Task, schedule it, and use the same stateKey every time. Connect the dataset or the webhook to Slack, Sheets or Make.

Why was my URL billed even though the price did not change? A url-check is billed for every URL where a price was found. change-event is billed only when something changed.

Where is the previous state stored? In a named key-value store pmm-state-<stateKey> in your Apify account. Delete it or set resetState to start over.

What if a variant price is shown? For product groups the Actor reports the lowest variant price (priceMin, priceMax, variantCount). Use a URL with a variant parameter (for example ?variant=123) and, if the page publishes it, that variant's own price is used.

A site returns blocked. What now? Lower maxConcurrencyPerDomain, raise minDelayPerDomainMs, or enable useResidentialFallback.

Found a bug or a store that should work but does not? Open a ticket in the Issues tab with the URL and the run ID. We reply within 12 hours.

More tools by the same author

price monitor, competitor price tracker, map violation monitor, price tracking api, ecommerce price scraper, price change alert, json-ld product scraper, minimum advertised price, price comparison data, bulk product price checker, price monitoring tool, product price api.