Stockist price check for brands: one sheet showing what each online reseller charges for your products
What you get: one Google Sheet for a brand or distributor that, every week, shows each stockist's online price for each of your products next to your RRP, the gap in %, and flags for below RRP, above RRP, out of stock and on sale. A second tab lists, per stockist, your products they do not list, ready for your sales rep's next call. You build it from your stockists' public product lists (CSV), a one-time URL-to-SKU map and three formulas. Setup takes an afternoon for 5–20 stockists; the weekly refresh is about fifteen minutes.
The finished sheet
check tab
| Stockist | Product | Your SKU | Their price | Your RRP | Gap | Flag |
|---|---|---|---|---|---|---|
| stockist-a.in | Cold-pressed hair oil 200 ml | HO-200 | ₹449 | ₹499 | −10% | Below RRP · on sale |
| stockist-b.com | Cold-pressed hair oil 200 ml | HO-200 | ₹499 | ₹499 | 0% | OK · out of stock |
| stockist-b.com | Herbal shampoo 300 ml | SH-300 | ₹420 | ₹349 | +20% | Above RRP |
not_listed tab (stockist-a.in)
| Your SKU | Product | RRP |
|---|---|---|
| SH-300 | Herbal shampoo 300 ml | ₹349 |
| HO-100 | Cold-pressed hair oil 100 ml | ₹279 |
Why a brand wants this sheet
- Out of stock at a stockist is a lost sale for you as well. It is the most useful flag here: a reason to call that stockist about a reorder this week.
- Not listed shows range gaps per stockist. "You sell our 200 ml oil but not the shampoo" is a concrete sell-in conversation.
- Above RRP often means an old price, a wrong pack size mapped, or a shop adding its own margin. Worth a friendly check.
- Below RRP often means a typo, a stale price after your own price change, a clearance of old stock, or a different pack size. Read the section on what you may and may not do with this flag before you act.
Watching competitors rather than your own stockists? Use the weekly competitor price sheet or the competitor out-of-stock and new products guide instead.
Step 1: write your RRP list (tab rrp)
One row per sellable product. Format columns A and B as Plain text first (Format → Number → Plain text), so codes keep their leading zeros.
| A: your SKU | B: GTIN / barcode | C: product name | D: RRP | E: currency |
|---|---|---|---|---|
| HO-200 | 0890000000000 | Cold-pressed hair oil 200 ml | 499 | INR |
Use the RRP as shoppers see it in that market (normally including tax). One currency per sheet keeps the gap column honest; make a second sheet for another country.
Step 2: list your stockists' web shops and pick a day
- List the stockists that sell your products on their own Shopify or WooCommerce shop. Use the shop homepage, or a brand page such as
/collections/your-brandto keep runs small. - Pick one day and time each week, for example Monday 10:00.
- Skip marketplaces (Amazon, Flipkart, eBay and similar). Their terms forbid scraping and this method is not for them.
- Keep a
stockiststab: shop host, stockist name, your contact there.
Step 3: get this week's product CSV
Free, one or two shops
- Shopify: many shops answer at
/products.json?limit=250&page=1. Each variant carriessku,price,compare_at_priceandavailable, but no barcode. Step-by-step: Export a Shopify store's products to CSV. - WooCommerce: many shops answer at
/wp-json/wc/store/v1/products?per_page=100, withsku, prices andis_in_stock. Step-by-step: Export a WooCommerce store's products to CSV.
If a shop returns 401, 403 or 404 there, or its robots.txt or terms forbid scraping, drop it from the list and check that stockist by hand.
No code, many shops (Apify)
- Create a free Apify account (no card needed for the free plan; see apify.com/pricing for current free monthly usage).
- Open Shopify & WooCommerce Product + Price Tracker and click Try for free.
- Paste your stockist list. In JSON view:
{
"startUrls": [
{ "url": "https://stockist-a.in/collections/your-brand" },
{ "url": "https://stockist-b.com" }
],
"maxProductsPerShop": 500,
"maxListingPages": 20,
"includeDescription": false
}
Set maxProductsPerShop above the number of products you scan per shop; if the cap cuts a shop short, your products past the cap will look "not listed". Turn descriptions off.
When the run finishes, open the dataset and choose Export → CSV. Paste it as-is into a new tab named like raw_2026-10-26. The columns this sheet uses are url, shop, title, sku, gtin, price, currency, onSale and inStock. Then open the run's Storage → Key-value store → SHOPS_SUMMARY: it lists every shop with its status (ok, blocked-by-terms, blocked-by-robots, no-products-found and so on). Every stockist that is not ok goes on your by-hand list for the week.
Save the input as an Apify Task so the list stays the same each week, and add a weekly Schedule if you like.
Two things to know about the columns. (1) For a Shopify product with several sizes, the row's price, sku and compareAtPrice are those of the cheapest variant; each variant's own sku, price and available sit in the variants/0/…, variants/1/… columns. If you sell several pack sizes, map each size's row or read those columns. (2) gtin is filled only when the shop publishes a barcode, mostly in the product page's schema.org data. Expect it to be empty for many Shopify rows, and empty for rows from the WooCommerce Store API.
Step 4: map each stockist URL to your SKU (tab map, once)
Tab map has two columns: A stockist product URL, B your SKU (format both as Plain text). A stockist's product URL rarely changes, so once a URL is mapped it stays mapped. Tab settings: cell B1 holds this week's raw tab name, B2 the "below RRP" threshold (for example 10%), B3 the "above RRP" threshold (for example 5%), B4 the stockist host for the not-listed tab.
Put this in cell A1 of a tab called to_map. It lists every product in this week's CSV whose URL is not yet in map, with a suggested SKU when the barcode or the SKU matches your rrp list:
=LET(R, INDIRECT("'"&settings!B1&"'!A1:ZZ"), H, CHOOSEROWS(R, 1), D, DROP(R, 1),
col, LAMBDA(n, CHOOSECOLS(D, MATCH(n, H, 0))),
url, col("url"), title, col("title"), sku, TO_TEXT(col("sku")), gtin, TO_TEXT(col("gtin")),
g14, ARRAYFORMULA(RIGHT(REPT("0", 14) & gtin, 14)),
rg14, ARRAYFORMULA(RIGHT(REPT("0", 14) & TO_TEXT(rrp!B2:B), 14)),
byG, ARRAYFORMULA(IF(gtin = "", "", IFERROR(VLOOKUP(g14, {rg14, rrp!A2:A}, 2, FALSE), ""))),
byS, ARRAYFORMULA(IF(sku = "", "", IFERROR(VLOOKUP(sku, rrp!A2:A, 1, FALSE), ""))),
sug, ARRAYFORMULA(IF(byG <> "", byG, byS)),
VSTACK({"url", "title", "their sku", "gtin", "suggested SKU"},
FILTER(HSTACK(url, title, sku, gtin, sug), url <> "", ISNA(MATCH(url, map!A2:A, 0)))))
Padding every barcode to 14 digits lets a 12-digit UPC and the same code written as a 13-digit EAN with a leading zero match each other. Copy the rows you accept into map as values (Paste special → Values only). For the rest, search the title for your product names and type your SKU by hand. Rows that are not your products (a stockist's whole shop) you simply leave unmapped; the check tab ignores them.
Step 5: the check tab
Cell A1 of tab check:
=LET(R, INDIRECT("'"&settings!B1&"'!A1:ZZ"), H, CHOOSEROWS(R, 1), D, DROP(R, 1),
col, LAMBDA(n, CHOOSECOLS(D, MATCH(n, H, 0))),
url, col("url"), shop, col("shop"), title, col("title"), price, col("price"),
cur, col("currency"), stock, TO_TEXT(col("inStock")), sale, TO_TEXT(col("onSale")),
key, ARRAYFORMULA(IF(url = "", "", IFERROR(VLOOKUP(url, map!A2:B, 2, FALSE), ""))),
rrpP, ARRAYFORMULA(IF(key = "", "", IFERROR(VLOOKUP(key, rrp!A2:D, 4, FALSE), ""))),
gap, ARRAYFORMULA(IF((rrpP = "") + (price = ""), "", price / rrpP - 1)),
flag, ARRAYFORMULA(IF(gap = "", "",
IF(gap <= -settings!B2, "Below RRP", IF(gap >= settings!B3, "Above RRP", "OK"))
& IF(stock = "FALSE", " · out of stock", "") & IF(sale = "TRUE", " · on sale", ""))),
VSTACK({"Stockist", "Product", "Your SKU", "Their price", "Currency", "Your RRP", "Gap", "Flag", "URL"},
FILTER(HSTACK(shop, title, key, price, cur, rrpP, gap, flag, url), key <> "")))
Format column G as a percentage. Sort or filter by the Flag column. If a row's currency differs from your RRP currency, ignore its gap; the shop is selling in another market.
Step 6: the not-listed tab
Type a stockist host (exactly as in the shop column, for example stockist-a.in) into settings!B4. Cell A1 of tab not_listed:
=LET(have, FILTER(check!C2:C, check!A2:A = settings!B4),
VSTACK({"Your SKU", "Product", "RRP"},
FILTER({rrp!A2:A, rrp!C2:C, rrp!D2:D}, rrp!A2:A <> "", ISNA(MATCH(rrp!A2:A, have, 0)))))
That is your list of products this stockist does not list this week. Change B4 to see the next stockist. Before your rep calls, confirm the shop was ok in SHOPS_SUMMARY and that you scanned the right page; a skipped shop or a narrow category page makes everything look "not listed".
In Excel, the same logic works with LET, XLOOKUP, FILTER and VSTACK on direct sheet references instead of INDIRECT and ARRAYFORMULA.
Step 7: check before you act
- Open the product page for every flagged row. Confirm the pack size, the price shown and whether it includes tax; a shop may show prices with or without tax.
- Blank stock means unknown, not out of stock. Some shops do not publish availability. In the Unadhi scraper,
inStockat product level is true when any variant is available, so one sold-out size does not flip it. - One week is noise. A price two weeks running, or stock out two weeks running, is a pattern; a single reading may be a flash sale or a restock in transit.
- Re-check the map when you add products or change pack sizes.
What you may and may not do with "below RRP"
This sheet shows prices. What you do about a low price is regulated in many countries:
- EU: under the vertical block exemption, a supplier may recommend a resale price or impose a maximum, but must not turn it into a fixed or minimum resale price "as a result of pressure from, or incentives offered by, any of the parties" (Regulation (EU) 2022/720, Article 4(a)). Restricting the buyer's ability to set its own sale price is a hardcore restriction.
- India: resale price maintenance is listed in Section 3(4)(e) of the Competition Act, 2002, and is defined to include any direct or indirect restriction that resale prices must be the seller's prices, unless it is clearly stated that lower prices may be charged. It is unlawful where it causes or is likely to cause an appreciable adverse effect on competition in India.
- Elsewhere: rules differ. Ask a competition lawyer before you link a stockist's price to supply, discounts or terms.
Safe, useful uses of the flag: spotting a typo or a stale price after your own price change, a wrong pack size, a product you thought was discontinued, and stock that is about to run out.
What not to do
- Do not run it daily "just in case". Weekly is enough and keeps your load on each shop light.
- Do not ignore robots.txt or terms that forbid scraping. Check that stockist by hand instead.
- Do not place test orders or fill carts to probe stock. Use only what the shop publishes.
This guide is general information, not legal advice. You are responsible for how you use the data and for following each shop's terms and your local law. The Apify Actor mentioned reads only public pages, uses an honest bot User-Agent, follows robots.txt, scans each shop's terms and skips shops whose terms forbid scraping, never logs in and never gets around blocks.
Partner programme opening soon
Partner programme opening soon: join the free waitlist. Planned terms are 50% of each referred sale (75% once you have 5+ paying customers), single level, paid through Gumroad only from revenue received.
Sources
- Commission Regulation (EU) 2022/720 (vertical block exemption), Article 4(a): eur-lex.europa.eu/eli/reg/2022/720/oj/eng
- The Competition Act, 2002 (India), Section 3(4)(e) and Explanation (e), as amended by Act 9 of 2023: indiacode.nic.in
- GS1, Global Trade Item Number (GTIN): gs1.org/standards/id-keys/gtin
- Product scraper input and output fields: Apify Store page (input schema and README of build 0.1.3)
Related
- Build a weekly competitor price sheet
- Spot out-of-stock and newly added products on competitor stores each week
- Export a Shopify store's products to CSV
- Export a WooCommerce store's products to CSV without a plugin
- Looking for new stockists rather than checking existing ones? Related only: Website Contact Scraper: Emails, Phones & WhatsApp (also an Unadhi Apify product) finds a shop's published role inboxes such as sales@ or wholesale@. Use it for genuine one-to-one enquiries, not bulk cold email.
Unadhi Guides are free, independent explainers. We correct errors as soon as we find them: write to mininglife7@gmail.com.