Minimum Advertised Price Monitor - Evidence Rows
Pricing
from $60.00 / 1,000 violations
Minimum Advertised Price Monitor - Evidence Rows
Flag advertised prices below MAP across retailers with per-row evidence URLs, checked timestamps and seller identity.
Pricing
from $60.00 / 1,000 violations
Rating
0.0
(0)
Developer
steadyworks
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
20 days ago
Last modified
Categories
Share
Flag advertised prices below MAP across up to 200 retailer pages per run, and get one evidence row per below-minimum price with URL, checked timestamp, and seller identity. This Minimum Advertised Price Monitor checks the product URLs you list against the minimum advertised prices you set under your unilateral policy — one list in, MAP evidence rows out, no API key needed. Click Try for free — input opens prefilled. Start as-is, check rows, then raise the cap.
What it does / What MAP data you get
You set the minimum advertised price per product under your unilateral policy. A dealer advertises below it and you hear about it from a customer — every day it stays live, the discount comes out of your margin. This Actor checks each URL you list and returns one row per below-minimum advertised price with evidence fields. Review-only: forward rows only after you verify.
- Below-MAP evidence rows — one row per advertised price below your minimum, with
evidenceUrl,checkedAt, seller identity, shortfall in USD and percent, and a severity grade. - Quiet when compliant — run 1 seeds the baseline and previews current rows; later runs emit only changes (
new/deeper-cut), so a compliant portfolio stays silent instead of spamming rows. - Recovery rows on request — set
alertModetoviolations-and-recoveriesand SKUs back at or above MAP emitrecoveryrows. - Retailer and portfolio rollups — one compliance row per retailer hostname plus a single-row portfolio summary, alongside the full
MAP_COMPLIANCE_REPORTJSON. - Spend caps you control — per-run
maxSpendUsdplus the platform max-charge limit; a capped run lands counts plus the report instead of a surprise bill. - Verify-before-enforce on every row — each violation and recovery row carries the unilateral-policy notice; nothing is edited, nothing is sent, nothing is guessed.
Quick start
- Click Try for free — the input opens prefilled with 2 demo products and
storeName: demo-store(a seconds-long run under 1¢: 2 checks). - Keep the prefill for your first run, or swap in 1–2 of your own public HTTPS product page URLs with their minimum advertised prices.
- Keep
maxSpendUsd: 2for the first run — the run stops before billing past your cap. - Click Save & Start, then open the Dataset tab (
defaultholds the violation rows) and theMAP_COMPLIANCE_REPORTrecord. - Once the row shape looks right, paste your full list (up to 200 URLs) — the spreadsheet prompt under Input converts sheet rows to JSON in about a minute.
Trial billing on step 1 is honest: $0 on paid-plan trial (5 runs / 7 days); on free plans the prefilled run bills prepaid credit from run 1 (platform rule, still under 1¢ for the demo).
Input
Minimal JSON (required fields only, byte-exact with the form prefill — copy, paste, start):
{"targets": [{ "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html", "mapPrice": 60, "sku": "attic" },{ "url": "https://books.toscrape.com/catalogue/sharp-objects_997/index.html", "mapPrice": 40, "sku": "sharp" }],"storeName": "demo-store"}
Full JSON (every default shown):
{"targets": [{ "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html", "mapPrice": 60, "sku": "attic" },{ "url": "https://books.toscrape.com/catalogue/sharp-objects_997/index.html", "mapPrice": 40, "sku": "sharp" }],"authorizedSellers": [],"alertMode": "violations-only","maxSpendUsd": 2,"capMode": "capped","storeName": "demo-store"}
| field | type | required | default | units | limits |
|---|---|---|---|---|---|
targets | array of objects | yes | — | 1–200 items per run | each item: url required string, ^https://, 1–2048 chars, public host only (http://, IP literals, localhost / private hosts, and userinfo rejected at input); mapPrice required number, 0.01–1000000, plain number, no currency symbols; sku optional string, 1–256 chars, ^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$, defaults to the URL — reuse identically every run (case-sensitive: Boots and boots are different products; without a stable sku, below-MAP prices cannot be confirmed and count as unmatched); currency optional string, default USD, ^[A-Z]{3}$ |
authorizedSellers | array of strings | no | [] | at most 50 hostnames, each at most 253 chars | plain non-empty hostnames, case-insensitive exact match — list subdomains explicitly (e.g. shop.example.com); trailing dots ignored; matching hosts emit no violation or recovery rows (checks still bill) |
alertMode | string enum | no | violations-only | — | violations-only | violations-and-recoveries — anything else rejected; recoveries emit only in the second mode |
maxSpendUsd | number | no | 2 | USD per run | 0.05–50; the run stops before billing past the cap, publishing counts plus the report instead of evidence rows |
capMode | string enum | no | capped | — | capped | complete-input — capped bills up to maxSpendUsd, then lands counts-only; complete-input processes the whole list in one run with the worst case quoted before anything bills (never above $0.08 snapshots / $20.00 violations / $4.00 recoveries per run); schedules and task runs always run capped |
storeName | string | yes | — (prefills demo-store) | portfolio key, max 63 chars | ^[a-z0-9][a-z0-9-]{1,62}$; the same name plus the same targets reuses one baseline — a different name forks a separate baseline |
Mode rules: duplicate urls (after canonicalization) collapse to one check; a duplicated sku fails the run; a currency mismatch against the baseline is treated as no change and never flagged; split portfolios above 200 pages across scheduled runs.
Job-titled recipes (one line each): single-dealer check (minimal JSON above) · reseller-list audit (up to 200 targets with sku on each) · recovery watch (add "alertMode": "violations-and-recoveries") · authorized-seller exclusion (add "authorizedSellers": ["shop.example.com"]) · tight-budget probe (add "maxSpendUsd": 0.5).
Output
Violations land in the default dataset, rollups in three named datasets, and the run summary in the key-value store — every buyer-facing row goes to a dataset; run state stays in KV:
| surface | key | content |
|---|---|---|
dataset default | violations | one row per advertised price below MAP |
dataset recoveries | recoveries | one row per SKU back at or above MAP (only when alertMode is violations-and-recoveries) |
dataset retailer_summary | retailer summary | one row per retailer hostname: retailer, complianceRate, violationCount, riskLevel (High / Medium / Low) |
dataset category_summary | category summary | single-row rollup: violationCount, mostViolatedSku (null when zero violations) |
| key-value store | MAP_COMPLIANCE_REPORT | run report: storeName, checkedAt, notice, violations, retailerSummary, categorySummary, recoveryCount, spendUsd, chargeId, blockCounts, allFail, baselineReset, candidateCapHit, lockedCount, llmCalls |
Violation row fields:
| field | type | notes |
|---|---|---|
sku | string | your stable product identifier |
retailer | string | hostname of the listing URL |
advertisedPrice | number | current advertised price only, never was-now strike-through |
mapPrice | number | your unilateral policy floor from input |
shortfallUsd / shortfallPct | numbers | how far below MAP, in USD and percent |
severity | enum minor / moderate / severe | minor under 5%, moderate 5–10%, severe over 10% |
seller | string | seller identity where the page states one |
evidenceUrl | string | the exact page checked |
checkedAt | string | ISO timestamp of the check |
notice | string | verify-before-enforce: advertised price only under a unilateral minimum advertised price policy; independently verify before any action. |
Illustrative row shape on the prefill host (shortened for viewing — field names are exact, values illustrate the shape, not a live finding):
{"sku": "attic","retailer": "books.toscrape.com","advertisedPrice": 51.77,"mapPrice": 60,"shortfallUsd": 8.23,"shortfallPct": 13.7,"severity": "severe","seller": "books.toscrape.com","evidenceUrl": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html","checkedAt": "2026-09-14T00:00:00.000Z","notice": "verify-before-enforce: advertised price only under a unilateral minimum advertised price policy; independently verify before any action."}
Partial-failure semantics (never a guessed price): unreachable or oversize pages skip for that URL with $0 charge; login-walled pages yield requiresAuth ($0, never a guessed price); a run that trips maxSpendUsd publishes counts plus the report with no evidence rows — raise the cap and re-run to collect rows; unmatched counts below-MAP prices whose identity could not be confirmed — add sku values to convert them into evidence rows; if a run halts mid-charge, billed checks stand and a retry attempts only the remainder (billed charges stand, no void API).
Canonical identifiers: events map-snapshot, map-violation, map-recovery; datasets default, recoveries, retailer_summary, category_summary; key-value store record MAP_COMPLIANCE_REPORT; every violation and recovery row carries evidenceUrl + checkedAt + notice. (map-start is retired — no charge call; requiresAuth is a $0 reason label on misses + report, never a billing event.)
How much will it cost?
from $0.32 / 1,000 URL checks (checks = map-snapshot events, Gold tier and above). The billable noun is the event: map-snapshot fires once per URL iff a price literal was actually read (S = prices actually read); walled (requiresAuth), failed, and skipped pages bill $0. map-violation fires per emitted violation row, map-recovery per emitted recovery row (only in violations-and-recoveries mode). There is no per-run start charge — including all-fail runs. authorizedSellers exclusions emit no row and bill $0 beyond their check. This meter is under review — the table below is the current live meter stated with its quiet costs, and it makes no frequency or revenue promises.
| plan | checks / 1k (map-snapshot) | violations / 1k (map-violation) | recoveries / 1k (map-recovery) |
|---|---|---|---|
| Free (= base) | $0.40 | $100.00 | $20.00 |
| Bronze (= base) | $0.40 | $100.00 | $20.00 |
| Silver (−10%) | $0.36 | $90.00 | $18.00 |
| Gold (−20%) | $0.32 | $80.00 | $16.00 |
| Platinum (−20%) | $0.32 | $80.00 | $16.00 |
| Diamond (−20%) | $0.32 | $80.00 | $16.00 |
Base single-run prices: check $0.0004, violation $0.10, recovery $0.02. Formula: cost = S×0.0004 + V×0.10 + R×0.02 (S = prices actually read). The $5 trial credit covers about 12,500 quiet URL checks (≈ 125 daily 100-URL runs) or, for example, 49 violations plus 100 checks ($5.00). A quiet portfolio of 100 URLs checked daily runs about $1.20/mo on base (3,000 checks); paid tiers bring the same portfolio to about $1.08 (Silver) or $0.96 (Gold and above).
Worked math (base tier): demo (2 URLs, 0 violations) = 2×0.0004 = $0.0008 — under a cent. Small dealer list (20 URLs, 1 violation) = 20×0.0004 + 1×0.10 = ≈$0.11 per run, ≈$3.24/mo on a daily schedule plus any further violations. Spike month (100 URLs/day, 20 violations) = 3,000×0.0004 + 20×0.10 = ≈$3.20/mo. All-walled month (no price read on any page) = $0.00. Capped run (200 checks + 30 violations) = 200×0.0004 + 30×0.10 = $3.08 — set maxSpendUsd to 3.09 or higher to keep the evidence rows.
Completeness mode (one click, no billing field): keep capMode: capped (default) for the cap behavior above, or set capMode: complete-input to process your entire input list in one run instead of splitting it. The run quotes its worst case from YOUR input before anything bills (Y = N×$0.0004 snapshots + N×$0.10 violations + N×$0.02 recoveries for N targets) and never bills above the frozen per-event ceilings ($0.08 snapshots / $20.00 violations / $4.00 recoveries for the 200-target schema max; the $24.08 sum is informational, never a gate). The buyer max-charge pin still lowers any run; schedules and task runs always run capped; lifted trips land counts-only SUCCEEDED (lifted-cap-trip). Store promise: Max $2/run default (maxSpendUsd 0.05–50) for capped runs — lands SUCCEEDED at the cap; you pay more only if you turn on Complete my input (worst case computed from your input, never above $0.08/$20.00/$4.00 per event).
Cap, floor, and quiet-cost disclosure: maxSpendUsd (0.05–50, default 2) is your per-run cost ceiling; a buyer-set platform max total charge (ACTOR_MAX_TOTAL_CHARGE_USD) is respected the same way, and charging stops on eventChargeLimitReached — capped runs land counts plus the report, never an abort. Quiet costs stated plainly: steady-state re-checks bill checks while emitting zero new rows by design; requiresAuth pages bill $0 with no price delivered. The listed event prices are the whole meter — no separate compute, proxy, or AI add-on is asserted; the Console Pricing tab price is authoritative.
event:map-snapshot | $/1k per tier Free/Bronze/Silver/Gold/Platinum/Diamond 0.40/0.40/0.36/0.32/0.32/0.32 | includes_usage:false | billed_on_empty:false | caps:[targets-200,maxSpendUsd] | gate:price-read-onlyevent:map-violation | $/1k per tier Free/Bronze/Silver/Gold/Platinum/Diamond 100.00/100.00/90.00/80.00/80.00/80.00 | includes_usage:false | billed_on_empty:false | caps:[targets-200,maxSpendUsd]event:map-recovery | $/1k per tier Free/Bronze/Silver/Gold/Platinum/Diamond 20.00/20.00/18.00/16.00/16.00/16.00 | includes_usage:false | billed_on_empty:false | caps:[targets-200,maxSpendUsd,alertMode]note:requiresAuth | $0 reason-label-only (misses + report; never billed) | map-start retired (no charge call)
Trial / Free tier
$5 of credit covers roughly 12,500 quiet URL checks — enough to prove the loop before spending anything real. The Actor enforces its own trial in code: 5 runs within 7 days, $0 on paid plans — each trial run covers up to 100 pages (500 checks total); run 1 shows up to 4 current rows plus lockedCount (rows beyond the first 4 are counted and unlock on paid runs); trial data is kept 14 days after the trial ends. On free plans, runs bill per event from prepaid credit from run 1 (platform rule, not ours) — the prefilled 2-product demo still costs under a cent. Click Try for free — input opens prefilled. Start as-is, check rows, then raise the cap. No card is needed for the trial beyond an Apify account; paid plans unlock higher caps plus the tier discounts above.
Compatibility
Works with any server-rendered structured-data shop page. Filed-test coverage (L0 — no payers yet; paid test meter, Oct 2026):
- Shopify page shape — filed test, Oct 2026 (Allbirds sample; not a live-buyer finding)
- WooCommerce page shape — filed test, Oct 2026 (Porter & York sample; not a live-buyer finding)
- BigCommerce page shape — filed test, Oct 2026 (Mountain Crest Gardens sample; not a live-buyer finding)
- Demo fixture — books demo shop catalogue pages (below-MAP row flagged, clean product correctly quiet; fixture-class demo shop, not a retailer checkout)
Not covered: marketplace and big-box bot walls, JavaScript-rendered prices, login-walled pages. These return requiresAuth or fetch-failed — never a guessed price.
Store names belong to their owners; no affiliation, endorsement, or sponsorship implied.
What people use it for + What it does NOT do
- Weekly dealer MAP sweep: schedule the same dealer list and wake up only to change rows.
- Reseller-list audit before a review: bulk-check up to 200 URLs and forward verified rows.
- Authorized-seller exclusion review: keep approved dealers out of violation rows while still checking them.
- Recovery confirmation after a price fix: re-run in
violations-and-recoveriesmode and confirm the SKU is back at or above MAP. - Retailer risk ranking: sort
retailer_summarybyviolationCountandriskLevelto see which dealer needs attention first. - Portfolio rollup for category review: one
category_summaryrow per run for the shelf-level view.
What this Actor does NOT do: it does not edit listings or prices — read-only checks of public pages. It does not read login-walled, marketplace bot-wall, or JavaScript-rendered prices — those return requiresAuth or fetch-failed, never a guessed price. It does not send legal notices or take action — every row is verify-before-enforce, forward only after you verify. Evidence is the row fields (evidenceUrl + checkedAt + notice) — no screenshots.
Use with schedules / webhooks / API / MCP
Schedule (daily quiet-portfolio cadence): Console → Schedules → Add Actor → steadyworks/minimum-advertised-price-monitor, cron daily, with input-JSON overrides carrying only targets plus a distinct storeName per schedule (baselines are per-portfolio; per-run maxSpendUsd times cadence is the spend bound — schedules themselves are free).
Webhook (alert only when rows exist): Actor Detail → Integrations → webhook on ACTOR.RUN.SUCCEEDED, POST JSON with a payload template surfacing {{resource.defaultDatasetId}}, then filter buyer-side on violationCount > 0 (webhooks fire per run status, not per violation — severe-only filtering lives in the receiver or an iPaaS step such as Watch-runs → Get-Dataset-Items → filter severity == severe → Slack/Sheets; always async, never sync Run against the ~30s iPaaS timeout).
MCP (agent calling this Actor through the Apify MCP client):
{ "mcpServers": { "apify": { "command": "npx", "args": ["-y", "@apify/mcp-server"], "env": { "APIFY_TOKEN": "<your-token>" } } } }
Then call-actor on steadyworks/minimum-advertised-price-monitor with the Input JSON above, and poll get-actor-run (or a webhook) for completion — call-actor waits at most ~45s, so never block on it; fetch rows with get-dataset-items.
API one-liners (start, poll, fetch — replace $APIFY_TOKEN):
$curl -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" -d '{"targets":[{"url":"https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html","mapPrice":60,"sku":"attic"}],"storeName":"demo-store","maxSpendUsd":2}' "https://api.apify.com/v2/acts/xeupuwWOQlX587MoH/runs?waitForFinish=45"
const run = await apifyClient.actor('xeupuwWOQlX587MoH').call({ targets: [{ url: 'https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html', mapPrice: 60, sku: 'attic' }], storeName: 'demo-store', maxSpendUsd: 2 });const { items } = await apifyClient.dataset(run.defaultDatasetId).listItems({ limit: 50 });
run = client.actor('xeupuwWOQlX587MoH').call(run_input={'targets': [{'url': 'https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html', 'mapPrice': 60, 'sku': 'attic'}], 'storeName': 'demo-store', 'maxSpendUsd': 2})items = client.dataset(run['defaultDatasetId']).list_items(limit=50).items
Troubleshooting
| symptom | cause | fix |
|---|---|---|
| 0 rows on a run you expected to flag | portfolio already baselined, or prices at or above MAP (runs 2+ emit changes only) | lower one mapPrice below its advertised price and re-run to confirm a row appears |
requiresAuth on marketplace / big-box URLs | login or bot wall — no readable price | swap in public structured-data shop URLs; the Actor never guesses a price |
| counts on the report but no evidence rows | maxSpendUsd cap tripped | raise maxSpendUsd and re-run to collect the rows |
unmatched counts above 0 | below-MAP prices seen but identity unconfirmed | add a stable sku per target and re-run to convert them into evidence rows |
| a price fix never emits a recovery | alertMode is violations-only | set alertMode to violations-and-recoveries and re-run |
| run stops on the charge limit | buyer-set max total charge below the run cost | raise the binding cap and re-run — counts plus the report are preserved |
| oversize or unreachable pages | page skipped for that URL, $0 charge | remove or replace the URL; billed checks stand and a retry attempts only the remainder |
FAQ + legality
Does this change my listings, prices, or data? No — read-only checks of public retailer pages. Nothing is edited, logged into, or sent.
How much will my dealer list cost? See the worked math above: the 2-product demo is under a cent, 20 URLs with 1 violation is about $0.11 a run on base, and a quiet 100-URL daily portfolio is about $1.20/mo plus any violations.
What happens on login-walled or bot-wall pages? They yield requiresAuth ($0, no price) or fetch-failed ($0) — never a guessed price. Use public structured-data shop pages for coverable targets.
Is a flagged row proof of a violation? No — it is an advertised-price signal under your unilateral policy. Every row carries the verify-before-enforce notice: independently verify before any action, and forward rows only after you verify.
Does it process personal data (GDPR)? It reads only the public listing pages you submit (URL, advertised price, seller hostname) and returns price-comparison rows. It requests no personal data, opens no accounts, and touches no login-walled pages. You control the target list — submit only pages you may review.
Which pages may I check (Terms)? Proceed only for public HTTPS retailer pages you may review, under your own unilateral minimum advertised price policy. Respect the target sites' terms; this Actor is review-only tooling, not an enforcement service.
Who do I contact about a charge? Charge disputes go to Apify support within 15 days of the charge; billed charges stand (no void API).
Other Actors you might need
| need | actor |
|---|---|
| full-site crawl to build your URL list before MAP targeting | apify/web-scraper |
Enforcement-letter workflows, screenshot evidence pipelines, and marketplace/big-box coverage live outside this Actor by design — the boundaries under Compatibility and What it does NOT do apply.
Changelog + support
./CHANGELOG.md (0.1 — 2026-10-02 adoption baseline: MAP evidence rows across 4 datasets + MAP_COMPLIANCE_REPORT, map-* events, 5-run/7-day trial, per-run maxSpendUsd cap; 0.2 — 2026-10-03 re-meter per ADR-008: snapshot $0.0004 read-only gate, requiresAuth $0, map-start retired). Report breakage via the Store discussion / Issues tab — replies and fixes stay visible there; charge disputes go to Apify support within 15 days of the charge; no SLA. Store page: https://apify.com/steadyworks/minimum-advertised-price-monitor