Every Shopify store publishes a JSON feed of its catalog at /products.json. It needs no API key, no app, and no authentication — it's the same data the storefront theme reads. This is the single most useful endpoint for anyone doing price monitoring, catalog analysis, or competitor research on Shopify, and it's badly under-documented. Here's everything it actually returns and how to use it.

The URL

https://any-store.com/products.json

By default it returns the first 30 products. Two query params matter:

So the full catalog of a large store is just a loop:

import json, ssl, time, urllib.request, urllib.error

def fetch(url):
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
    for ctx in (ssl.create_default_context(), ssl._create_unverified_context()):
        try:
            return urllib.request.urlopen(req, timeout=25, context=ctx).read()
        except urllib.error.URLError:
            continue
    raise RuntimeError("fetch failed: " + url)   # fail loudly, don't return None

def all_products(base):
    page, out = 1, []
    while True:
        data = json.loads(fetch(f"{base}/products.json?limit=250&page={page}"))
        batch = data.get("products", [])
        if not batch:
            break
        out += batch
        page += 1
        time.sleep(0.5)                            # be polite between pages
    return out

products = all_products("https://onyxcoffeelab.com")
print(len(products), "products")

What each product contains

A product object has these fields:

FieldWhat it's for
idStable Shopify product ID
titleProduct name
handleURL slug (/products/<handle>)
vendorBrand
product_typeCategory
tagsMerchandising tags
body_htmlFull description (HTML)
published_at / updated_atWhen it went live / last changed
variantsThe array that actually holds prices
imagesImage URLs
optionsVariant option names (Size, Grind, etc.)

The variant fields (where the money is)

Prices live on variants, not the product. Each variant gives you:

FieldWhy it matters
priceThe current selling price (string, e.g. "26.00")
compare_at_priceThe "was" price — if this is set and higher than price, the item is on sale. This is how you detect promotions.
availableBoolean in/out of stock. (Note: it's a boolean, not a quantity.)
skuThe store's SKU — useful for matching the same product across stores
gramsShipping weight
option1/2/3The variant values (e.g. "12oz", "Whole Bean")
updated_atTimestamp — diff this between runs to catch changes cheaply

Three things you can build in an afternoon

  1. Price monitor — save {sku: price} each run, diff against last run, alert on changes.
  2. Sale detector — flag any variant where compare_at_price > price. You now know exactly what a competitor is discounting and by how much.
  3. Stock watch — track available flipping to false; a competitor selling out is your window to hold or raise price.

Honest limits

Turn it into something you'll actually use

The open-source tool that does the save-and-diff (price + sale + stock) for you is here: github.com/willylam2222-bot/priceprobe. If you'd rather just get the answer, send me a few competitor URLs and I'll return a clean report of their prices, sales, and stock: willyverse188.gumroad.com/l/kphhe.

The endpoint is public and free. Now you know exactly what's in it.

Get a competitor price report — send your URLs, get clean data back → ← More on PriceProbe