Every Shopify storefront publishes its full product catalog as public JSON. No login, no API key, no headless browser:
GET https://<store-dom...
For further actions, you may consider blocking this person and/or reporting abuse
Confirmed the 999,999 sentinel on allbirds here too (Free Returns Coverage,
inventory_management: null).One more trap on the same payload: the discount roll-up. Min price vs min compare-at still crosses variants. A $10 accessory sitting next to a $100 jacket marked down from $120 produces a discount that belongs to neither of them. Per variant, then take the best, is the only version that survives a spot check:
Same class of bug as the sentinel: it produces a plausible number rather than an obvious error, so nothing downstream catches it.
Also, on the per-product endpoint the variants carry
price_currencyandcompare_at_price_currency— so if you are already paying for the detail request you can skipmeta.jsonfor currency. The bulk feed still needs it.The three-outcome model at the end is especially important. I would carry the same distinction into the stored record:
source_status(ok,disabled,fetch_failed),product_count, andfiltered_countas separate fields instead of encoding all of them as an empty array. That keeps a later successful retry from looking like inventory suddenly appeared.For the 999999 sentinel, I would preserve the raw number only alongside an explicit
inventory_tracked: false. Otherwise someone exporting the normalized rows to CSV can lose the predicate and accidentally reintroduce the bad aggregate downstream.The 999999 sentinel is a really interesting edge case. It’s a good reminder that Shopify product data shouldn’t always be treated as straightforward numeric values, especially when building inventory or catalog aggregation logic. Filtering on inventory management before calculating totals is a simple fix, but an easy detail to miss.