Price Monitor & MAP Monitor for Online Stores
Pricing
from $0.70 / 1,000 url checkeds
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
Maintained by CommunityActor 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,unchangedorchanged, 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
newViolationflag. They are also written to a separate violations dataset and to aVIOLATIONSrecord. - 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-javascriptand 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?
- 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.
- 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. - 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:
| Event | What is billed | FREE | BRONZE | SILVER | GOLD |
|---|---|---|---|---|---|
url-check | One URL fetched and a price found | $0.001 | $0.0009 | $0.0008 | $0.0007 |
change-event | One 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
| Field | Type | Description |
|---|---|---|
urls | array of strings | Product page URLs to check. |
mapList | array of objects | {"url", "minimumAdvertisedPrice", "label"?, "currency"?}. These URLs are checked too; prices below MAP are flagged. |
stateKey | string | Name 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. |
resetState | boolean | Forget the saved prices and start a new baseline. |
includeUnchanged | boolean | If off, only new, changed, violating and failed rows are written. Checked URLs are still billed. Default on. |
priceChangeThresholdPercent | number | Ignore price moves smaller than this percentage. Default 0. |
webhookUrl | string (secret) | URL that receives a JSON POST when something changed. |
webhookOn | string | changes-and-violations (default), changes or violations. |
maxItems | integer | Maximum number of URLs to process in one run. |
maxConcurrency | integer | Total parallel requests. Default 20. |
maxConcurrencyPerDomain | integer | Parallel requests per website. Default 2. |
minDelayPerDomainMs | integer | Minimum delay between request starts to one domain. Default 500. |
requestTimeoutSecs, maxRetries | integer | Per-request timeout (30) and extra attempts on 429/5xx/network errors (2). |
proxyConfiguration | object | Apify Proxy by default. Custom proxy URLs are supported. |
useResidentialFallback | boolean | Retry 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)
- Collect the URLs. Paste your competitor or reseller product URLs into
urls. For MAP checks add them tomapListwith the minimum advertised price. - Pick a State key. Use a short name such as
widget-map. Runs with the same key are compared with each other. - Run once to create the baseline. Every row comes back as
new. Check thestatuscolumn: rows withneeds-javascriptmean that site publishes no price in static HTML. - Save a Task and schedule it (daily or weekly). From the second run on, rows show
changedwith the previous price, and MAP violations appear in themapdataset view. - Connect alerts. Add a
webhookUrl, or attach an Apify integration (Slack, email, Google Sheets, Make) to the Task and filter the dataset bychange = changedormapViolation = 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
webhookUrlto receive a JSON body withchanges,violationsand a summary (up to 200 rows each, with atruncatedflag). - n8n, Make, Zapier: use the Apify module, run the Actor from a schedule and branch on
changeormapViolation. - 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.
Legal / ethical use
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
- Prozorro & TED Tenders Scraper - public tenders from Ukraine and the EU
- Billing & Payment Stack Detector - which payment gateway a website uses
- Website Technology Detector - tech stack of any site
- Decision-Maker Contacts - people and roles at a company
Related searches
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.