MAP Price Policy Evidence Monitor avatar

MAP Price Policy Evidence Monitor

Pricing

from $100.00 / 1,000 evidence packages

Go to Apify Store
MAP Price Policy Evidence Monitor

MAP Price Policy Evidence Monitor

Compare public product-page prices with a brand's own advertised-price rules, reconcile each seller against the supplied approved list, and save one evidence package per material price incident.

Pricing

from $100.00 / 1,000 evidence packages

Rating

0.0

(0)

Developer

Olalekan Johnson

Olalekan Johnson

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

A brand with its own advertised-price rules can point this Actor at product URLs and get a review file: what the page showed, which catalog row and seller it matched, and whether that public price sits below a threshold the brand supplied. When a comparable offer is confirmed below that threshold, the Actor writes one evidence package for that incident and does not write another package on later runs while the same price is still on the page.

It reports observations. It does not send enforcement messages, and it does not reach a legal conclusion. A seller that is absent from the list you supplied is described as seller not matched to the supplied list.

Unofficial, and not affiliated with Shopify, Graza, OLAPLEX, or Beardbrand. The sample reads public product pages those stores already publish. Page text stays on the store; this Actor keeps the extracted fields, a SHA-256 of the bytes it read, and an optional screenshot.

Contact: plusonecares@gmail.com

Who it is for

Brand operations and agency teams who already have:

  • a catalog with SKU, GTIN, or MPN, plus pack size
  • advertised-price thresholds and the dates they apply
  • an approved-seller list with aliases and domains
  • exemptions, when a lower public price is expected
  • the exact product URLs to watch

The useful output is a queue a person can review, with the source URL, the time, the fields that were read, and the page hash.

What you supply

InputRequiredWhat it contains
Product URLsYes, unless you run the sampleurl, and optionally sku, gtin, mpn, variantId
CatalogYes for a real brandsku, gtin, mpn, title, packSize, brand
PoliciesYes for a comparisonpolicyId, sku, minAdvertisedPrice, currency, effectiveFrom, optional effectiveTo, packSize, appliesToCondition
Approved sellersYes for identitysellerId, name, aliases, domains
ExemptionsNoexemptionId, optional sku and sellerId, reason, dates, condition, optional floorPrice

JSON arrays and CSV text are both accepted. CSV columns use the same names. In the seller CSV, separate aliases and domains with |.

Leave the URL list and the catalog empty and the Actor runs the built-in sample. That sample is also the prefilled input, so Apify's daily check has a public, stable run.

What a run does

  1. Rejects non-public targets (localhost, private IPs, file://, credentials in the URL) before any request.
  2. Fetches the product page and, for a Shopify-style URL, the public products/{handle}.json document. Requests are throttled, retried with backoff, and sent with an identifying User-Agent.
  3. Reads the selected variant, seller, condition (new, used, refurbished), pack quantity, displayed price, currency, and whether that offer is a member price, a coupon price, or another conditional price. A promotion mentioned elsewhere on the page is recorded and does not, by itself, exclude the offer.
  4. Joins the offer to your catalog by the SKU you supplied or by GTIN, MPN, or SKU on the page. A title-only candidate is reported and is not strong enough to apply a threshold.
  5. Joins the seller by domain first (high confidence), then by exact name (medium), then by a fuzzy name (medium or low). Two equally plausible sellers stay ambiguous.
  6. Compares the price with the policy that is effective on the observation date. Currency must match. Pack and condition must match. There is no currency conversion.
  7. When the comparable price is below the supplied minimum, fetches the page again. The evidence package is written only when the second read shows the same price and the same result.
  8. Charges offer-checked for a completed, validated check, and evidence-package the first time that material incident is delivered. The same URL, variant, price, and currency are not charged again on the same UTC day. The same incident is not packaged again while the price is unchanged.

Coverage is one of complete, partial, empty, blocked, or unsupported. A blocked or empty page is still a dataset row, and it is not charged.

Sample input

The prefilled run watches three public Shopify product pages:

PageSample ruleWhat the sample is meant to show
Graza Hot 'n Sweet Olive Oil Potato Chips (CHP-HS55)minimum USD 24.00 from 2026-01-01The public price is below that sample minimum, so a confirmed run writes one evidence package.
OLAPLEX The Healthy Hair Travel Set (VB000030)minimum USD 0.01A matched seller whose public price is above the supplied minimum.
Beardbrand Fox Hunt Men's Cologne (32836920)minimum USD 0.01The product is in the catalog. Beardbrand is not on the approved-seller list.

The USD 24.00 and USD 0.01 figures are sample thresholds, chosen so the demo has one below-threshold offer and two offers above their thresholds. They are not those brands' policies. Prices on the stores can change. If the chip price later moves to USD 24.00 or above, that row becomes at_or_above_supplied_threshold and the dataset is still filled.

A test run of this sample in October 2026 returned three complete offer checks and one evidence package, with the cologne row carrying the seller note seller not matched to the supplied list.

Output

Dataset rows are flat, so a webhook, a sheet, or a CSV export can filter them. recordType is offer-check or evidence-package.

Offer check from the sample shape. Price values in these examples are illustrative, not the stores' actual prices. On a pay-per-event run, chargedOfferCheck is true. Before that pricing is configured, the same row is delivered with chargedOfferCheck false and chargeNote set to platform_did_not_charge.

{
"recordType": "offer-check",
"monitorId": "sample",
"sourceUrl": "https://www.graza.co/products/hot-n-sweet-olive-oil-potato-chips",
"observedAt": "2026-10-23T15:04:11.000000+00:00",
"rechecked": true,
"recheckAgreed": true,
"coverage": "complete",
"extractionMethod": "shopify_json",
"pageHash": "439942313ba358f8a7e5f8803e12bec41bfdb198ff27650e8f2a2462fd03295c",
"productSku": "CHP-HS55",
"productGtin": "850062393370",
"variantId": "48239480438998",
"variantSelection": "sku",
"sellerMatchStatus": "matched",
"sellerMatchConfidence": "high",
"sellerMatchMethod": "domain",
"sellerId": "graza",
"sellerName": "Graza",
"sellerMatchNote": null,
"condition": "new",
"conditionSource": "json_ld",
"packQuantity": 1,
"packSource": "assumed_single",
"price": 20.0,
"priceCents": 2000,
"currency": "USD",
"memberPrice": false,
"couponPrice": false,
"conditionalPrice": false,
"promotionMentioned": true,
"policyId": "SAMPLE-CHIPS",
"policyMinAdvertisedPrice": 24.0,
"policyCurrency": "USD",
"policyStatus": "below_supplied_threshold",
"comparable": true,
"priceDifference": -4.0,
"evidenceStatus": "created",
"evidenceId": "ev_4a1b2d079991ee64a9b4bd2e",
"changedFromPrevious": true,
"chargedOfferCheck": true
}

The evidence package for that incident repeats the extracted fields and adds the confirmation time, the confirmation page hash, screenshotStatus, and a factual snippet. chargedEvidencePackage is true only on the run that first delivers that package.

A seller that is not on your list looks like this:

{
"recordType": "offer-check",
"sourceUrl": "https://www.beardbrand.com/products/fox-hunt-mens-cologne",
"productSku": "32836920",
"price": 30.0,
"currency": "USD",
"policyStatus": "at_or_above_supplied_threshold",
"sellerMatchStatus": "not_matched",
"sellerMatchNote": "seller not matched to the supplied list",
"evidenceStatus": "not_applicable"
}

policyStatus values you will actually see:

StatusMeaning
below_supplied_thresholdComparable public price is below the minimum you supplied. Confirmed by a second fetch when recheck is on.
at_or_above_supplied_thresholdComparable public price is at or above that minimum.
exemptA supplied exemption covers this offer. exemptionId says which one.
not_comparableUsed or refurbished when the rule is for new, a different pack, or a member, coupon, or conditional price.
currency_mismatchThe page currency and the policy currency differ. No conversion is applied.
recheck_not_confirmedThe first read was below the threshold and the second read did not agree. No package is written.
product_not_matchedThe page did not join a catalog row by SKU, GTIN, or MPN.
no_policyNo supplied rule is effective for that SKU on the observation date.
blocked, via coverageThe store did not return a page. That row is not charged.

The run summary is the OUTPUT record in the default key-value store. It includes counts, coverage, charged units, the estimated charge at the Actor's prices, and one line per URL. The named store map-price-policy-state (change it with stateStoreName) keeps the baseline and the incident ledger under monitor-{monitorId}. A run that cannot read that record does not replace it and does not bill a new package.

alertOnlyOnChange keeps the summary and writes dataset rows only when the price, policy status, seller match, condition, pack, currency, or coverage changed, plus any new evidence package.

Pricing

This Actor uses pay-per-event pricing. There is no per-dataset-item event. The platform start event is $0.005 per GB and is covered below.

EventWhen it is chargedPrice
offer-checkedA validated offer check was delivered. One charge per URL, variant, price, and currency per UTC day.$0.01
evidence-packageA new material incident was delivered after a confirming recheck. The same price does not create another package on the next run. A changed price is a new incident.$0.10

Failed, blocked, empty, and ambiguous reads are not charged. A recheck that disagrees is not charged. A retry of a result already charged that UTC day is not charged again.

WorkloadEventsCharge
Sample run: 3 pages, 1 new package3 offer checks + 1 package$0.13
One brand, 200 URLs checked each weekday for a month (about 22 runs) and 30 new incidents4,400 offer checks + 30 packages$47.00
The workload used to illustrate a heavier month: 8,000 checks and 200 new packages8,000 + 200$100.00

A 512 MB run also incurs the platform start event. At $0.005 per GB that is about $0.0025 per run. Screenshot and browser runs use 2048 MB, so the start event is about $0.01 and the run is slower. maxChargeUsd stops the run using the prices above. The run also stops when Apify reports that the account's max total charge has been reached. Rows that were not charged are not presented as billed.

When you run it locally without pay-per-event, the Actor still writes the dataset and sets chargedOfferCheck and chargedEvidencePackage to false, with chargeNote of platform_did_not_charge.

Scheduling

Run it daily per brand. Give each brand its own monitorId. The next day bills a fresh offer-checked event for each validated URL and writes a package only when a new material incident appears, or when a previously open incident's price changes. When an offer is no longer below the threshold, the offer row says incidentResolution: no_longer_below_supplied_threshold and the stored incident is closed. That resolution is not an evidence package and is not billed as one.

An errored run does not wipe a good baseline. URLs that fail today keep yesterday's stored observation.

Integrations

  • Webhook. Subscribe to the run's succeeded event. Read the default dataset items. Filter recordType = evidence-package for the review queue, or policyStatus = below_supplied_threshold for confirmed rows.
  • n8n or Make. HTTP node to run the Actor, then a dataset node. Map sourceUrl, productSku, price, currency, policyStatus, sellerMatchNote, evidenceId, and pageHash into the sheet or ticket.
  • Google Sheets. Export the dataset as CSV. Every field is a column. notComparableReasons and changedFields are semicolon-separated.
  • Key-value store. OUTPUT is the run summary. Screenshots, when requested, are PNG records named screenshot-{evidenceId}.

Scope and limits

  • Static extraction covers Shopify product JSON, JSON-LD offers, and simple HTML price metadata. A browser is used only when you enable a screenshot or a rendered retry, and only after the static fetch.
  • Screenshots and rendered retries need a run with at least 1024 MB. The default memory rises to 2048 MB when those switches are on. If memory is lower, the package is still written and screenshotStatus is skipped_low_memory.
  • Condition defaults to new, and pack size defaults to 1, when the page does not say otherwise. Both are labeled (assumed_new_no_signal, assumed_single).
  • Out-of-stock pages can still show an advertised price. Availability is recorded. It does not by itself remove the offer from the comparison.
  • Shipping, tax, and checkout prices are out of scope. The price is the one displayed on the product page.
  • Marketplace pages where several sellers share one URL need a seller name on the page. A domain match is the high-confidence path, which fits the brand's own store and approved retailer domains.
  • Overlapping policies that disagree are policy_ambiguous. No package is written.
  • This version does not convert currency, does not log into stores, and does not draft letters to sellers.

Run it

pip install -r requirements.txt
python3 main.py

With no INPUT.json, or with {}, the sample input runs. Memory defaults to 512 MB for static runs and 2048 MB when a screenshot or a browser retry is requested. The memory ceiling is 2048 MB.

$apify push

Tests:

python3 -m pytest tests
MAP_LIVE=1 python3 -m pytest tests/test_live_smoke.py

FAQ

How is a seller who is not on my list described? The row says seller not matched to the supplied list. You decide what that means for your program.

Will every daily run create another evidence package? No. A package is tied to the product, seller, URL, condition, pack, currency, policy, and price. The same price on the next run points at the existing package and bills only the offer check, once per UTC day.

What if the page shows a coupon or a member price? If the extracted offer itself is a coupon, member, or other conditional price, the row is not_comparable and no package is written. A promotion mentioned somewhere else on the page sets promotionMentioned and leaves the public price eligible.

What if two reads disagree? policyStatus is recheck_not_confirmed. Nothing is billed for that URL on that attempt, and the stored baseline is left as it was.

Can I bring a spreadsheet? Yes. Paste CSV into the CSV fields, or pass the same rows as JSON arrays.

Is the sample a real policy? No. It is a public demo. The chip threshold is set above the price that was on the page when the sample was prepared, so the first successful run can show an evidence package.