# Shopify Email Scraper: Store Contacts, Phones & Socials (`datagleaner/shopify-store-contacts`) Actor

Shopify store contacts lookup by domain: give store URLs, get each store's public emails, phones, address and socials plus Shopify facts (store name, country, currency, myshopify domain, product count). $0.003 per store with an email; stores with nothing found are free.

- **URL**: https://apify.com/datagleaner/shopify-store-contacts.md
- **Developed by:** [Data Gleaner](https://apify.com/datagleaner) (community)
- **Categories:** Lead generation, Integrations, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.10 / 1,000 store with emails

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 Contact Finder: Email, Phone & Socials

**Shopify store contacts by domain.** It answers "find the contact email for this Shopify store", "Shopify store owner email", "Shopify contact page" and "Shopify store contact finder" for a list of stores you already have. Give it store URLs or domains and get each store's **public emails, phone numbers, business address and social profiles**, plus its **Shopify facts**: store name, country, currency, myshopify domain and product count.

**You pay $0.003 only for a store where an email was found. A store with nothing found is free.** No login, no API key, no browser.

It is a lookup, not a discovery database: you bring the stores (from a lead list, a competitor list, a store-search tool or a CRM) and it fills in how to reach each one.

### What it does

For each store, it reads what any visitor can see:

1. **`/meta.json`**, Shopify's public store summary: the store name, country, currency, `name.myshopify.com` domain, product and collection counts, and the countries it ships to. This also tells whether the site runs on Shopify.
2. **Up to 5 pages, stopping as soon as an email on the store's own domain is found**: the home page (header and footer), the contact page the store links to, `/pages/contact`, `/pages/contact-us`, `/policies/terms-of-service` and `/policies/privacy-policy`. The `/policies/contact-information` page (trade name, phone, email, physical address) is read when the store links to it.
3. **`/products.json`** (optional, one request) for the newest product's creation date and the latest publish date, a quick sign of whether the store is active.

Most stores take 1 to 3 pages. Sites that are not on Shopify are still checked (home page, `/contact`, `/contact-us`, `/about`, `/privacy-policy`) and come back with `isShopify: false`.

**Only the store's own addresses count.** Theme placeholders (`your@email.com`, `name@domain.com`, `youremail@example.com`), image file names, no-reply mailboxes and addresses of Shopify, its apps, payment and shipping partners (Klaviyo, Judge.me, Gorgias, PayPal and so on) are dropped. Addresses on other businesses' domains, such as a parent company, are kept apart in `otherEmails` and are never billed. The best mailbox for outreach is picked as `primaryEmail`: the store's own domain first, then general mailboxes (`hello@`, `info@`, `support@`) before legal or privacy desks.

### Use cases

- **Lead lists for agencies, apps and suppliers selling to Shopify merchants**: turn a list of store domains into emails, phones and socials, and pay only for the stores you can actually reach.
- **Enriching a store-search or competitor export** that has domains but no contacts.
- **Qualifying stores** by country, currency, catalog size and how recently they added a product.
- **AI agents** that need "the contact email of this Shopify store" for one or many domains, with a price per hit and an output that is the same every time.
- Finding a brand's Instagram, TikTok, Facebook, X, YouTube, Pinterest and LinkedIn from its store.

### Input

| Field | Meaning |
|---|---|
| `storeUrls` | Stores to look up: a domain (`brooklinen.com`), any URL on the store, or `name.myshopify.com`. Duplicates are removed. |
| `datasetId` | Optional. A dataset of another Actor's run; its store sites are added to `storeUrls`. See below. |
| `datasetField`, `maxDatasetItems` | Field to read first (default tries `website`, `storeUrl`, `shopUrl`, `myshopifyDomain`, `domain`, `url`...) and a cap on sites (default 1000). |
| `maxPagesPerStore` | Most HTML pages per store, home page included (default 5, max 10). Reading stops at the first own email. |
| `includeProductStats` | Read `/products.json` once for product dates (default true). |
| `onlyWithEmail` | Output only stores with an email (default false). Stores without one are free either way. |
| `pagePaths` | Your own order of pages to check after the home page, e.g. `["/pages/about", "/policies/terms-of-service"]`. |
| `maxConcurrency` | Stores processed at once (default 5). |
| `delaySecs` | Pause between requests to the same store (default 1 second, randomised). |
| `requestTimeoutSecs`, `respectRobotsTxt`, `proxyConfiguration` | Network options. No proxy by default. |

Leave everything empty to run 3 example stores (beardbrand.com, tentree.com, drinkolipop.com; about 10 seconds, 3 x $0.003 = $0.009).

```json
{
  "storeUrls": ["brooklinen.com", "https://www.allbirds.com", "beardbrand.myshopify.com"],
  "maxPagesPerStore": 5,
  "includeProductStats": true,
  "onlyWithEmail": false
}
```

### Run it after Google Maps Scraper or any store list

Open the source Actor's **Integrations** tab, add this Actor (`datagleaner/shopify-store-contacts`) and set its input to:

```json
{"datasetId": "{{resource.defaultDatasetId}}"}
```

Every time the source run succeeds, this Actor reads the store sites from its results (`website`, `storeUrl`, `shopUrl`, `myshopifyDomain`, `domain`, `url` and similar, the first usable one), skips items with no site, map or social links, and duplicates, and looks up each store (at most `maxDatasetItems`, default 1000). Sites that are not on Shopify come back with `isShopify: false`. Any site, Shopify or not, is billed only when a public email is found, so a Google Maps list costs $0.003 per business with an email and nothing for the rest. Works with Google Maps Scraper results and other store-list Actors. From code:

```python
run = client.actor("datagleaner/shopify-store-contacts").call(run_input={"datasetId": "SOURCE_DATASET_ID"})
```

The status message tells how many sites were read, how many items had none, and whether the dataset could not be read.

It also works as an integration without setting any input: in the source Actor's Integrations tab choose Add integration and pick this Actor, and the dataset is passed automatically.

### Output

One row per store. Fields that were not found are empty or null, never invented. A real row:

```json
{
  "domain": "brooklinen.com",
  "storeUrl": "https://www.brooklinen.com",
  "isShopify": true,
  "storeName": "Brooklinen",
  "myshopifyDomain": "brooklinen2.myshopify.com",
  "country": "US",
  "currency": "USD",
  "description": "Discover Brooklinen's premium bedding collection. Shop soft sheets, cozy comforters, and s...",
  "primaryEmail": "hello@brooklinen.com",
  "emails": [
    {"email": "hello@brooklinen.com", "source": "contact-page", "foundOn": "https://www.brooklinen.com/pages/contact"},
    {"email": "press@brooklinen.com", "source": "contact-page", "foundOn": "https://www.brooklinen.com/pages/contact"},
    {"email": "influencer@brooklinen.com", "source": "contact-page", "foundOn": "https://www.brooklinen.com/pages/contact"},
    {"email": "sales@brooklinenbusiness.com", "source": "contact-page", "foundOn": "https://www.brooklinen.com/pages/contact"}
  ],
  "otherEmails": [],
  "phones": [{"phone": "+16467987447", "raw": "6467987447", "source": "contact-page"}],
  "primaryPhone": "+16467987447",
  "address": {"formatted": "Brooklyn, New York, US", "street": null, "city": "Brooklyn", "region": "New York",
              "postalCode": null, "country": "US", "source": "shopify-meta"},
  "socials": {
    "instagram": "https://www.instagram.com/brooklinen",
    "facebook": "https://www.facebook.com/Brooklinen",
    "tiktok": "https://www.tiktok.com/@brooklinen",
    "x": "https://x.com/brooklinen",
    "youtube": "https://www.youtube.com/c/Brooklinen",
    "pinterest": "https://www.pinterest.com/brooklinen",
    "linkedin": null
  },
  "productCount": 523,
  "collectionCount": 787,
  "newestProductAt": "2026-10-08T19:25:58+00:00",
  "lastPublishedAt": "2026-10-08T21:24:05+00:00",
  "shipsToCountries": ["AU", "BE", "CA"],
  "hasEmail": true,
  "pagesFetched": 2,
  "pageUrls": ["https://www.brooklinen.com/", "https://www.brooklinen.com/pages/contact"],
  "status": "ok",
  "error": null,
  "input": "brooklinen.com",
  "scrapedAt": "2026-10-09T14:39:27+00:00"
}
```

- `emails[].source` is the kind of page: `homepage`, `contact-page`, `contact-information`, `terms-of-service`, `privacy-policy`, `policy` or `page`. `foundOn` is the exact URL.
- `address.source` is `contact-information` (the store's legal contact page, full street address), `homepage` or another page (structured data), or `shopify-meta` (city, region and country from Shopify's store summary).
- `phones` are E.164. On the home page only the header and footer are read for phone numbers, so prices and SKUs are not mistaken for one.
- `status` is `ok`, `passwordProtected`, `unreachable`, `rateLimited` (the store kept answering HTTP 429), `invalidUrl` or `error`. Rows that are not `ok` are free unless an email was still found.

The dataset has two views: **Store contacts** (one row per store, socials flattened into columns, ready for CSV or Sheets) and **All emails** (one row per email, with the page it was on).

### Pricing

Pay per answer, one event:

- **`store-with-email`: $0.003** for each store where at least one public email of the store's own was found.
- **A store with nothing found is free.** It is still returned with its Shopify facts, phones and socials.
- Unreachable, rate-limited, invalid and password-protected stores are free.

**Worked example.** You look up 1,000 Shopify stores. About 85% show a public email, so 850 x $0.003 = **$2.55**. The other 150 cost nothing. Turn on `onlyWithEmail` to leave them out of the dataset; the price is the same.

Other Shopify lead tools charge per row whether or not an email is found (around $0.004 to $0.006 a store on the most used ones), or per contact reveal (up to $0.075 each, 25 times this price). Set a maximum total charge on the run and the Actor stops cleanly when it is reached.

### What to expect

On a test run of 20 well-known stores, 18 returned an email on the store's own domain (90%), 12 a phone number, 17 an address, and every one at least one social profile, using about 4.7 requests per store. Coverage on small stores depends on what the owner published; a store that hides its email behind a contact form has none to find.

### Limits

- **Public pages only.** It never submits contact forms, logs in, or asks a store for anything.
- **Headless storefronts** (custom front ends on Shopify, such as Hydrogen) may not serve `/meta.json`, `/products.json` or the standard `/pages/` paths, so they return fewer fields. They are still flagged `isShopify: true` when Shopify's headers show.
- `productCount` is Shopify's published-product count. When `/meta.json` is missing, it falls back to the number listed on the first `/products.json` page, which stops at 250.
- `newestProductAt` and `lastPublishedAt` come from the first `/products.json` page (up to 250 products), so on very large catalogs they are the newest among those.
- Shopify rate-limits heavy traffic from one IP (HTTP 429) across all its stores at once. The Actor waits and retries with backoff, one 429 slows every worker, and stores still rate-limited get one more try at the end of the run. Any store that stays rate-limited comes back with `status: "rateLimited"` and is free. On large runs lower `maxConcurrency`, raise `delaySecs`, or set `proxyConfiguration`.
- Shopify's default `robots.txt` asks search engines not to index `/policies/` on every store. This Actor reads those pages one at a time, the way a visitor would, unless you turn on `respectRobotsTxt`, which skips them and finds fewer emails.

### Use it from n8n, Make, Zapier or an AI agent

Actor ID: `datagleaner/shopify-store-contacts`

Minimal input:

```
{"storeUrls": ["beardbrand.com", "brooklinen.com"], "onlyWithEmail": true}
```

Each tool below runs this Actor with your own Apify API token.

- **n8n:** add the **Apify** node (`@apify/n8n-nodes-apify`). On n8n Cloud you install it from the community node registry. Choose **Run an Actor and get dataset**, set Actor to `datagleaner/shopify-store-contacts` and paste the input above.
- **Make:** use the Apify app's **Run an Actor** module, then **Get Dataset Items** to read the results. **Watch Actor Runs** can trigger a scenario when a run finishes.
- **Zapier:** use the Apify action **Run Actor**, then the search **Fetch dataset items**. The trigger **Finished Actor run** starts a Zap when a run ends.
- **AI agents (MCP):** connect to `https://mcp.apify.com/?tools=datagleaner/shopify-store-contacts`. Without a token, an agent can still find Actors with the `search-actors` tool, but running this one needs your Apify account. In Claude Code:

  ```
    claude mcp add --transport http apify "https://mcp.apify.com/?tools=datagleaner/shopify-store-contacts"
  ```

  Then run `/mcp` to sign in to Apify in your browser. Then ask in plain language, for example: "Find the contact email and Instagram for brooklinen.com and allbirds.com, and say which of these 50 Shopify stores sell in EUR and how to reach them."
- **LangChain (Python):**

```python
## pip install langchain-apify, then set APIFY_TOKEN in your environment
import json
from langchain_apify import ApifyActorsTool
tool = ApifyActorsTool("datagleaner/shopify-store-contacts")
result = tool.invoke({"run_input": json.loads('{"storeUrls": ["beardbrand.com", "brooklinen.com"], "onlyWithEmail": true}')})
```

### FAQ

**Does it find stores for me?** No. It is a per-domain lookup. Bring the store domains; it returns how to reach each one.

**Does it work on stores that are not on Shopify?** Yes. They are checked the same way on generic contact paths and come back with `isShopify: false` and no Shopify facts.

**Is the email the owner's personal address?** It is whatever the store publishes: usually a support or hello mailbox, sometimes the founder's address on small stores. Nothing hidden is revealed.

**Why is an email in `otherEmails` and not billed?** It sits on another business's domain (a parent company, an agency, a 3PL). It may still be useful, but it is not the store's own address, so it does not count as a hit.

**Can I pass `name.myshopify.com`?** Yes. The Actor reads the store's primary domain from Shopify and continues there.

**What happens with an empty input?** It runs 3 example stores and says so in the run's status message.

### Responsible use

This Actor reads public pages only. Email addresses and phone numbers can be personal data, especially for small stores run by one person. You are responsible for using the results lawfully, including consent and opt-out rules for outreach (GDPR, CAN-SPAM, CASL and local law), and for respecting the terms of the platforms you use them on.

# Actor input Schema

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

Shopify stores to look up, one per line: a domain (`brooklinen.com`), any URL on the store (`https://www.allbirds.com/products/x`) or a `name.myshopify.com` address. Sites that are not on Shopify are still checked and flagged `isShopify: false`. Leave empty to run 3 example stores.

## `datasetId` (type: `string`):

Optional. Read store sites from this dataset, for example Google Maps Scraper or a Shopify store list, and add them to the stores above. Each item's `website` (or `storeUrl`, `shopUrl`, `myshopifyDomain`, `domain`, `url`, `homepage`...) is used; items without one are skipped. In an integration use `{{resource.defaultDatasetId}}`.

## `datasetField` (type: `string`):

Optional. Field name to read first in each dataset item (use `a.b` for a nested field). Empty tries the usual names.

## `maxDatasetItems` (type: `integer`):

Most distinct sites read from the dataset. Only stores with an email are billed.

## `maxPagesPerStore` (type: `integer`):

Most HTML pages read per store, the home page included. The run stops reading a store as soon as an email on the store's own domain is found, so most stores take 1 to 3 pages. /meta.json and /products.json are extra.

## `includeProductStats` (type: `boolean`):

Read /products.json once per Shopify store for the newest product's date and the latest publish date. The product count comes from Shopify's store summary either way.

## `onlyWithEmail` (type: `boolean`):

Output only stores where at least one public email was found. Stores without one are free either way.

## `pagePaths` (type: `array`):

Paths read in this order after the home page until an email is found. Empty uses /pages/contact, /pages/contact-us, /policies/terms-of-service, /policies/privacy-policy on Shopify stores (and /contact, /contact-us, /about, /privacy-policy elsewhere). A contact page the store links to from its home page is always read first.

## `maxConcurrency` (type: `integer`):

How many stores are processed at the same time. Shopify slows down heavy traffic from one IP, so keep it modest on large runs.

## `delaySecs` (type: `number`):

Pause of 50 to 100% of this many seconds between requests to the same store, and a random start delay up to this value per store.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for each page before retrying.

## `respectRobotsTxt` (type: `boolean`):

Skip pages a store's robots.txt disallows. Shopify's default robots.txt disallows /policies/ for search engines on every store, so turning this on skips the terms and privacy pages and finds fewer emails.

## `proxyConfiguration` (type: `object`):

Optional. Defaults to no proxy. Use Apify Proxy if many stores answer with rate limits (HTTP 429) on large runs.

## Actor input object example

```json
{
  "storeUrls": [
    "beardbrand.com",
    "brooklinen.com",
    "drinkolipop.com"
  ],
  "maxDatasetItems": 1000,
  "maxPagesPerStore": 5,
  "includeProductStats": true,
  "onlyWithEmail": false,
  "maxConcurrency": 5,
  "delaySecs": 1,
  "requestTimeoutSecs": 20,
  "respectRobotsTxt": false
}
```

# 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

```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": [
        "beardbrand.com",
        "brooklinen.com",
        "drinkolipop.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datagleaner/shopify-store-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": [
        "beardbrand.com",
        "brooklinen.com",
        "drinkolipop.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("datagleaner/shopify-store-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": [
    "beardbrand.com",
    "brooklinen.com",
    "drinkolipop.com"
  ]
}' |
apify call datagleaner/shopify-store-contacts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datagleaner/shopify-store-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/vh82bQtzJgYXFnYWA/builds/vGDXJwaL4LdhKmB4b/openapi.json
