# Shopify Store Leads — Catalog Size, Apps, Contacts (`autofacts/shopify-store-leads-catalog-size-apps-contacts`) Actor

Find and qualify Shopify stores, or enrich domains you own. Each lead returns catalog size, price band, theme, installed apps, published policies, new products a month, emails, phones and socials. Filter by country, category, keyword, size, ships-to, price or apps. You pay only for rows you keep.

- **URL**: https://apify.com/autofacts/shopify-store-leads-catalog-size-apps-contacts.md
- **Developed by:** [Richard Feng](https://apify.com/autofacts) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.50 / 1,000 qualified stores

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](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Shopify Store Leads — Catalog Size, Apps, Contacts

**Shopify store leads** with the numbers you need to qualify them — not just a domain and
an email address.

Most Shopify lead lists tell you a store exists and hand you an email address. This one
also tells you **how big the store is, what it runs, and whether it is still trading** —
the product count, the price band, the theme, the installed apps, how many new products it
publishes a month, and how many days since the merchant last published one.

Use it two ways, or both in one run:

- **Discover** stores you don't know yet — filter by country, product category, keyword,
  catalog size or how recently the store published.
- **Enrich** domains you already have — paste them in and get the full profile back.

**You are never charged for a store a filter rejected.** You pay for rows you keep.

### How to use it

1. Open the Actor and click **Try for free**.
2. To profile stores you already know, paste their domains into **Store URLs** — one per
   line, `gorjana.com` or `https://gorjana.com` both work.
3. To find new stores instead, leave **Store URLs** empty and set the filters:
   **Countries**, **Product category**, **Keyword**, **Minimum products** or
   **Published a product within (days)**.
4. Set **Maximum stores** to cap how many rows the run can return — this is the hard cap
   on what the run can charge you.
5. Click **Start**, then download the results from the **Storage** tab as JSON, CSV,
   Excel or XML — or pull them from the API.

A run with the default input finishes in well under a minute. Start small, check the
rows look how you expect, then raise **Maximum stores**.

### Input

Everything is optional. With no input at all, the Actor discovers stores using its
built-in seed list.

| Field | Type | Default | What it does |
|---|---|---|---|
| `storeUrls` | array | — | Domains to profile. Set this and discovery is skipped entirely. |
| `query` | string | — | Keyword matched against store name, description and domain. |
| `category` | string | — | Product category, matched against the store's own product types. |
| `countries` | array | — | Two-letter country codes, e.g. `["US","GB"]`. |
| `minProducts` | integer | — | Skip stores with fewer products than this. |
| `maxProducts` | integer | — | Skip stores with more products than this. |
| `activeWithinDays` | integer | — | Only stores that published a product this recently. |
| `excludeKeywords` | array | — | Drop stores matching any of these words. |
| `shipsToCountries` | array | — | Only stores shipping to at least one of these countries. |
| `minAvgPrice` | integer | — | Minimum average product price, in minor units — `5000` is `50.00`. |
| `maxAvgPrice` | integer | — | Maximum average product price, in minor units. |
| `inStockOnly` | boolean | `false` | Only stores with something actually in stock. |
| `hasApps` | array | — | Only stores running these apps, by handle — `klaviyo`, `judge-me`, `okendo`. |
| `appMatchMode` | string | `any` | Whether `hasApps` means any of them or all of them. |
| `excludeApps` | array | — | Drop stores running any of these apps — find stores that have *not* installed a competitor yet. |
| `hasSocials` | array | — | Only stores linking at least one of these networks — `instagram`, `tiktok`, `facebook`. |
| `requireEmail` | boolean | `false` | Only stores that publish a business email address. |
| `requirePhone` | boolean | `false` | Only stores that publish a phone number. |
| `maxItems` | integer | `100` | Maximum stores to return. The hard cap on what the run can charge. To cap spend in currency instead, set **Maximum total charge** in the run options — the platform enforces it. |
| `includeCatalog` | boolean | `true` | Product/collection counts, price band, types, vendors. |
| `includeContacts` | boolean | `true` | Business emails, phones and social profiles. |
| `includeTechStack` | boolean | `true` | Theme, installed apps, accepted card brands. |
| `useSeed` | boolean | `true` | Start discovery from the built-in list of verified stores. |
| `ccIndex` | string | — | Pin a specific Common Crawl index. Leave empty for the newest. |
| `proxy` | object | Apify Proxy | Leave this on — see the FAQ. |

**Find active US jewellery stores with a real catalog**

```json
{ "countries": ["US"], "category": "jewelry", "minProducts": 50, "activeWithinDays": 90, "maxItems": 500 }
```

**Find mid-market stores that ship to you, are trading, and can be contacted**

```json
{ "shipsToCountries": ["US"], "minAvgPrice": 5000, "maxAvgPrice": 30000, "inStockOnly": true, "requireEmail": true, "maxItems": 200 }
```

**Find stores already running an app you integrate with**

```json
{ "hasApps": ["klaviyo", "okendo"], "hasSocials": ["instagram"], "countries": ["US", "GB"], "maxItems": 200 }
```

**Profile stores you already know**

```json
{ "storeUrls": ["gorjana.com", "allbirds.com"], "maxItems": 10 }
```

### Output

One row per store. Real output, trimmed for length:

```json
{
  "url": "https://www.gorjana.com/",
  "domain": "www.gorjana.com",
  "myshopifyDomain": "gorjana.myshopify.com",
  "name": "gorjana",
  "description": "Complete your look with jewelry designed to layer...",
  "country": "US",
  "province": "California",
  "city": "Laguna Beach",
  "currency": "USD",
  "languages": ["en"],
  "shipsToCountries": ["AC", "AD", "AE", "..."],
  "catalog": {
    "productCount": 991,
    "collectionCount": 573,
    "priceMin": 3000,
    "priceMax": 599000,
    "priceAvg": 17746,
    "pricedFrom": 250,
    "productTypes": ["Anklets", "Bracelets", "Earrings", "Necklaces", "Rings"],
    "vendors": ["Fine", "Layering Set", "Spring 2026 Fashion"],
    "sizeBand": "large"
  },
  "activity": {
    "newestProductUTC": 1789392506,
    "oldestProductUTC": 1767224138,
    "daysSinceLastProduct": 0,
    "isActive": true,
    "productsPerMonth": 29.7
  },
  "theme": { "name": "[RELEASE] 5.1.0 | Fixed 2", "id": 163612524675, "version": "" },
  "apps": [{ "handle": "klaviyo", "name": "Klaviyo" }, { "handle": "okendo", "name": "Okendo" }],
  "policies": [],
  "announcementBarText": null,
  "cardBrands": ["visa", "master", "american_express", "discover"],
  "offersShopPayInstallments": true,
  "contacts": {
    "emails": ["customercare@gorjana.com"],
    "phones": [],
    "socials": {
      "instagram": "http://instagram.com/gorjana",
      "tiktok": "https://www.tiktok.com/@gorjana",
      "facebook": "https://www.facebook.com/gorjanabrand",
      "youtube": "https://www.youtube.com/@gorjanabrand",
      "x": "https://twitter.com/gorjana_brand"
    }
  },
  "discoveredVia": "input",
  "scrapedUTC": 1789450857
}
```

Two conventions worth knowing: **prices are integers in minor units**, so `17746` means
`$177.46`, and **timestamps are epoch seconds**.

`productsPerMonth` is what separates a store publishing 30 products a month from one that
published its last 30 over four years — `isActive` calls both of them active.

`policies` is empty for this store because gorjana links its policies as ordinary pages
rather than at Shopify's canonical `/policies/…` URLs, which is worth knowing before you
filter on it. Across a sample of ordinary stores about eight in ten use the canonical form
and return a list like `["refund-policy", "shipping-policy", "terms-of-service"]` — but
**large direct-to-consumer brands mostly do not**: of nine well-known ones checked, one
published canonical policy URLs. Read an empty list as "not published at the canonical
URL", never as "this store has no refund policy".

`announcementBarText` is the storefront's announcement bar, verbatim and in whatever
language the merchant wrote it in — `"FREE SHIPPING FOR ORDERS OVER £70"`,
`"DESCUENTO DEL 10% PARA COMPRAS SUPERIORES A 80€"`, `"Final Sale. No Returns or
Exchanges."` It tells you what the store is promoting right now, which is the difference
between a shop that is trading and one that is merely online. Null for gorjana, and for
about four stores in ten.

### What this Actor does not do

Stated up front so you can judge fit before paying.

- **The price band is a sample, not a census.** Computed from up to 250 products;
  `catalog.pricedFrom` tells you how many were used. Product and collection counts are
  exact — the store reports those itself.
- **App detection sees the public storefront only.** A purely back-office app with no
  asset on the page is invisible. A detected app is real; an undetected one is not proof
  of absence.
- **Contacts are business addresses, not people.** It keeps role addresses (`info@`,
  `support@`, `sales@`) and drops anything shaped like a person's name. No named
  individuals, job titles or LinkedIn profiles — only what the business publishes about
  itself.
- **Discovery finds stores through their `.myshopify.com` address.** Roughly half have a
  custom domain configured; a store only ever crawled at its custom domain may not be
  discoverable, though you can always profile it directly via `storeUrls`.
- **`policies` counts Shopify's canonical policy pages only.** A store that hand-builds a
  `/pages/refund-policy` instead is reported as having none. Empty means "not published at
  the canonical URL", not "this store has no refund policy".
- **`announcementBarText` is the bar's text, not a parsed promotion.** There is no
  free-shipping threshold, discount percentage or promo-code field, and that is measured,
  not an omission: the copy is written in the store's own language, so an English pattern
  would report every non-English store as running no promotion — and a percent-off pattern
  run over a storefront matches product prices, not promotions. You get the sentence; read
  it yourself. **How much of a rotating bar you get depends on the theme.** Where each
  slide sits in its own element you get one message; where the theme puts them all in one
  element you get them all, joined — `tentree.com` returns `"FREE SHIPPING ON ORDERS OVER
  $100 FREE SHIPPING ENDS MONDAY. CODE: FREESHIP DEEPEST DISCOUNTS: 30-50% OFF SHOP NOW
  EVERY ITEM PLANTS 10 TREES"`, which is four promotions at once. Where a theme splits one
  message across nested elements you get the innermost piece. Null means the store has no
  bar, or fills it in after the page renders.
- **`productsPerMonth` is measured over the sampled window.** For a store with more
  products than the sample it is the recent rate; for a smaller store it is the lifetime
  average. It is `null` when the sample is too thin to support a rate.
- **The price filters compare each store in its own currency.** `minAvgPrice` is matched
  against the store's own prices, so a run spanning several currencies is comparing
  numbers, not value. Pair them with `countries` when that matters.
- **Stores that don't answer are skipped, not guessed at** — and cost you nothing.

### Works well with

| Actor | Use it for |
|---|---|
| [Shopify Scraper](https://apify.com/autofacts/shopify) | Products and collections from a store this Actor found. The natural next step: qualify the store here, then pull its catalog there. |
| [Schema Markup Scraper & SEO Auditor](https://apify.com/autofacts/metadata-scraper) | Audit the structured data and SEO of any store in your list. |

### FAQ

**Do I need to configure the proxy?**
No — leave it on the default. Shopify limits requests per IP address, so the Actor
rotates through Apify Proxy automatically. Turning the proxy off makes every store share
one address and get refused together.

**Is scraping these stores legal?**
Everything returned is data the store publishes openly on its own storefront for anyone
to read — no login, no paywall, nothing personal. Contacts are limited to business role
addresses the merchant chose to publish. You are responsible for how you use the output,
including under GDPR, CAN-SPAM and local marketing rules.

**How many stores can I get?**
As many as `maxItems` allows. For large runs, raise it and let the run work through the
candidates; the Actor stops as soon as it has stored what you asked for.

**Can I get results automatically?**
Yes. Schedule the Actor, or call it from the Apify API, or connect it to Make, Zapier,
Slack, Google Sheets and the other Apify integrations. Results can also be pushed to a
webhook when a run finishes.

**Why are some fields empty?**
Not every store publishes everything. A store with no public email returns an empty
`contacts.emails` rather than a guess. Empty means "not published", not "not checked".

**Can you add a field or a filter?**
Yes — open an issue on the Actor and describe what you need.

# Changelog

This Actor's version history is a separate document: https://apify.com/autofacts/shopify-store-leads-catalog-size-apps-contacts/changelog.md

# Actor input Schema

## `storeUrls` (type: `array`):

Stores you already know, to enrich directly. When this is set, discovery is skipped entirely and only these stores are processed. Accepts a full URL or a bare domain.

## `query` (type: `string`):

Keep only stores whose name, description or domain contains this text. Applied at the first request, so non-matching stores cost nothing.

## `category` (type: `string`):

Keep only stores selling a matching product type (substring match against the store's own product types, e.g. 'jewelry', 'coffee', 'skincare').

## `countries` (type: `array`):

Keep only stores based in these countries. Two-letter ISO codes, e.g. US, GB, AU.

## `minProducts` (type: `integer`):

Keep only stores with at least this many published products. Evaluated on the first request.

## `maxProducts` (type: `integer`):

Keep only stores with at most this many published products.

## `activeWithinDays` (type: `integer`):

Keep only stores that published a product in the last N days — the cheapest way to drop abandoned storefronts.

## `excludeKeywords` (type: `array`):

Drop stores whose name, description or domain contains any of these.

## `shipsToCountries` (type: `array`):

Keep only stores that ship to at least one of these countries. Two-letter codes. Decided from /meta.json, so a store that does not ship where you sell is dropped for a single request.

## `minAvgPrice` (type: `integer`):

Keep only stores whose average product price is at least this, in minor units of the store's own currency — 5000 means 50.00. Sampled from up to 250 products.

## `maxAvgPrice` (type: `integer`):

Keep only stores whose average product price is at most this, in minor units of the store's own currency — 5000 means 50.00.

## `inStockOnly` (type: `boolean`):

Keep only stores with at least one in-stock product in the sample. Drops abandoned and sold-out storefronts.

## `hasApps` (type: `array`):

Keep only stores running at least one of these apps, by handle — for example klaviyo, okendo, judgeme, rechargepayments. Detected from the public storefront.

## `appMatchMode` (type: `string`):

How 'Runs any of these apps' matches: any of them, or all of them.

## `excludeApps` (type: `array`):

Drop stores running any of these apps, by handle. Useful for finding stores that have NOT yet installed a competitor to what you sell.

## `hasSocials` (type: `array`):

Keep only stores linking at least one of these networks: instagram, tiktok, facebook, youtube, x, pinterest, linkedin.

## `requireEmail` (type: `boolean`):

Keep only stores that publish a business email address.

## `requirePhone` (type: `boolean`):

Keep only stores that publish a phone number.

## `maxItems` (type: `integer`):

Stop after storing this many stores. This is the hard cap on what the run can charge.

## `includeCatalog` (type: `boolean`):

Price band, product types and vendors. Costs one extra request per store; the product and collection counts are returned either way.

## `includeContacts` (type: `boolean`):

Role email addresses (info@, support@), customer-service phone and social profiles published on the storefront. Named-individual addresses are filtered out.

## `includeTechStack` (type: `boolean`):

Storefront theme and the third-party apps detectable from the page's own assets.

## `useSeed` (type: `boolean`):

Start discovery from the bundled list of known-good Shopify stores before falling back to Common Crawl. Makes short runs fast.

## `ccIndex` (type: `string`):

Which Common Crawl index to discover from, e.g. CC-MAIN-2026-34. Leave empty to use the newest available — the index is republished monthly and a pinned value stops refreshing the seed.

## `proxy` (type: `object`):

Used for every request the actor makes. Shopify rate-limits /meta.json per client IP, so rotation matters more than reputation here: the automatic Apify Proxy is enough. Leave it on — running without a proxy means sharing one egress IP with every other actor on the platform and being rate-limited as a group. No proxy group is requested, so the run uses whichever groups your own account has.

## `previousDatasetId` (type: `string`):

Reserved name, accepted and ignored. Hidden from the form because it does nothing: change tracking was evaluated and deliberately not built. The name is kept so adding it later is not a breaking input change.

## Actor input object example

```json
{
  "storeUrls": [
    "gorjana.com",
    "tentree.com",
    "ayvii.com"
  ],
  "shipsToCountries": [
    "US"
  ],
  "inStockOnly": false,
  "appMatchMode": "any",
  "requireEmail": false,
  "requirePhone": false,
  "maxItems": 100,
  "includeCatalog": true,
  "includeContacts": true,
  "includeTechStack": true,
  "useSeed": true,
  "proxy": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `stores` (type: `string`):

One row per store: identity, catalog, activity, tech stack and contacts.

# 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

```javascript
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 = {
    "storeUrls": [
        "gorjana.com",
        "tentree.com",
        "ayvii.com"
    ],
    "shipsToCountries": [
        "US"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("autofacts/shopify-store-leads-catalog-size-apps-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

```python
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 = {
    "storeUrls": [
        "gorjana.com",
        "tentree.com",
        "ayvii.com",
    ],
    "shipsToCountries": ["US"],
}

# Run the Actor and wait for it to finish
run = client.actor("autofacts/shopify-store-leads-catalog-size-apps-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

```bash
echo '{
  "storeUrls": [
    "gorjana.com",
    "tentree.com",
    "ayvii.com"
  ],
  "shipsToCountries": [
    "US"
  ]
}' |
apify call autofacts/shopify-store-leads-catalog-size-apps-contacts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,autofacts/shopify-store-leads-catalog-size-apps-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/pU2hf0UhalIpjJFzh/builds/OP8ketnjwDL27Sg39/openapi.json
