Minimum Advertised Price Monitor - Evidence Rows avatar

Minimum Advertised Price Monitor - Evidence Rows

Pricing

from $60.00 / 1,000 violations

Go to Apify Store
Minimum Advertised Price Monitor - Evidence Rows

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

steadyworks

Maintained by Community

Actor 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 alertMode to violations-and-recoveries and SKUs back at or above MAP emit recovery rows.
  • Retailer and portfolio rollups — one compliance row per retailer hostname plus a single-row portfolio summary, alongside the full MAP_COMPLIANCE_REPORT JSON.
  • Spend caps you control — per-run maxSpendUsd plus 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

  1. Click Try for free — the input opens prefilled with 2 demo products and storeName: demo-store (a seconds-long run under 1¢: 2 checks).
  2. 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.
  3. Keep maxSpendUsd: 2 for the first run — the run stops before billing past your cap.
  4. Click Save & Start, then open the Dataset tab (default holds the violation rows) and the MAP_COMPLIANCE_REPORT record.
  5. 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"
}
fieldtyperequireddefaultunitslimits
targetsarray of objectsyes—1–200 items per runeach 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}$
authorizedSellersarray of stringsno[]at most 50 hostnames, each at most 253 charsplain 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)
alertModestring enumnoviolations-only—violations-only | violations-and-recoveries — anything else rejected; recoveries emit only in the second mode
maxSpendUsdnumberno2USD per run0.05–50; the run stops before billing past the cap, publishing counts plus the report instead of evidence rows
capModestring enumnocapped—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
storeNamestringyes— (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:

surfacekeycontent
dataset defaultviolationsone row per advertised price below MAP
dataset recoveriesrecoveriesone row per SKU back at or above MAP (only when alertMode is violations-and-recoveries)
dataset retailer_summaryretailer summaryone row per retailer hostname: retailer, complianceRate, violationCount, riskLevel (High / Medium / Low)
dataset category_summarycategory summarysingle-row rollup: violationCount, mostViolatedSku (null when zero violations)
key-value storeMAP_COMPLIANCE_REPORTrun report: storeName, checkedAt, notice, violations, retailerSummary, categorySummary, recoveryCount, spendUsd, chargeId, blockCounts, allFail, baselineReset, candidateCapHit, lockedCount, llmCalls

Violation row fields:

fieldtypenotes
skustringyour stable product identifier
retailerstringhostname of the listing URL
advertisedPricenumbercurrent advertised price only, never was-now strike-through
mapPricenumberyour unilateral policy floor from input
shortfallUsd / shortfallPctnumbershow far below MAP, in USD and percent
severityenum minor / moderate / severeminor under 5%, moderate 5–10%, severe over 10%
sellerstringseller identity where the page states one
evidenceUrlstringthe exact page checked
checkedAtstringISO timestamp of the check
noticestringverify-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.

planchecks / 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-only
event: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-recoveries mode and confirm the SKU is back at or above MAP.
  • Retailer risk ranking: sort retailer_summary by violationCount and riskLevel to see which dealer needs attention first.
  • Portfolio rollup for category review: one category_summary row 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

symptomcausefix
0 rows on a run you expected to flagportfolio 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 URLslogin or bot wall — no readable priceswap in public structured-data shop URLs; the Actor never guesses a price
counts on the report but no evidence rowsmaxSpendUsd cap trippedraise maxSpendUsd and re-run to collect the rows
unmatched counts above 0below-MAP prices seen but identity unconfirmedadd a stable sku per target and re-run to convert them into evidence rows
a price fix never emits a recoveryalertMode is violations-onlyset alertMode to violations-and-recoveries and re-run
run stops on the charge limitbuyer-set max total charge below the run costraise the binding cap and re-run — counts plus the report are preserved
oversize or unreachable pagespage skipped for that URL, $0 chargeremove 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

needactor
full-site crawl to build your URL list before MAP targetingapify/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