If you sell online, you probably check competitors' prices by hand: open their store, click around, write numbers in a spreadsheet. It's slow, and you only ever see a snapshot.
Here's a shortcut most people don't know about: many Shopify stores expose their entire product catalog as public JSON. Prices, compare-at prices, variants, stock status, tags, images. No scraping of HTML needed.
The endpoint
Add /products.json to the store's domain:
https://www.allbirds.com/products.json?limit=250&page=1 You get up to 250 products per page. Keep incrementing page until it returns an empty products array.
Each product includes:
-
title,handle,vendor,product_type,tags -
created_at,updated_at,published_at -
variants[]: each withprice,compare_at_price,available,sku,option1-3 images[]
Two related endpoints are handy too:
-
/collections.jsonlists the store's collections -
/collections/{handle}/products.jsongives you just one collection (e.g. "sale")
A minimal price tracker in Node.js
async function getAllProducts(domain) { const products = []; for (let page = 1; page <= 20; page++) { const res = await fetch(`https://${domain}/products.json?limit=250&page=${page}`); if (!res.ok) throw new Error(`${domain}: HTTP ${res.status}`); const { products: batch } = await res.json(); if (!batch.length) break; products.push(...batch); await new Promise(r => setTimeout(r, 500)); // be polite } return products; } const products = await getAllProducts('www.allbirds.com'); const rows = products.map(p => { const prices = p.variants.map(v => Number(v.price)); const compare = p.variants.map(v => Number(v.compare_at_price) || 0); return { title: p.title, minPrice: Math.min(...prices), maxCompareAt: Math.max(...compare), onSale: p.variants.some(v => Number(v.compare_at_price) > Number(v.price)), inStock: p.variants.some(v => v.available), }; }); console.log(`${rows.length} products, ${rows.filter(r => r.onSale).length} on sale`); console.table(rows.slice(0, 5)); Run it daily, save the rows with a date, and you have price history. Then you can see when a competitor starts a sale, which products they discount most, and what they quietly drop from the catalog.
Things to watch out for
- Not every store exposes it. Some return 404 or block the endpoint. Respect that and move on.
-
compare_at_pricecan be0ornull. Both mean "no sale price". Don't count0as a discount. - Prices are in the store's default currency. Multi-currency stores may show different prices to visitors in other countries.
- No inventory quantities. You get
available: true/false, not how many units are left. - Pace your requests. Half a second between pages is plenty. Shopify returns 429 if you go too fast.
- Use it responsibly. This is public catalog data, but check the store's terms, and use it for research and price monitoring, not for copying content.
If you'd rather not run it yourself
I packaged this as an Apify Actor: Shopify Store Products Scraper. Give it a list of store URLs and it handles pagination, collections, redirects to the canonical domain, retries and rate limits. It has a "variant" mode (one row per variant, good for price tracking), and it writes a per-store summary: product count, price range and % on sale.
Example output (trimmed):
{ "store": "www.allbirds.com", "title": "Women's Wool Runner - Natural Grey (Light Grey Sole)", "price": 49, "compareAtPrice": 110, "onSale": true, "available": true, "currency": "USD", "variantsCount": 7 } It costs $1 per 1,000 products. Schedule it daily in Apify and you have a competitor price tracker without maintaining any code.


