Catalog Matcher: Competitor Product Matching & Price Gaps avatar

Catalog Matcher: Competitor Product Matching & Price Gaps

Pricing

from $1.60 / 1,000 matched pairs

Go to Apify Store
Catalog Matcher: Competitor Product Matching & Price Gaps

Catalog Matcher: Competitor Product Matching & Price Gaps

Match your product catalogue against competitors' by barcode (GTIN/UPC/EAN), MPN, SKU, model number or title, with match method and confidence per pair, price gap %, and the products only one side sells. Reads Shopify, any-store and Amazon datasets or your own rows.

Pricing

from $1.60 / 1,000 matched pairs

Rating

0.0

(0)

Developer

CleanPull

CleanPull

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

6 hours ago

Last modified

Share

Give it your product catalogue and one or more competitors': datasets from Shopify Store Scraper, Any Store Product Monitor or Amazon Product Scraper, or your own rows. It finds the same products on both sides, by barcode (GTIN, UPC, EAN), manufacturer part number, SKU, model number or title, and says how each pair was matched and how sure it is. Every pair comes with both prices and the price gap. It also lists what they sell that you don't, and what you sell that they don't, and a summary per competitor with overlap and a price index. From $2 per 1,000 matched pairs.

What you can do with it

  • "Which of my products does this competitor also sell, and at what price?" One row per matched pair: your price, theirs, the gap in %, and who is cheaper.
  • "What do they sell that I don't?" Gap rows for their products with no match among yours. By default only in brands you already carry (Gaps in your brands only): the ranges you could add first. Turn it off for every product they sell that you don't.
  • "Where am I the only seller?" Gap rows the other way: your products they don't carry.
  • "Am I cheaper or dearer overall?" The summary row per competitor gives the median price index (their price as % of yours), how many pairs are cheaper on each side, and the overlap.
  • "Tell me when that changes." Schedule it with a watchlist name and Changes only: you get only new matches, price moves, new gaps, and pairs or gaps that are gone.
  • "Tell me when a competitor sells something I sell for less." Give your store and theirs as Shopify domains (Competitor stores), an Undercut alert and a watchlist name, and schedule it weekly. One run reads the stores, compares them and reports the products where they newly undercut you. No other task or webhook is needed.
  • "Feed it to a spreadsheet or an AI agent." Flat rows with the same field names for every source, and one dataset view per job.

How it matches

Each competitor product goes down a ladder and stops at the first step that matches. matchMethod and confidence (0–1) say which step it was:

StepmatchMethodMatches onConfidence
1gtinThe same barcode (UPC, EAN-13, EAN-8, GTIN-14, written any way, check digit verified), on the product or any variant1 (0.9 for an Amazon product found by a barcode search whose page doesn't show the barcode)
2mpnThe same manufacturer part number (also found in the other side's SKU field), same brand0.95 (0.83 if one side has no brand)
3skuThe same SKU, or the style code a product's variant SKUs share (HZ8CL-02-XS, HZ8CL-02-SM → HZ8CL-02), same brand0.9 (0.78)
4modelThe same model number in the title or part number (WH-1000XM5), same brand, titles that agree0.8–0.95
5titleToken-set ratio of the titles ≥ 90, within the same brand, once brand, filler words and the variant's colour names are removedup to 0.9

The title step is stricter than the ratio alone:

  • Gender must agree. Men's never pairs with women's, and a lone "W" or "M" counts.
  • Colours must agree, where both titles name one.
  • Numbers must agree (55L, 13, 105).
  • A word only one title has names another model, and blocks the match. "Arizona Suede LE" is not "Arizona Suede"; "Atom SL Hoody" is not "Atom Hoody". Words for the kind of product (jacket, shoes, backpack) and model years don't count.
  • Doubts lower the confidence by 0.1 each: a gender named on one side only, or a number on one side only.

In tests on 146 title pairs from real outdoor stores, checked by hand, 4 were wrong (2.7%), and none of the 124 with confidence 0.85 or more. All 4 had "gender on one side only" in matchedOn. Filter on confidence to choose your own trade-off.

Code matches have checks of their own:

  • Brands compare by whole word: "Bedrock" is "Bedrock Sandals", but "On" is not "Salomon".
  • Amounts must agree on a model-number match: 128 GB and 256 GB, a 2-pack and a 4-pack are other products.
  • Without a brand on one side, a part number or SKU match needs titles that don't disagree, and a SKU also needs similar titles.

Pairs are one to one, best confidence first. When a competitor lists the same product several times (each colour as its own product, or a second listing), the extras are shown on the pair as alsoMatched. They are not gaps and are not billed.

What makes the data right

  • Prices in different currencies are never compared. Such a pair has no price gap, and priceNote says why, for example "different currencies (USD and CAD): prices not compared". The summary counts these pairs. A currency missing on one side is compared as the same currency, and the note says so.
  • The matched variant's price. When a variant's barcode or SKU made the match, its own price is used, not the product's lowest.
  • A source read in part never makes false gaps. If a competitor's dataset fails part-way, that competitor is left out of the run (no rows, no charges, watchlist unchanged) and the run summary says why. If your own catalogue is read only in part, pairs are still returned but no gaps, and the watchlist is not moved.
  • A bare "$" is not assumed to be US dollars. The store's domain decides where it can ($ on a .ca store is CAD); otherwise the currency is unknown and the prices are not compared.
  • Duplicates count once. A product listed twice (same page, or the same barcodes) is matched once, never billed twice, and counted in the summary.
  • Shopify variant rows are merged. Shopify Store Scraper's flat one-row-per-variant output is put back together into products.
  • One dataset, several stores. A Shopify Store Scraper run over your store and three rivals is split by store: name yours in Your store, and each rival becomes its own comparison.
  • Placeholders are not codes. Barcodes with a wrong check digit or all zeros, store-internal numeric SKUs, and codes shared by many products are ignored.
  • Only what you pick is read. Datasets are chosen in the form, which gives this actor read access to those datasets only. It runs with limited permissions and fetches nothing from the web.

Input

FieldWhat it does
yourDatasetId, yourItemsYour catalogue: a dataset, rows, or both
competitorStoresUp to 10 Shopify stores, each read in this run by Shopify Store Scraper and compared
competitorDatasetIds, competitorItemsOne or more competitor catalogues; each store in them is its own comparison
yourStoreYour store's domain: read in this run too with competitorStores and no other catalogue of yours; otherwise it picks your products out of a competitor dataset
titleMatching, minTitleScore, titleWithoutBrandThe title step: on or off, its threshold (default 90), and whether to match without a brand
minConfidencePairs below this confidence are not matches (default 0.6)
gaps, gapsInYourBrandsOnlyGap rows both ways, one way or none; only brands you carry
monitorName, changesOnly, minPriceChangePercentWatchlist: compare with the last run, return only changes, ignore small price moves
alertUndercutPercentMark pairs where they are at least this % cheaper; with a watchlist, report new undercuts (off by default)
maxItemsPerCatalogueProducts read per catalogue (default 50,000)
maxRunCostUsdThe most one run may charge, for scheduled runs (with competitorStores, the store reads included)

Rows you give use these fields: title, brand, price, currency, url, gtin (or upc, ean, barcode), mpn, sku, store, and optionally variants (each with sku, gtin, price).

Example inputs

A weekly undercut watch: your Shopify store against two rivals, read in the same run, reporting products they newly sell at least 5% below your price:

{
"yourStore": "mystore.com",
"competitorStores": ["rival-one.com", "rival-two.com"],
"alertUndercutPercent": 5,
"monitorName": "undercut-watch",
"changesOnly": true,
"maxRunCostUsd": 3
}

Your Shopify store against two rivals, from one Shopify Store Scraper run:

{ "competitorDatasetIds": ["<Shopify Store Scraper dataset id>"], "yourStore": "mystore.com" }

Your Any Store Product Monitor dataset against Amazon products found by the same barcodes, matches only:

{
"yourDatasetId": "<Any Store dataset id>",
"competitorDatasetIds": ["<Amazon Product Scraper dataset id>"],
"gaps": "none"
}

What a rival sells in brands you carry, checked weekly for changes:

{
"yourDatasetId": "<your catalogue>",
"competitorDatasetIds": ["<rival catalogue>"],
"gaps": "they-sell",
"gapsInYourBrandsOnly": true,
"monitorName": "rival-ranges",
"changesOnly": true,
"maxRunCostUsd": 1
}

Output

A real matched pair:

{
"recordType": "match",
"catalogue": "alpenglowsports.com",
"matchMethod": "title",
"confidence": 0.9,
"matchedOn": "title 100",
"yourTitle": "Atom Hoody — Men's",
"yourBrand": "Arc'Teryx",
"yourPrice": 300,
"yourCurrency": "USD",
"yourUrl": "https://www.outdoorsports.com/products/arcteryx-atom_hoody-jackets-2025",
"yourGtin": "623555579557",
"theirTitle": "M Atom Hoody",
"theirBrand": "Arcteryx",
"theirPrice": 300,
"theirCurrency": "USD",
"theirUrl": "https://www.alpenglowsports.com/products/m-atom-hoody",
"priceGapPercent": 0,
"cheaper": "same",
"priceNote": null
}

priceGapPercent is their price against yours: −10 means they are 10% cheaper. cheaper is you, them or same. Also: yourSku, theirSku, theirGtin, yourInStock, theirInStock, and alsoMatched (their other listings of the same product).

A gap row: { "recordType": "gap", "direction": "they-sell", "catalogue": "...", "title", "brand", "price", "currency", "url", "gtin", "sku", "inStock", "youCarryBrand": true }. The direction is they-sell (they sell it, you don't) or you-sell.

A summary row per competitor gives: yourProducts, theirProducts, matched, overlapPercent (of yours), theirOverlapPercent, byMethod, medianPriceIndex (their price as % of yours: 95 means they are 5% cheaper at the median), youAreCheaper, theyAreCheaper, samePrice, currencyMismatches, gapsTheySell, gapsYouSell, alsoMatched, duplicates, and topGapBrands (brands with the most products you don't sell).

With a watchlist name, every row has changes and firstSeen. The change types are:

  • new-match, your-price and their-price (with from and to; a price appearing or disappearing always counts);
  • new-gap;
  • match-lost and gap-closed, reported once when a pair or gap is missing from two runs in a row;
  • undercut (with gapPercent), with Undercut alert set: a pair where they are now at least that much cheaper and were not on the last run, a new pair included. A pair whose price crossed the threshold through moves each smaller than Smallest price change is reported too.

With Undercut alert set, each pair also has undercut: true or false, and each summary gives undercuts and, with a watchlist, newUndercuts.

Dataset views: Matches, Gaps and Summary. RUN_SUMMARY in the key-value store gives:

  • the outcome (COMPLETE, PARTIAL, EMPTY, FAILED);
  • the counts per competitor and method;
  • the charges;
  • every source that could not be read, with the reason;
  • with Competitor stores, storeRuns (the Shopify Store Scraper run for each store) and storeReadsCostUsd;
  • droppedCatalogues (competitors left out because their source failed part-way, or because the store is your own: its pages are in your catalogue and Your store is not set) and gapsSkipped (why no gaps were made, when your catalogue was read only in part).

Pricing

EventFree and BronzeSilverGold and above
Matched pair (per 1,000)$2.00$1.80$1.60
Gap row (per 1,000)$0.10$0.09$0.08
Pair or gap checked, unchanged, Changes only (per 1,000)$0.05$0.045$0.04
Competitor catalogue compared (each, per run)$0.05$0.05$0.05

Each run also has a $0.00005 start fee. Summary rows are free. Duplicates, products you didn't ask gaps for and sources that could not be read are never charged; a competitor catalogue read only in part is left out and not charged at all. The comparison event is charged before a catalogue's rows, so a Max cost per run below $0.05 compares nothing.

Your 2,500 products against two rivals' 2,500 and 2,042 (93 pairs in common) come to:

  • $0.37 for the pairs and what they sell in brands you carry (93 pairs, 859 gap rows, 2 catalogues);
  • $0.86 with the default gaps both ways (also your 4,897 products they don't sell);
  • $1.22 with every gap both ways (Gaps in your brands only off: 9,344 gap rows).

A daily watchlist of the same with Changes only pays for its changes, $0.05 per 1,000 unchanged pairs and gaps, and $0.05 per competitor.

A weekly undercut watch on Shopify stores

With Competitor stores, each store (yours too, when this run reads it) is read by Shopify Store Scraper. Its run starts in your account and is billed at its own prices: $0.80 per 1,000 products on the Free tier ($0.70 Silver, $0.60 Gold and above). This actor charges its usual events on top, nothing more. For 250-product stores, on the Free tier, with Changes only after the first run:

RivalsStore reads (yours + theirs)ComparisonsUnchanged checks and ~10 changes eachA runA month (weekly)
1$0.40$0.05about $0.03$0.48about $2.10
2$0.60$0.10about $0.06$0.76about $3.30
3$0.80$0.15about $0.10$1.05about $4.50

The first run sets the baseline and returns every pair, at $2 per 1,000. With full overlap (all 250 products matched per rival) it is the dearest run: $0.95 for one rival, $1.70 for two and $2.45 for three. Later runs return only changes and cost the amounts in the table. If your catalogue is already in a dataset, give it as Your products (dataset) instead of Your store to skip one store read.

Set Max cost per run so it covers the first run: about $2 for one rival, $3 for two and $4 for three at 250 products each. A schedule keeps the same limit, and later runs charge only what they use. The limit covers the store reads too:

  1. The actor keeps back $0.15 per catalogue (its comparison and 50 rows) and $0.10 per rival (the least useful read).
  2. Your store is read first, with the rest as its limit, so it is read in full whenever the limit allows.
  3. The rivals then share what is left equally, each share being that read's own limit.

A store needs about $0.0008 per product. A rival whose share would be under $0.10, or under what your store's read cost, is not started (a rival read in part is never compared, so it would be paid for nothing). The last stores listed give way first, each with a reason giving the limit to set, and the run warns once.

Limits

  • It matches what the datasets hold. Shopify's public catalogue has no barcodes, so two Shopify Store Scraper datasets match by title. Run Any Store Product Monitor on the products to get their barcodes.
  • Title matching needs a brand on both sides by default. Products whose titles differ by more than the rules allow stay unmatched, as gaps.
  • Prices are compared as given: no currency conversion, no tax or shipping.
  • Up to 50,000 products a side run in the default 256 MB (the default limit). Give 1 GB and raise Max products per catalogue for up to 200,000 a side; a catalogue cut at the limit gives no gap rows.
  • Datasets are read only when you pick them in the form. The actor cannot see your other storages.
  • Competitor stores must be Shopify stores. Some reads are reported in failures and left out (no rows, no charges, watchlist unchanged): a store that can't be read, a read that stopped at its share of the limit, and a store with more products than Max products per catalogue. A catalogue read in part would show its missing products as lost pairs. The other stores are still compared.
  • Your store must be read in full. If it can't be, nothing is compared, charged or alerted, and the reason gives the limit the read stopped at.
  • Nothing found: when nothing could be compared because a source does not exist or cannot be used (a dataset id that is not there or not shared, a store that is not a Shopify store, a limit too low to read your store), the run still succeeds, with one free recordType: "unreadable" row per source giving the reason; nothing is charged. Near its time limit the run stops starting competitor catalogues and lists them the same way.

Changelog

  • Reliability (2026-10-24): runs that find nothing to compare (missing or unshared datasets, stores that are not Shopify stores, limits too low to read your store) end SUCCEEDED with a free unreadable row per source saying why, instead of FAILED; competitor catalogues not started before the time limit are listed, never TIMED-OUT. A source that breaks on every read, or a store blocked on every route, still fails the run. Competitor stores are capped at 10 in the input form. Prices and events unchanged.
  • Undercut watch (October 2026): Competitor stores reads Shopify stores in the same run, each through its own Shopify Store Scraper run within its share of the cost limit. Your store is read too when no other catalogue of yours is given. Undercut alert marks pairs where a competitor is cheaper by at least a set percentage and, with a watchlist, reports new undercuts. Existing inputs work as before.
  • 0.1.8 (October 2026): a run limit that leaves room for the products found now delivers them (before, the run stopped once another catalogue comparison no longer fitted, even when pairs did). Your own store found in a competitor dataset is left out instead of being compared with itself.
  • 0.1 (October 2026): matching by barcode, part number, SKU and style code, model number, and title within the brand, with method and confidence. Price gaps without cross-currency comparison. Gaps both ways, per-competitor summaries and a watchlist mode. Shopify Store Scraper, Any Store Product Monitor and Amazon Product Scraper datasets, or your own rows.