MAP Price Policy Evidence Monitor
Pricing
from $100.00 / 1,000 evidence packages
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
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
| Input | Required | What it contains |
|---|---|---|
| Product URLs | Yes, unless you run the sample | url, and optionally sku, gtin, mpn, variantId |
| Catalog | Yes for a real brand | sku, gtin, mpn, title, packSize, brand |
| Policies | Yes for a comparison | policyId, sku, minAdvertisedPrice, currency, effectiveFrom, optional effectiveTo, packSize, appliesToCondition |
| Approved sellers | Yes for identity | sellerId, name, aliases, domains |
| Exemptions | No | exemptionId, 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
- Rejects non-public targets (localhost, private IPs,
file://, credentials in the URL) before any request. - Fetches the product page and, for a Shopify-style URL, the public
products/{handle}.jsondocument. Requests are throttled, retried with backoff, and sent with an identifying User-Agent. - 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. - 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.
- 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. - 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.
- 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.
- Charges
offer-checkedfor a completed, validated check, andevidence-packagethe 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:
| Page | Sample rule | What the sample is meant to show |
|---|---|---|
Graza Hot 'n Sweet Olive Oil Potato Chips (CHP-HS55) | minimum USD 24.00 from 2026-01-01 | The 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.01 | A matched seller whose public price is above the supplied minimum. |
Beardbrand Fox Hunt Men's Cologne (32836920) | minimum USD 0.01 | The 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:
| Status | Meaning |
|---|---|
below_supplied_threshold | Comparable public price is below the minimum you supplied. Confirmed by a second fetch when recheck is on. |
at_or_above_supplied_threshold | Comparable public price is at or above that minimum. |
exempt | A supplied exemption covers this offer. exemptionId says which one. |
not_comparable | Used or refurbished when the rule is for new, a different pack, or a member, coupon, or conditional price. |
currency_mismatch | The page currency and the policy currency differ. No conversion is applied. |
recheck_not_confirmed | The first read was below the threshold and the second read did not agree. No package is written. |
product_not_matched | The page did not join a catalog row by SKU, GTIN, or MPN. |
no_policy | No supplied rule is effective for that SKU on the observation date. |
blocked, via coverage | The 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.
| Event | When it is charged | Price |
|---|---|---|
offer-checked | A validated offer check was delivered. One charge per URL, variant, price, and currency per UTC day. | $0.01 |
evidence-package | A 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.
| Workload | Events | Charge |
|---|---|---|
| Sample run: 3 pages, 1 new package | 3 offer checks + 1 package | $0.13 |
| One brand, 200 URLs checked each weekday for a month (about 22 runs) and 30 new incidents | 4,400 offer checks + 30 packages | $47.00 |
| The workload used to illustrate a heavier month: 8,000 checks and 200 new packages | 8,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-packagefor the review queue, orpolicyStatus=below_supplied_thresholdfor confirmed rows. - n8n or Make. HTTP node to run the Actor, then a dataset node. Map
sourceUrl,productSku,price,currency,policyStatus,sellerMatchNote,evidenceId, andpageHashinto the sheet or ticket. - Google Sheets. Export the dataset as CSV. Every field is a column.
notComparableReasonsandchangedFieldsare semicolon-separated. - Key-value store.
OUTPUTis the run summary. Screenshots, when requested, are PNG records namedscreenshot-{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
screenshotStatusisskipped_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.txtpython3 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 testsMAP_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.