# Shopify Store Analyzer — Theme, Apps, Products per Domain (`scrapersdelight/shopify-store-analyzer`) Actor

Feed it domains, get one intelligence row per store: is it Shopify (4 detection tiers), theme + version + custom flag, exact product & collection counts, price range, currency, 70+ detectable apps/pixels/payment methods, socials and contact email. No discovery — it analyzes the list you have.

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

## Pricing

$5.00 / 1,000 per store analyzeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Shopify Store Analyzer — Theme, Apps & Product Counts per Domain

Feed this Actor the store domains you already hold; it returns **one intelligence row per
STORE** (not per product): **isShopify** with a 4-tier detection confidence,
**myshopifyDomain, shopId, themeName, themeVersion, themeIsCustom, productCount,
collectionCount, priceMin/priceMax, currency, countryCode**, the **apps, ad pixels and
payment methods** detectable from the homepage (**70 signatures**: 49 apps, 14 pixels, 7
payment methods), **socialLinks**, and **contactEmail** where the homepage publishes one.
No login, no API key, no browser. No discovery and no crawling beyond the domains you give
it — this is the analysis side of the pipe, for lists you already have.

**Measured on a real 45-domain mixed list (2026-08-15): 39 billed verdicts — 36 Shopify,
3 correctly identified as not Shopify — and 6 no-verdict domains (4 store-side bot walls,
2 dead) returned as rows but NOT billed.** You pay $0.005 only when you get an answer.

```json
{
  "domains": ["allbirds.com", "gymshark.com", "colourpop.com", "deathwishcoffee.com",
              "stevemadden.com", "kyliecosmetics.com", "fashionnova.com", "ruggable.com"],
  "maxProductPages": 8,
  "fetchCollections": true
}
```

Click **Try for free** and hit **Start** — that is the input the Actor ships with. A
platform run on those shipped defaults returned **8 rows in 35 seconds for $0.04**: 8/8
Shopify verdicts including one headless frontend with an open catalog (fashionnova) and one
fully headless build (ruggable), each labeled with its detection tier instead of being
silently guessed at — and the run log printed its own billing reconciliation: 8 events
charged for 8 rows, 1:1.

***

### The wedge: one row per store, and you pick the stores

Nearly every "Shopify scraper" on the Store is a **product** scraper — you give it one
store, it gives you thousands of product rows. This Actor answers a different question:
**"what is this store?"** — asked across a whole list at once. Agencies qualifying a pitch
list, app vendors scoring leads by tech stack, and competitive researchers need the
store-level row (platform, theme, catalog size, price band, app stack), not 2,000 product
rows per domain.

The intelligence comes from three sources no estimate can fake:

- **The Shopify globals** every Liquid storefront inlines — `Shopify.shop` (the myshopify
  handle), `Shopify.theme` (name, version, and whether it came from the official theme
  store), currency, country, locale, shop id.
- **The public catalog API** (`/products.json`, paged at 250/page) — an **exact** product
  count on most stores (28 of the 33 counted stores were exact at the default depth), plus
  real price range, vendors, product types, and newest/oldest product publish dates — a
  live activity signal.
- **Homepage script signatures** — 70 curated app/pixel/payment fingerprints (Klaviyo,
  Recharge, Rebuy, Gorgias, Attentive, Judge.me, Meta/TikTok/Pinterest pixels, Shop Pay,
  Afterpay, Klarna…). Measured: **4.5 detected apps per Shopify store on average** (max 10).

**How this differs from our [Shopify App Store Leads Scraper](https://apify.com/scrapersdelight/shopify-app-leads-scraper):**
that Actor walks the Shopify **App Store** and produces one row per **app publisher**
(support email, developer website, ratings) — it is how you find the *sellers of apps*.
This Actor takes **store domains you already hold** and analyzes the *stores themselves*,
including which of those apps each store runs. Use them at opposite ends of the same
pipeline.

***

### Read this before you buy rows

Five things that would otherwise turn into a refund request.

1. **`contactEmail` is sparse: 38.9%** on the 36-store sample. It is whatever the
   homepage itself publishes (mailto links first, visible footer text second) — no
   guessing, no enrichment, no contact-form scraping. If you need an email on every row,
   pair this with an email finder; do not buy this expecting one.
2. **A verdict is billed even when the answer is "not Shopify."** The verdict is the
   product when you qualify a mixed list — nytimes.com, stripe.com and zara.com each cost
   $0.005 to rule out in the measured run. If you only want Shopify rows kept, set
   `emitNonShopify: false` and non-Shopify domains are skipped unbilled.
3. **Headless stores return the verdict but few fields.** ~19% of the measured Shopify
   stores (7 of 36) run custom frontends: `products-json-only` stores (fashionnova) still
   give you catalog counts and prices but no theme; `cdn-assets-only` stores (ruggable)
   give you little beyond the verdict. `shopifyDetection` tells you exactly how sure the
   row is — filter on it.
4. **`themeIsCustom: true` does not mean "not Dawn."** It means the theme was not
   installed from the official theme store. Big stores fork a base theme: deathwishcoffee
   reads `themeName: "Dawn"` + `themeIsCustom: true` (a customized Dawn). Only 3 of 26
   themed stores in the sample ran an unmodified official-store theme (Split, Pipeline,
   Symmetry — each with its `themeStoreId`).
5. **`currency` / `countryCode` are as served to a US datacenter exit IP.** Localized
   storefronts answer per-visitor (the same store served CAD to a Canadian IP and USD
   through the Apify proxy in testing). `priceMin`/`priceMax` come from `/products.json`,
   which serves the store's **base** currency regardless of visitor location.

***

### What you get

One row per unique input domain. `scrapedAt` is a full UTC timestamp; publish dates are
the store's own ISO timestamps.

| Group | Fields | Example |
|---|---|---|
| **Verdict** | `isShopify`, `shopifyDetection`, `status`, `passwordProtected` | `true` · `storefront` |
| **Identity** | `domain`, `finalUrl`, `myshopifyDomain`, `shopId`, `storeName`, `storeDescription` | `deathwishcoffee.myshopify.com` · `2717209` |
| **Theme** | `themeName`, `themeVersion`, `themeStoreId`, `themeIsCustom`, `themeLabel` | `Dawn` · `10.0.0` · custom |
| **Catalog** | `productCount` (+`IsExact`), `collectionCount` (+`IsExact`), `vendorCount`, `topVendors[]`, `topProductTypes[]` | `146` exact · `124` collections |
| **Pricing** | `priceMin`, `priceMax`, `currency` | `5` – `119.99` `USD` |
| **Activity** | `newestProductPublishedAt`, `oldestProductPublishedAt` | `2026-08-14…` |
| **Locale** | `countryCode`, `locale` | `US` · `en` |
| **Tech stack** | `appsDetected[]`, `pixelsDetected[]`, `paymentsDetected[]`, `appCount` | Klaviyo, Rebuy, Gorgias… |
| **Contact** | `contactEmail`, `contactEmails[]`, `socialLinks{}`, `organizationAddress` | `help@allbirds.com` |
| **Provenance** | `error` (no-verdict rows only), `scrapedAt` | |

`shopifyDetection` tiers, strongest first: `storefront` (Shopify globals on the page) →
`products-json-only` (headless frontend, open catalog API) → `headers-only` (Shopify
response headers) → `cdn-assets-only` (only cdn.shopify.com asset references — treat as
"almost certainly Shopify, headless") → `none` (not Shopify).

***

### Field fill — measured on 36 Shopify stores

A 45-domain mixed list (big DTC brands, mid-size stores, 3 non-Shopify controls, 1 dead
domain), 2026-08-15, through the Apify datacenter proxy. Percentages are against the 36
Shopify-verdict rows, sorted descending so the sparse fields are impossible to miss.

| Field | Fill | Notes |
|---|---|---|
| `storeName` / `storeDescription` | 97.2% | og:site\_name / meta description |
| `socialLinks` | 94.4% | share-links excluded; first profile link per network |
| `productCount` | 91.7% | 28 of 33 exact at the default depth; the rest flagged `productCountIsExact: false` |
| `collectionCount` | 91.7% | exact up to 1,000 |
| `priceMin` / `priceMax` | 91.7% | store base currency, from real variant prices |
| `vendorCount` / `topVendors` / `topProductTypes` | 91.7% | |
| `newestProductPublishedAt` / `oldest…` | 91.7% | catalog activity signal |
| `appsDetected` (≥1) | 91.7% | average 4.5 apps per store, max 10 |
| `paymentsDetected` (≥1) | 91.7% | Shop Pay, Afterpay, Klarna, Sezzle, Affirm, PayPal, Apple Pay |
| `myshopifyDomain` / `shopId` | 77.8% | absent on headless frontends |
| `currency` / `countryCode` / `locale` | 77.8% | absent on headless frontends |
| `pixelsDetected` (≥1) | 75.0% | Meta, TikTok, Pinterest, Snap, GTM/GA4, Microsoft, Criteo… |
| `themeName` / `themeVersion` | 72.2% | headless stores have no Liquid theme |
| **`contactEmail`** | **38.9%** | **homepage-published only — no guessing. Read gotcha 1.** |
| **`organizationAddress`** | **16.7%** | **JSON-LD only; most stores publish none** |
| **`themeStoreId`** | **low on big brands — 0 of 8 on the shipped default** | **Not a gap: it is `null` at the source whenever a store runs a forked or bespoke theme, which every large DTC brand does. Re-probed live 2026-08-19: allbirds, deathwishcoffee, stevemadden, kyliecosmetics, colourpop, brooklinen, thesill, pinklily, trueclassictees, ohpolly all publish `"theme_store_id":null`; beardbrand.com (stock *Split* theme) publishes `842` and the Actor returns it. `themeStoreId: null` alongside a non-null `themeName` is exactly what `themeIsCustom: true` means.** |

**Every run prints its own measured fill in the log.** The shipped 8-domain default
printed: productCount 87.5%, apps 87.5%, socials 87.5%, theme 75.0%, email 25.0%.

***

### How to run it

#### 1. Qualify a list (the usual job)

```json
{ "domains": ["brand-a.com", "brand-b.com", "brand-c.com"] }
```

Paste up to 10,000 domains, with or without `https://` — duplicate spellings
(`www.brand-a.com`, `https://brand-a.com/about`) are normalized and dropped **before
billing** (measured: a 6-entry input with 3 duplicate spellings billed exactly 2 rows).

#### 2. Shopify-only output

```json
{ "domains": ["…"], "emitNonShopify": false }
```

Non-Shopify domains are skipped and never billed. Use when your list is scraped from ads
or marketplaces and you only want the Shopify stores kept.

#### 3. Fast verdicts, no catalog walk

```json
{ "domains": ["…"], "maxProductPages": 1, "fetchCollections": false }
```

2 requests per store instead of 3–5. You keep the verdict, theme, apps, socials and email;
`productCount` stays exact only for stores under 250 products. Same price per store —
depth costs you time, not money.

#### 4. Deep catalog counts

```json
{ "domains": ["…"], "maxProductPages": 40 }
```

Counts up to 10,000 products exactly. `productCountIsExact: false` on a row means the cap
stopped the walk and the true catalog is larger than the number shown.

#### Scheduling and integrations

Save the input as a **Task** and attach an Apify **Schedule** for a weekly re-scan of your
target accounts (theme changes, app installs/uninstalls and catalog growth are all
re-measured each run). The dataset exports to CSV/JSON/Excel/Sheets and the standard Apify
integrations (Zapier, Make, n8n, webhooks, MCP). From the API:

```bash
curl -X POST "https://api.apify.com/v2/acts/scrapersdelight~shopify-store-analyzer/runs?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"domains":["allbirds.com","gymshark.com"]}'
```

***

### Sample row

A real row from an actual run on the shipped defaults, 2026-08-15:

```jsonc
{
  "domain": "deathwishcoffee.com",
  "finalUrl": "https://www.deathwishcoffee.com/",
  "status": "analyzed",
  "isShopify": true,
  "shopifyDetection": "storefront",
  "passwordProtected": false,
  "myshopifyDomain": "deathwishcoffee.myshopify.com",
  "shopId": 2717209,
  "storeName": "Death Wish Coffee",
  "storeDescription": "Bold, Smooth and Delicious since 2012. We source USDA Organic and Fair Trade Certified arabica and robusta coffee beans…",
  "themeName": "Dawn",
  "themeVersion": "10.0.0",
  "themeStoreId": null,
  "themeIsCustom": true,
  "themeLabel": "Copy of deathwish-shopify-20/production",
  "productCount": 146,
  "productCountIsExact": true,
  "collectionCount": 124,
  "collectionCountIsExact": true,
  "priceMin": 5,
  "priceMax": 119.99,
  "currency": "USD",
  "countryCode": "US",
  "locale": "en",
  "vendorCount": 15,
  "topVendors": ["Death Wish Coffee Company", "Death Wish Coffee", "Sportiqe", "Planet Apparel", "Deneen Pottery"],
  "topProductTypes": ["Apparel", "Coffee", "Accessories", "Coffee Bundle", "On-The-Go Drinkware"],
  "newestProductPublishedAt": "2026-08-14T08:40:37-04:00",
  "oldestProductPublishedAt": "2014-03-12T13:35:00-04:00",
  "appsDetected": ["Klaviyo", "Rebuy", "Gorgias", "Postscript", "Smile.io"],
  "pixelsDetected": ["Google Tag Manager"],
  "paymentsDetected": ["Shop Pay", "Afterpay", "Apple Pay"],
  "appCount": 5,
  "socialLinks": {
    "twitter": "https://twitter.com/deathwishcoffee",
    "facebook": "https://www.facebook.com/deathwishcoffee",
    "instagram": "https://www.instagram.com/deathwishcoffee/",
    "youtube": "https://www.youtube.com/channel/UC1uurJMded2rRJioe8yka7g"
  },
  "contactEmail": null,
  "contactEmails": [],
  "organizationAddress": null,
  "error": null,
  "scrapedAt": "2026-08-15T04:10:06.861Z"
}
```

Fields people misread:

- `themeLabel` is the store's own internal theme name (`"Copy of
  deathwish-shopify-20/production"`) — often more revealing than `themeName` itself.
- `priceMin: 5` is a real $5 variant (stickers, samples). Judge the price band with both
  ends, not just the floor.
- `contactEmail: null` on a store with 5 detected apps is normal — this brand publishes no
  email on its homepage. That is the 38.9% fill from the table above, not a bug.

***

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `domains` | string list | 8 live stores | The stores to analyze. With or without `https://`; duplicates dropped before billing; up to 10,000 per run. **Send no domains at all (a bare `{}` from the API or an agent) and the Actor does not fail — it runs a 3-store demo sample (`allbirds.com`, `deathwishcoffee.com`, `colourpop.com`) so you get real rows to inspect. Those 3 rows are billed like any other verdict: $0.015.** |
| `maxProductPages` | integer | `8` | Catalog pages of 250 to walk. 8 = exact counts up to 2,000 products. `0` skips the catalog. |
| `fetchCollections` | boolean | `true` | Count collections (exact up to 1,000). |
| `emitNonShopify` | boolean | `true` | Off = non-Shopify domains skipped, never billed. |
| `maxConcurrency` | integer | `5` | Parallel **domains**. Requests within one store are always sequential — no store ever sees concurrent traffic from this Actor. |
| `proxyConfiguration` | proxy | Apify datacenter | Measured: Shopify serves identical bytes on every rung, so the cheap rung is the default. Blocked statuses retry once through RESIDENTIAL automatically. |

***

### Pricing

**$0.005 per store analyzed — $5 per 1,000.** Charged on the `store-analyzed` event.
No monthly platform fee from this Actor.

| Run | Stores | Cost |
|---|---|---|
| The shipped default | 8 | **$0.04** |
| An agency pitch list | 200 | **$1.00** |
| A lead-scoring batch | 1,000 | **$5.00** |
| A full TAM sweep | 10,000 | **$50.00** |

- **You are charged per verdict, not per attempt.** Blocked and unreachable domains are
  pushed as rows (so you can see what happened, with the HTTP status in `error`) **without
  the charge event** — in the measured 45-domain run, 6 of 45 domains were not billed.
- **Duplicate spellings are dropped before billing.**
- **Rows are charged as they are pushed** (`Actor.pushData(items, 'store-analyzed')`), so a
  budget cap gets you whole rows and stops, never a half-billed dataset.
- Depth settings (`maxProductPages`, `fetchCollections`) change run time, not price.
- **A run that hits its time limit stops cleanly instead of being killed.** The Actor keeps a
  wall-clock budget from the platform's own run timeout, stops taking new domains before it,
  delivers everything already collected, and ends *Succeeded* with a status message naming how
  many domains were never started. Those un-started domains are **not billed** — re-run them
  with a longer run timeout or in smaller batches. Measured 2026-08-19: 301 domains at
  `maxProductPages: 40` against a 120 s wall → finished in 74 s, 3/3 runs, 57 verdict rows
  delivered and billed, 0 rows lost, never TIMED-OUT.

***

### How it works, and what was measured

Per domain, 3–5 requests at the defaults: the homepage (Shopify globals, JSON-LD, app/pixel
signatures, socials, mailto links), then `/products.json?limit=250` paged until a short
page (an **exact** count) or the cap, then `/collections.json`. The catalog probe runs even
when the homepage carries no Shopify markers — that is how headless-frontend stores with an
open catalog API (fashionnova) are caught instead of misfiled as "not Shopify."

**Transport ladder, measured 2026-08-15 with got-scraping:**

| Rung | Result |
|---|---|
| Direct (reported and then ignored — home-IP recon lies) | 200, identical bytes |
| Apify proxy, datacenter | 200, identical bytes |
| Apify RESIDENTIAL | 200, identical bytes |
| Apify RESIDENTIAL + pinned session | 200, identical bytes |

Shopify's edge does not discriminate between rungs, so the **cheap datacenter rung is the
default** and you are not billed residential bandwidth you do not need. Sustained load
through the Apify datacenter proxy: **20 calls (10 domains × homepage + catalog) at
concurrency 4 = 18/20 = 90.0% in 26.5 s — and both misses were bombas.com**, which serves
HTTP 429 on every rung *including residential and a home IP* (a store-side bot wall).
Excluding that one walled store: **18/18 = 100%**. Store-side walls exist on roughly 1 in
10 big-brand domains (4 of 45 measured: bombas, meundies, huckberry, mizzenandmain) — those
rows come back unbilled with the status in `error`.

**Duplicates, measured in both directions:** contiguous `/products.json` pages returned
**0 duplicate product ids** across two full catalog walks (291 and 146 products), so counts
never double-count; and the run itself asserts one row per unique domain before it is
allowed to succeed.

**robots.txt:** Shopify's generated robots.txt opens with *"Public product, collection,
page, blog, policy, cart, and localized HTML is crawlable"* (fetched live 2026-08-15). Its
`Disallow` list (`/admin`, `/cart/`, `/checkout`, `/orders`, `/account`, `/cart.js`, sort/
filter parameter URLs…) never mentions `/products.json` or `/collections.json` — and this
Actor touches none of the disallowed paths. It reads currency from the homepage globals
specifically because `/cart.js` **is** disallowed. Per-store traffic is sequential — one
request at a time per domain, concurrency only across different domains.

***

### Honest limits

- **No revenue or traffic estimates.** Rival analyzers headline "revenue estimates" —
  numbers no public endpoint provides, so they are models, not measurements. Everything in
  this row is read from the store itself; nothing is modeled. If a field cannot be read, it
  is `null`, not guessed.
- **`contactEmail` fill is 38.9%** and comes from the homepage only. This is not an email
  finder.
- **App detection is homepage-signature-based.** Apps that load only on product/checkout
  pages (some upsell and shipping apps), server-side apps, and apps with no front-end
  footprint are invisible. `appsDetected` is a floor, not a census — 70 signatures curated
  for the highest-signal revenue apps (reviews, subscriptions, SMS/email, support, BNPL).
- **Headless stores yield thin rows** (see gotcha 3) — the verdict and detection tier are
  the deliverable there.
- **~1 in 10 big-brand domains runs its own bot wall** and comes back `blocked`, unbilled.
  This Actor does not fight DataDome/Kasada-class walls for a $0.005 row.
- **Password-protected (pre-launch) stores** return `passwordProtected: true` with the
  verdict — useful as a "store about to launch" signal, but catalog fields are null.
- **One row per domain, ever.** Multi-region stores on separate domains (gymshark.com vs
  gymshark.eu? paste both) are separate rows; this Actor never expands your list.

***

### Who buys this

- **Agencies pitching Shopify services** — qualify a pitch list: platform, theme (stock vs
  custom), catalog size, price band and app gaps, in one CSV.
- **Shopify app vendors** — score leads by tech stack: who runs a competitor's app, who
  runs no reviews/subscription/SMS app at all (`appsDetected` as a gap map).
- **BNPL / payments / logistics vendors** — `paymentsDetected` shows exactly which of
  Shop Pay / Afterpay / Klarna / Sezzle / Affirm a store already offers.
- **Competitive & market researchers** — catalog size, price range, publish-date activity
  and ad-pixel mix across a whole vertical.
- **Theme and CRO agencies** — `themeName`/`themeVersion`/`themeIsCustom` find stores on
  aging stock themes.

***

### FAQ

**Does this find Shopify stores for me?**
No — you supply the domains; it analyzes them. Store discovery is a different job (our
[Shopify App Store Leads Scraper](https://apify.com/scrapersdelight/shopify-app-leads-scraper)
covers the app-publisher side).

**Do I need a login, API key or residential proxies?**
No. Everything is public storefront data, and the measured ladder shows the cheap
datacenter rung works everywhere Shopify itself answers.

**Will every row have an email?**
No — 38.9% measured. It is whatever the homepage publishes. Read gotcha 1 before buying.

**Is the product count exact?**
`productCountIsExact` tells you per row. At the default depth, 28 of 33 counted stores
were exact; raise `maxProductPages` to 40 for exact counts up to 10,000.

**What happens on a non-Shopify domain?**
A billed `isShopify: false` verdict row (that answer is usually the point), or set
`emitNonShopify: false` to skip them unbilled.

**What happens on a blocked or dead domain?**
An unbilled row with `status: "blocked"` or `"unreachable"` and the HTTP status in
`error`. You never pay for a non-answer.

**Will a run ever succeed with zero rows?**
No. A run that produces zero billable verdicts throws, and the error message breaks down
exactly what happened (blocked / unreachable / filtered) so you can tell a list problem
from a site problem.

***

### Legal & fair use

This Actor reads public storefront pages and the public catalog endpoints each store
serves to every visitor. It does not log in, does not touch checkout, cart, admin or any
robots.txt-disallowed path, and collects nothing behind authentication. Store rows can
contain business contact data. **You are responsible for complying with each store's
terms, Shopify's terms, and with how you use the data.** Shopify® is a trademark of
Shopify Inc.; this Actor is not affiliated with, endorsed by, or connected to Shopify Inc.
or any store analyzed.

***

### Feedback

Found a store that misdetects, or want a signature added to the app list? Open an issue on
the **Issues** tab — signature additions ship fast. If the Actor earns it, a review on the
**Reviews** tab helps other buyers find it.

# Actor input Schema

## `domains` (type: `array`):

Domains or URLs, one per entry: allbirds.com, https://www.gymshark.com/ — both forms work, duplicates are dropped before billing. Ships prefilled with 8 live stores so a zero-edit run shows you real rows (~$0.04). Leave it completely empty and the Actor still returns rows: it falls back to a 3-store demo sample (allbirds.com, deathwishcoffee.com, colourpop.com), billed like any other verdict at $0.015. Up to 10,000 domains per run.

## `maxProductPages` (type: `integer`):

How many /products.json pages (250 products each) to walk per store. 8 counts up to 2,000 products EXACTLY — enough for most stores; productCountIsExact tells you per row whether the count is exact or the cap stopped the walk. Set 0 to skip the catalog entirely (no product count, price range, vendors or publish dates). Price per store is the same either way.

## `fetchCollections` (type: `boolean`):

Walk /collections.json (up to 1,000 collections counted exactly). One extra request per store on most stores. Off = collectionCount stays null.

## `emitNonShopify` (type: `boolean`):

On (default): a domain that turns out NOT to be Shopify still produces a row (isShopify: false) and is billed — the verdict is the product when you are qualifying a mixed list. Off: non-Shopify domains are silently skipped and never billed. Domains that could not be answered at all (blocked / unreachable) are pushed unbilled either way.

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

How many DOMAINS are analyzed in parallel. Requests within one store are always sequential, so no single store ever sees concurrent traffic from this Actor — this only spreads the run across different stores. Measured: 20 calls across 10 domains at concurrency 4 through the Apify datacenter proxy = 90.0% (100% excluding one store with its own bot wall).

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

Leave it on Apify Proxy (automatic/datacenter) — measured 2026-08-15: Shopify's edge serves identical bytes on direct, datacenter, RESIDENTIAL and RESIDENTIAL+session, so the cheap rung is the default on purpose. A blocked status is retried once through RESIDENTIAL automatically before the domain is reported (unbilled) as blocked.

## Actor input object example

```json
{
  "domains": [
    "allbirds.com",
    "gymshark.com",
    "colourpop.com",
    "deathwishcoffee.com",
    "stevemadden.com",
    "kyliecosmetics.com",
    "fashionnova.com",
    "ruggable.com"
  ],
  "maxProductPages": 8,
  "fetchCollections": true,
  "emitNonShopify": true,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per domain analyzed: Shopify verdict + detection tier, myshopify handle, shop id, theme name/version/custom flag, product & collection counts, price range, currency/country/locale, detected apps, ad pixels and payment methods, social links, contact email and organization address.

# 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 = {
    "domains": [
        "allbirds.com",
        "gymshark.com",
        "colourpop.com",
        "deathwishcoffee.com",
        "stevemadden.com",
        "kyliecosmetics.com",
        "fashionnova.com",
        "ruggable.com"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/shopify-store-analyzer").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 = {
    "domains": [
        "allbirds.com",
        "gymshark.com",
        "colourpop.com",
        "deathwishcoffee.com",
        "stevemadden.com",
        "kyliecosmetics.com",
        "fashionnova.com",
        "ruggable.com",
    ],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/shopify-store-analyzer").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 '{
  "domains": [
    "allbirds.com",
    "gymshark.com",
    "colourpop.com",
    "deathwishcoffee.com",
    "stevemadden.com",
    "kyliecosmetics.com",
    "fashionnova.com",
    "ruggable.com"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call scrapersdelight/shopify-store-analyzer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/shopify-store-analyzer"
        }
    }
}

```

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/MX6WPVs01LLvSuhhV/builds/xptAqnB4dYaBW2Avk/openapi.json
