Shopify Product Scraper & Store Leads (Apify Actor)
Find Shopify store owner contacts: extract emails, phones and social links from Shopify store domains, or scrape every product from a store's public /products.json. No login, no API key. Runs as an Apify Actor, callable from MCP clients via the Apify MCP server (https://mcp.apify.com/?actors=pennydata/shopify-store-leads-contacts).
Hosted MCP Server
npx add-mcp 'https://mcp.apify.com/?actors=pennydata/shopify-store-leads-contacts'Installs into Claude Code, Codex, Cursor and more
Documentation
Shopify Store Leads: Emails, Phones & Socials by Domain (pennydata/shopify-store-leads-contacts) Actor
Shopify store leads from a domain list: public business email, phone, social links, store name, tech stack. $0.004 per store, non-Shopify and failed domains are free. Prefilled 2 stores: just click Start.
- URL: https://apify.com/pennydata/shopify-store-leads-contacts.md
- Developed by: Penny Data (community)
- Categories: Lead generation, E-commerce, Agents
- Stats: 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- User rating: No ratings yet
Pricing
from $2.80 / 1,000 store leads
This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events. Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.
Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event
What's an Apify Actor?
An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes. In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours, and optionally produces a well-defined JSON output, datasets with results, or files in key-value store. In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.
How to integrate an Actor?
If asked about integration, you help developers integrate Actors into their projects. You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.
For examples already wired to this Actor's own input schema, see the API section below.
Each client library has reference documentation the quickstart does not restate: JavaScript/TypeScript (npm install apify-client) and Python (pip install apify-client).
README
Shopify Store Leads Scraper - Business Emails, Phones & Socials
Shopify store lead generation from a list of domains: one lead row per store with the public business email, phone, social links, product count, price range, currency and detected tech stack. Plain HTTP, no browser, no proxy. Domains that are not Shopify stores, are blocked or fail are returned with an error and are not charged.
Free to try: a new Apify account includes free monthly platform credit, enough to test a few dozen stores before you pay anything.
Quickstart (one API call)
curl -X POST "https://api.apify.com/v2/acts/pennydata~shopify-store-leads-contacts/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"domains":[{"url":"gymshark.com"}],"maxConcurrency":1}'
Or open the Actor in the Console and click Start: the prefilled input (two stores) runs as-is.
Example output (real run, 2026-10-20)
Input gymshark.com, output (one row, one store-lead event):
{
"input": "www.gymshark.com",
"domain": "gymshark.com",
"storeUrl": "https://www.gymshark.com",
"storeName": "Gymshark",
"description": "Unlock your full potential with our game-changing workout clothes. Shop gym clothing for the gym, running & everything in-between. Free delivery on orders over $75",
"myshopifyDomain": "gymsharkusa.myshopify.com",
"emails": ["press@gymshark.com", "support@gymshark.com"],
"phones": [],
"socials": {},
"technologies": ["Contentful", "Amazon CloudFront", "Next.js"],
"contactPagesChecked": ["/pages/about-us"],
"httpStatus": 200,
"capturedAt": "2026-10-20T04:40:09.275Z"
}
Empty phones and socials are real: the actor returns only what the store itself publishes and never guesses.
How it works
- Reads the homepage,
/pages/contact,/pages/contact-usand/pages/about-us(only while no email is found) and the store's public/products.json. - Respects each store's
robots.txt(on by default). Shopify's default robots.txt disallows/policies/, so those pages are skipped for most stores. - Business contacts only: by default only shared mailboxes a store publishes (info@, support@, hello@, sales@ ...). No names, no personal profiling, no social-profile scraping (only the links the store itself publishes).
- Output per store: name, description, domain and
myshopifydomain, currency, theme, emails, phones, socials (Instagram, Facebook, X, TikTok, YouTube, Pinterest, LinkedIn), product count and price range, top vendors, technologies.
Pricing
| Event | When it is charged | Price |
|---|---|---|
store-lead ("Store lead") | One Shopify store successfully processed into a lead row | $0.004 |
That is $4.00 per 1,000 stores on the Free plan (paid Apify plans get tiered discounts, down to $0.0028 per store). Non-Shopify domains, robots.txt-blocked domains, rate-limited (HTTP 429) and failed stores are not charged. Set a maximum cost per run in the run options to cap spend.
Weekly Shopify app change alerts
Track which apps and themes your target stores add or drop. Turn on changeAlerts; the actor keeps a snapshot of every store from the last run (in the named key-value store shopify-store-leads-snapshots) and, on the next run with the same domains, outputs only stores whose technologies, theme, storeName, currency, emails or phones changed, plus added / removed objects (for example added.technologies: ["Gorgias"], removed.technologies: ["Klaviyo"]) and a changes list. Stores not in the previous snapshot come back with changeType: "new-store". The first run outputs all stores as the baseline. Every store is still scraped and billed at the normal per-store price.
Schedule recipe (no per-run config):
- Save a Task with your store list and
"changeAlerts": true. - Create a Schedule (Console > Schedules), daily or weekly (for example
0 7 * * 1), pointing at that Task. Nothing else to update between runs. - Send the output to Slack, Sheets or a webhook through an Apify integration. A run with no changes produces an empty dataset.
previousDatasetId is an optional override to compare against an earlier full run's dataset. Do not pass a change-alert run's dataset: it only holds changed rows.
Use cases
- Build an outreach list for apps, agencies and suppliers that serve Shopify merchants.
- Qualify a list of store domains by product count, currency and detected tech.
- Find published business emails and brand social links for a set of stores.
- Enrich an existing CRM list of storefront domains.
Limits and honesty
- Many stores publish their contact only through a form; then
emailsis empty. The actor never invents addresses. - Stores may rate-limit (HTTP 429) fast bursts. Those rows carry an
error, are not charged, and can be re-run later; the default concurrency is low for this reason. productCountreads up tomaxProductPagesx 250 products (default 4 = 1,000; set up to 100 = 25,000). When the limit is hit or a later page is rate-limited,productCountIsLowerBoundistrue.- Pagination check (2026-10-24, fashionnova.com): the store's
/products.jsonreturned full 250-product pages through page 89 (over 22,000 products); product reading goes up to 100 pages (25,000 products) per store, and a count that stops early for any reason is flaggedproductCountIsLowerBound. - You are responsible for using contact data lawfully (GDPR, CAN-SPAM, local marketing rules).
More from PennyData
- Website Tech Stack Detector: CMS, ecommerce, analytics, CDN and JS frameworks per URL. Pair it with this actor to check which domains run Shopify first.
- Bulk Email Validator (No SMTP): check the emails you collect for syntax, MX, disposable and role addresses before you send.
- Bulk Domain Availability & WHOIS/DNS Lookup: registrar, expiry and MX per domain.
FAQ
Where do the emails come from? Only addresses the store itself publishes on its homepage or contact/about pages. Nothing is guessed or generated.
Why is emails empty for some stores?
Many stores publish only a contact form. The row is still returned and charged as a lead; use phones, socials and the store data.
Can I email these contacts? You are responsible for lawful use (GDPR, CAN-SPAM, local rules). Only shared business mailboxes are returned by default.
How much does a run cost? $0.004 per store, nothing else on top. 200 stores is about $0.80.
Is it maintained? Yes. Report breakage in the Issues tab; failed rows are never billed.
Built with AI assistance (Claude); maintained by the publisher.
Actor input Schema
domains (type: array):
Shopify store domains or URLs (bare domains like allbirds.com are accepted). Non-Shopify sites are skipped and not charged.
changeAlerts (type: boolean):
Turn on diff mode with zero config. Each run saves a full snapshot of all stores and the next run with the same domains outputs only stores whose apps/theme/contacts changed (new stores flagged new-store). The first run outputs all stores as the baseline. Ideal for a daily or weekly schedule.
previousDatasetId (type: string):
Optional override; leave empty when Change alerts is on (the saved snapshot is used). Dataset ID of an earlier FULL (non-diff) run of this actor. When set, only stores whose apps/technologies, theme, store name, currency, emails or phones changed since then are output, with added/removed lists. New stores are output with changeType new-store. All scraped stores are still billed.
includeProductStats (type: boolean):
Read the store's public /products.json to count products and get price range, top vendors.
maxProductPages (type: integer):
Pages of 250 products to read per store. Counts reaching the limit are flagged as a lower bound.
includeTechnologies (type: boolean):
List analytics, CDN and other technologies detected on the homepage.
includePersonalEmails (type: boolean):
By default only shared business mailboxes (info@, support@, hello@, sales@...) are returned. Enable to also return other addresses a store publishes.
respectRobotsTxt (type: boolean):
Skip pages disallowed for all bots in the store's robots.txt. Shopify's default robots.txt blocks /policies/, so contact details come from the homepage and /pages/contact.
maxConcurrency (type: integer):
Stores processed in parallel (1-20).
timeoutSecs (type: integer):
Per-request timeout in seconds (5-60).
Actor input object example
{
"domains": [
{
"url": "https://www.gymshark.com"
},
{
"url": "https://fashionnova.com"
}
],
"changeAlerts": false,
"includeProductStats": true,
"maxProductPages": 4,
"includeTechnologies": true,
"includePersonalEmails": false,
"respectRobotsTxt": true,
"maxConcurrency": 2,
"timeoutSecs": 20
}
Actor output Schema
results (type: string):
No description
API
You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.
JavaScript example
import { ApifyClient } from 'apify-client';
// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
token: '<YOUR_API_TOKEN>',
});
// Prepare Actor input
const input = {
"domains": [
{
"url": "https://www.gymshark.com"
},
{
"url": "https://fashionnova.com"
}
]
};
// Run the Actor and wait for it to finish
const run = await client.actor("pennydata/shopify-store-leads-contacts").call(input);
// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`πΎ Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
console.dir(item);
});
// π Want to learn more π? Go to β https://docs.apify.com/api/client/js/docs
Python example
from apify_client import ApifyClient
# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")
# Prepare the Actor input
run_input = { "domains": [
{ "url": "https://www.gymshark.com" },
{ "url": "https://fashionnova.com" },
] }
# Run the Actor and wait for it to finish
run = client.actor("pennydata/shopify-store-leads-contacts").call(run_input=run_input)
# Fetch and print Actor results from the run's dataset (if there are any)
print(f"πΎ Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
print(item)
# π Want to learn more π? Go to β https://docs.apify.com/api/client/python/docs/quick-start
CLI example
echo '{
"domains": [
{
"url": "https://www.gymshark.com"
},
{
"url": "https://fashionnova.com"
}
]
}' |
apify call pennydata/shopify-store-leads-contacts --silent --output-dataset
MCP server setup
{
"mcpServers": {
"apify": {
"type": "http",
"url": "https://mcp.apify.com/?tools=fetch-actor-details,pennydata/shopify-store-leads-contacts"
}
}
}
The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an Authorization: Bearer <APIFY_API_TOKEN> header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).
OpenAPI specification
Download the OpenAPI definition: https://api.apify.com/v2/actors/KM8BsJVj5ClCTtFYb/builds/juOadmwOawIesJn0g/openapi.json