If you sell anything to Shopify merchants — an app, an agency service, a 3PL — the hardest part isn't the pitch. It's building a list of the right stores: the ones a certain size, in a certain country, running (or not running) a certain app.
Most "Shopify leads" lists just dump domains on you. Here's how to do it with a scalpel instead — using only public data, no login, no paid database.
The trick: Shopify stores expose themselves
Every Shopify storefront serves two unauthenticated JSON endpoints to the open web:
-
https://<domain>/meta.json— store identity and size signals -
https://<domain>/products.json— the full catalogue
Let's look at a real store:
curl -s https://kith.com/meta.json | python3 -m json.tool
{
"name": "Kith",
"city": "Brooklyn",
"country": "US",
"currency": "USD",
"myshopify_domain": "kithnyc.myshopify.com",
"published_products_count": 25001,
"published_collections_count": 411
}
That published_products_count is gold. It's a live size signal — a proxy for how established the store is. Sorting or filtering by it is the difference between "10,000 random domains" and "stores big enough to afford what I sell."
Detecting the tech stack
Apps a store runs leave fingerprints in the storefront HTML. Want every store on Klaviyo? Their script tag is right there:
import httpx, re
APPS = {
"klaviyo": "Klaviyo", "recharge": "Recharge", "gorgias": "Gorgias",
"yotpo": "Yotpo", "judge.me": "Judge.me", "loox": "Loox",
"okendo": "Okendo", "attentive": "Attentive",
}
def profile(domain: str) -> dict | None:
meta = httpx.get(f"https://{domain}/meta.json", timeout=20)
if meta.status_code != 200 or "myshopify_domain" not in meta.text:
return None # not a live Shopify store
m = meta.json()
html = httpx.get(f"https://{domain}/", timeout=20).text.lower()
apps = sorted({name for key, name in APPS.items() if key in html})
return {
"domain": domain,
"name": m.get("name"),
"country": m.get("country"),
"products": m.get("published_products_count"),
"apps": apps,
}
print(profile("kith.com"))
# {'domain': 'kith.com', 'name': 'Kith', 'country': 'US',
# 'products': 25001, 'apps': ['Gorgias', 'Klaviyo']}
Now you can filter: keep only US stores with 50+ products that run Klaviyo. That's a targeted prospect list, not a firehose.
Where do the domains come from?
The one thing Shopify won't give you is a list of stores. Two honest options:
- Bring your own — you usually have a seed list (competitors' customers, a niche directory, past exports). Enrich those.
-
Common Crawl — the public web index has
*.myshopify.comsubdomains you can harvest for free. Each one servesmeta.jsondirectly (and tells you its real custom domain).
Doing it at scale without the plumbing
The code above works, but at volume you'll want concurrency, dedup, retry, dead-store filtering, CSV export — the boring 80%. I packaged all of it into an Apify actor so you can skip straight to the list:
- Filter by installed app (
requireApps/excludeApps) - Filter by size (
minProducts/maxProducts) and country - Discover stores from Common Crawl, or enrich your own domains
- You pay only for stores that match your filters — dead and non-matching domains are free
👉 Shopify Store Finder on Apify
Either way — hand-rolled or one-click — the principle is the same: Shopify stores tell you who they are, how big they are, and what they run. Point your prospecting at that, and stop paying for domain dumps.
Questions or a store type you can't find? Drop a comment.
Top comments (0)