# Shopify Store Analyzer: Apps, Theme & Tech Stack (`maxbuildog/shopify-store-analyzer`) Actor

Analyze any Shopify store by domain: installed apps with evidence, free or paid theme, catalog size, prices, pixels and markets. Pay per store analyzed.

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

## Pricing

from $2.10 / 1,000 shopify 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.
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 Analyzer: Apps, Theme & Tech Stack

**Shopify Store Analyzer** checks whether a website runs on Shopify and returns a structured profile of each store, for **$3 per 1,000 Shopify stores analyzed** (sites that are not Shopify, unreachable, closed or blocked are free). It is a **Shopify apps detector** (912 known apps and services in 31 categories, each with the evidence found in the page), a **Shopify theme detector** (theme, version, free or paid, and the Theme Store theme a custom copy is based on) and a **Shopify tech stack** tool: catalog size, prices, product dates, markets, marketing pixels and brand social pages, for agencies, app vendors, lead-generation teams and analysts. Give it a list of domains, or the dataset of another Actor, and it returns one honest row per store.

### What does Shopify Store Analyzer do?

For each domain you give it, the Actor:

1. **Confirms the platform**: Shopify, headless Shopify (Hydrogen and other custom storefronts, also through their `checkout.` domain), or another platform (WooCommerce, BigCommerce, Magento, PrestaShop, Wix, Squarespace, Salesforce Commerce Cloud and more). Shopify is called only on a proof of its own (the `Shopify` object of the page, a Shopify marker, `cdn.shopify.com` images, a Shopify header, or a valid Shopify shop description): file paths that merely look like those of Shopify prove nothing. When it cannot tell, it says `null`, never a guessed `false`.
2. **Reads the public data** of the store: its shop description, its product feed, its home page and one product page. Plain HTTP only: no browser, no login, and no contact details (see "Data and limits").
3. **Detects installed apps and services** (reviews, email and SMS, subscriptions, support, upsell and bundles, loyalty, translation, shipping and returns, buy now pay later, search, page builders, A/B testing, consent, fraud, accessibility, analytics and more). Every app comes with its evidence: a script host, a Shopify app embed, a theme app extension, a script tag of the app (Shopify deletes it when the app is uninstalled, so it proves an installed app even when the app is not in the table), the publisher number of an app's web pixel, or an exact widget marker. Custom web pixels that carry an app's name (for example a Listrak or Elevar pixel) count too. Apps that Shopify loads for the store but that are not in the table yet are still listed with their name (`isKnown: false`) and, when a function word in their handle names what they do ("bundle", "wishlist", "size chart"), with a guessed category; otherwise `other`. The embed and the extension of one app are merged into one line when they can be matched; an unknown app seen under two unrelated names can still be listed twice (3.7 % of the detections in independent evaluation 5 on version 1.1, 0.5 % in evaluation 6 on version 1.2, see "How accurate is it?").
4. **Identifies the theme** and tells whether it is a free or a paid Theme Store theme, by its Theme Store id (a copy renamed "Brand + Dawn" is still Dawn, free), or a custom theme. A custom theme that carries the name of a Theme Store theme (an uploaded or duplicated copy of Dawn, Prestige, Impulse...) says so in `theme.basedOn`, and a free Shopify theme at least one major version behind the Theme Store is flagged (`theme_outdated`: "Dawn 11.0.0, latest 16.1.0"). `theme.majorVersionsBehind` gives the number (1 for Dawn 15.4.0 against 16.1.0, 7 for Dawn 9.0.0), so a spreadsheet can keep the themes that are really behind (2 or more) and leave out those one version late. A retired Theme Store theme (Debut, Supply, Brooklyn and their peers, also an uploaded copy of one of the free ones) is flagged by the signal `theme_retired`, which is what an agency looks for.
5. **Computes catalog facts with their method**: product count (`exact`, `meta`, `sitemap` or `lower_bound`), size band, collections, variants, price statistics, share on sale, share out of stock, newest product creation and publication dates.
6. **Lists marketing pixels and tags**, with their tracking ids in one cell: Meta, Google Analytics 4, Google tag, Google Tag Manager, Google Ads, TikTok, Pinterest, Snapchat, Reddit, X, Microsoft Ads, LinkedIn, Klaviyo, Hotjar, Microsoft Clarity.
7. **Checks the product page data** that Google reads from the HTML of one product page: whether Product markup is present (the one check that can fail), and whether the Product block carries a price and currency, an availability and an image (reported as found, or as information when not found, never as a failure), plus brand and identifiers (information).
8. **Writes one readable sentence** per store, starting with its brand name, and readable signals (active store, outdated theme, large catalog, international, reviews, subscriptions, loyalty, buy now pay later, ad pixels).
9. **Says plainly what happened** to every entry: "The domain does not exist or has no website.", "The site blocks automated visits (bot check).", "The store is closed or unavailable". The technical cause is in a column of its own, and each row says whether it was billed.

### Who is it for?

- **Shopify agencies and freelancers** building prospect lists by theme (free and old, a copy of a Theme Store theme, or paid), app stack, catalog size or markets.
- **Shopify app vendors** measuring the adoption of competing apps, or finding the stores that use a given app, with the evidence for each detection (see "How to find every store with a given app").
- **E-commerce lead generation teams** qualifying a list they already have. The Actor returns no email address or phone number: use it to enrich and rank, not to find contacts.
- **Analysts and operators** doing competitor research on stores they know.

### How to use it

1. Paste store domains or URLs in **Store domains or URLs**, one per line. `allbirds.com`, `www.allbirds.com` and `https://allbirds.com/collections/x` are the same store: it is analyzed and billed once. A line copied from a spreadsheet, an email or a Markdown list is cleaned (quotes, angle brackets, punctuation at the end, a list mark such as `- ` or `1. `, a note in brackets after the domain, a Markdown link around the domain, a label such as `Website: allbirds.com`, a URL encoded as a whole), and a line such as `skullcandy.com, tarte.com` or `skullcandy.com | tarte.com` gives two rows. A line with a name and one domain around a bar (`Allbirds | allbirds.com`) gives that domain; a name alone (`Acme Candles`), a handle and a custom port are refused as invalid entries, free of charge.
2. Or give the **dataset of another run** (`datasetId`): the domains are read from it (see "Use it with other Actors").
3. Optionally list **apps to look for** (`appsToMatch`), raise **Max products to read per store** (default 250) or enable **Include a product sample**.
4. Run the Actor. Open the **Overview**, **Apps**, **Apps to match**, **Marketing tags** or **Signals** view. For a spreadsheet, export a view: each one is a clean table (see "Export to CSV or Excel").

You can also call it from the Apify API, schedule it, or use it from AI agents through the Apify MCP server: every input and output field is described in the schemas.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `storeUrls` | array of strings | empty | Domains or URLs. Scheme, `www`, case, paths and query strings are ignored; entries that are the same store are merged before anything is fetched. Required unless `datasetId` is given. A run reads at most 200,000 entries (this list and the dataset together): more is refused with a message asking to split it. An entry longer than 2,048 characters is not read (`invalid_input`, `errorCode` `entry_too_long`). |
| `datasetId` | string | none | Optional. ID or name of a dataset (the output of another Actor or of an earlier run) to read the domains from, in addition to `storeUrls`. Up to 200,000 items. |
| `domainField` | string | auto | Field of those items that holds the domain or URL (`store.url` for a nested field). When empty, the first text among `domain`, `url`, `website`, `storeUrl`, `shopUrl`, `inputUrl`, `finalUrl`, `homepage`, `link`. |
| `appsToMatch` | array of strings | empty | Optional. App ids or names to look for (`judgeme`, `Klaviyo`). Each row then says which were detected (`matchedApps`, `hasMatchedApp`). No effect on the price. |
| `maxProductsToScan` | integer | 250 | Products read per store for price, discount, stock and date statistics (1 to 5000). The product count does not depend on it when the store exposes it. Above 1000, concurrency is lowered to 3 and 2048 MB of memory is advised. |
| `includeProducts` | boolean | false | Adds a `productsSample` field with the first products of the feed. |
| `productsSampleSize` | integer | 20 | Size of the product sample, 50 at most (each product takes 12 columns in a CSV or Excel export). |
| `maxConcurrency` | integer | 10 | Stores analyzed in parallel. Requests to one store are always rate limited. |
| `proxyConfiguration` | object | none | Optional. No proxy is used by default. |

```json
{
  "storeUrls": ["studioproper.com", "graza.co", "rarebeauty.com"],
  "appsToMatch": ["judgeme"],
  "maxProductsToScan": 250
}
```

### Output

One row per distinct store. Lists are text cells separated by `; ` and nested objects always have the same keys, so the columns of an export do not change from one row to the next. Main fields:

- `inputUrl` (the entry as given), `domain` (normalized, the join key), `domainUnicode` (readable form of an internationalized domain: `xn--bcher-kva.de` is `bücher.de`).
- `status`, `statusDetail` (a plain-English sentence), `errorCode` (the technical cause for scripts: `ENOTFOUND`, `http_403`, `bot_challenge`, `store_deadline_exceeded`, `product_feed_not_public`...), `billed` (`true` when the row was charged), `isShopify`, `dataQuality`, `missingData`: see **Statuses and billing** below.
- `platform`, `myshopifyDomain`, `storeName` (the name set in the shop settings, not always the brand), `brandName` (the brand without its slogan, its legal form such as GmbH or Ltd, or its market suffix such as DE or UK; a `Co.` after `&` or `+` is part of the name and stays), `currency`, `country` (country of the business address of the store that answered, not necessarily the market), `primaryLanguage`, `languages` (`en; fr; de`).
- `markets`: `shipsToCountries` (`US; CA; GB`), `shipsToCountryCount`, `hreflangRegions`, `hasCountrySelector`.
- `theme`: `name`, `version`, `themeStoreId`, `themeStoreName`, `id`, `kind` (`free_theme_store`, `paid_theme_store`, `theme_store`, `custom`), `isFree`, `basedOn` and `basedOnIsFree` (for a `custom` theme whose schema name is that of a Theme Store theme), `majorVersionsBehind` (whole number: how many major versions the theme is behind the latest of the Theme Store, 0 when it is at the latest major version, `null` for a paid, a retired or a custom theme that is not a copy of a Theme Store theme, when no version was read, or when the version read is below 1 or above the latest known; the Overview view has a column for it). When a theme declares no schema name, `name` is the label of the theme in the store.
- `appCount`, `appNames` (`Klaviyo; Judge.me; Recharge`), `appIds` (`klaviyo; judgeme; recharge`), `appCategories` (`email_sms; reviews`), `matchedApps` and `hasMatchedApp` (with `appsToMatch`), and `apps`, the detailed list (`{ id, name, category, isKnown, evidence }`, one line per app in the Apps view; an empty list, not `null`, for a site that is not analyzed, so that an export has no column of its own for it, and `appCount` is `null` then).
- `catalog`: `productCount` with `productCountMethod` and `productCountIsExact`; `collectionCount`; `productsScanned` and `scanIsComplete` (when `false`, prices, shares and dates describe the products read only); `variantCount`, `avgVariantsPerProduct`; `priceStats` (`min`, `median`, `max`); `onSaleShare`, `outOfStockShare`; `lastProductCreatedAt`, `lastProductPublishedAt`, `firstProductCreatedAt` (oldest product, only when every product was read); `productsCreatedLast30Days`, `productsPublishedLast30Days` and the 90-day versions; `sizeBand`: `micro` under 20 products, `small` 20 to 99, `medium` 100 to 999, `large` 1,000 or more.
- `marketing`: one boolean per tag and `trackingIds` (`Meta Pixel: 1234567890; Google Analytics 4: G-ABC123`). `false` means not detected, never proven absent.
- `social`: social pages of the brand (company pages only).
- `merchantCenterReadiness`: structured-data checks on one product page, by id (`checks.availability.status`), with `pass`, `fail`, `info` or `not_checked`, and `notes` in plain English. Only `product_structured_data` can be `fail`: no Product markup was found in the server-rendered HTML of that page. `price_and_currency`, `availability` and `product_image` are `pass` when the Product block read carries them and `info` when it does not: a page often carries a second Product block or microdata that Google reads too, so a missing value in the first block is not proof of a problem. Nothing here says that Merchant Center will reject the feed.
- `signals`: one sentence per readable indicator, keyed by code (`active_store`, `dormant_store`, `theme_outdated`, `theme_retired`, `large_catalog`, `small_catalog`, `empty_catalog`, `heavy_discounting`, `stock_gaps`, `international`, `reviews_app`, `subscriptions`, `loyalty`, `bnpl`, `ad_pixels`, `headless_storefront`, `merchant_center_ok`, `merchant_center_gaps`). `summary`: one factual sentence. `warnings`: caveats such as `store_deadline_reached`.

Abridged example from a real run (21 October 2026):

```json
{
  "inputUrl": "studioproper.com",
  "domain": "studioproper.com",
  "domainUnicode": "studioproper.com",
  "finalUrl": "https://studioproper.com/",
  "status": "ok",
  "statusDetail": null,
  "errorCode": null,
  "billed": true,
  "isShopify": true,
  "platform": "shopify",
  "dataQuality": "complete",
  "missingData": null,
  "myshopifyDomain": "studio-proper-au.myshopify.com",
  "storeName": "STUDIO PROPER",
  "brandName": "STUDIO PROPER",
  "currency": "AUD",
  "country": "AU",
  "markets": { "shipsToCountries": "AD; AE; AG; AI; AL; AM; AR; AT; AU; AW; ...", "shipsToCountryCount": 155, "hreflangRegions": null, "hasCountrySelector": false },
  "theme": {
    "name": "Motto",
    "version": "1.4.1",
    "themeStoreId": 3039,
    "themeStoreName": "Motto",
    "id": "140203753543",
    "kind": "paid_theme_store",
    "isFree": false,
    "basedOn": null,
    "basedOnIsFree": null,
    "majorVersionsBehind": null
  },
  "appCount": 10,
  "appNames": "Digital Appointments; Mailchimp; Notify; Shopify Forms; Judge.me; Easy Redirects; Fomo; GSC Instagram Feed; Shopify Inbox; LogBase Upsell",
  "appIds": "shopify-app:digital-appointments; mailchimp; shopify-app:notify; shopify_forms; judgeme; sc_easy_redirects; fomo; gsc_instagram_feed; shopify_inbox; logbase_upsell",
  "appCategories": "social_proof; booking_scheduling; email_sms; other; popups_conversion; reviews; seo; support; upsell_bundles",
  "matchedApps": "Judge.me",
  "hasMatchedApp": true,
  "apps": [
    { "id": "shopify-app:digital-appointments", "name": "Digital Appointments", "category": "booking_scheduling", "isKnown": false, "evidence": "extension digital-appointments-581" },
    { "id": "mailchimp", "name": "Mailchimp", "category": "email_sms", "isKnown": true, "evidence": "chimpstatic.com" },
    { "id": "judgeme", "name": "Judge.me", "category": "reviews", "isKnown": true, "evidence": "judge.me" }
  ],
  "catalog": {
    "productCount": 150,
    "productCountMethod": "exact",
    "productCountIsExact": true,
    "collectionCount": 128,
    "productsScanned": 150,
    "scanIsComplete": true,
    "priceStats": { "min": 5, "median": 70, "max": 2199 },
    "onSaleShare": 0,
    "outOfStockShare": 0.28,
    "firstProductCreatedAt": "2014-06-17T02:34:19.000Z",
    "lastProductCreatedAt": "2026-09-23T01:19:27.000Z",
    "productsCreatedLast30Days": 1,
    "sizeBand": "medium"
  },
  "marketing": {
    "metaPixel": true,
    "googleAnalytics4": true,
    "tiktokPixel": true,
    "klaviyo": false,
    "trackingIds": "Meta Pixel: 502632411193761; Google Analytics 4: G-D6ZZGML6PX; ..."
  },
  "merchantCenterReadiness": {
    "sampledProductUrl": "https://studioproper.com/products/shoulder-strap",
    "checks": { "product_structured_data": { "status": "pass" }, "availability": { "status": "pass" }, "gtin_or_mpn": { "status": "info" } },
    "notes": "gtin_or_mpn: No GTIN or MPN in the structured data. Merchants often supply them through their feed instead, so this is informational."
  },
  "signals": {
    "active_store": "Active store: 1 new product created in the last 30 days.",
    "international": "Sells internationally: ships to 155 countries.",
    "reviews_app": "Shows product reviews with Judge.me."
  },
  "summary": "STUDIO PROPER (studioproper.com) is a Shopify store with 150 products at a median price of 70 AUD, using the Motto theme (paid) and 10 detected apps including Judge.me, Mailchimp, Shopify Inbox; newest product created on 2026-09-23.",
  "warnings": null
}
```

### Export to CSV or Excel

Each view of the dataset is a clean table, and the best way to use the data in a spreadsheet:

- **Overview**: one line per entry, starting with the entry as given, its status in plain English, `isShopify` and `billed`, then brand, country, theme, catalog and apps count.
- **Apps**: one line per detected app (store, app, category, evidence).
- **Apps to match**: one line per store with the apps of `appsToMatch` that were found.
- **Marketing tags**: one column per pixel or tag, and the tracking ids.
- **Signals**: one column per signal.

The export of all fields also works: about 115 columns outside the apps, whatever the number of rows (113 on a run of 65 stores), plus 5 columns per app of the store with the most apps (`apps/0/name` to `apps/39/evidence`: 313 columns in all on that run of version 1.1, measured on the platform; not measured again since), plus 12 per product when the product sample is on. Apify exports at most 2,000 columns to CSV or Excel, which is why `productsSampleSize` stops at 50: 300 stores with a sample of 50 products give 851 columns. To filter stores by app, use `appNames` or the Apps view, not the `apps/N` columns.

**Spreadsheet formulas.** Every text of a row is written by the store (its name, its theme, its products and vendors) or typed by you, and a spreadsheet reads a text that starts with `=`, `+`, `-` or `@` (or a tab or a carriage return) as a formula when the export is opened. The Actor puts an apostrophe in front of such a text (`'=HYPERLINK(...)`), the usual protection: the cell is a text, and the original characters follow the apostrophe. Numbers, URLs (they start with `http`), texts that are only a number (`-5`) and the entry as you typed it (`inputUrl`, when its domain was read: `- a.com` stays `- a.com`, it is your join key) are left as they are; line breaks and other control characters in a text are replaced by a space; an image address that is not `http` or `https` is dropped.

To export a view from the API: `https://api.apify.com/v2/datasets/DATASET_ID/items?view=overview&format=csv` (or `view=apps`, `view=matched`, `view=marketing`, `view=signals`; `format=xlsx` for Excel).

### How to find every store with a given app

Three ways, from the quickest:

1. **Before the run**: list the apps in `appsToMatch` (`["judgeme"]`, or the name `"Judge.me"`). Every row gets `matchedApps` and `hasMatchedApp`; open the **Apps to match** view and keep the lines where "Has a matched app" is true. An id or a name matches only a whole app (`klaviyo` does not match Klaviyo Reviews). It does not change the price.
2. **After the run, in a spreadsheet**: export all fields, or the Apps to match view (which carries `appNames` too), and filter the `appNames` column on "contains Judge.me" (or `appIds` on `judgeme`). One column, one filter.
3. **For the evidence**: export the **Apps** view (`view=apps`): one line per store and app, with the category and the trace found in the page; filter the App column.

App ids are stable from one run to the next (`klaviyo`, `judgeme`, `recharge`); an app that is not in our table is listed as `shopify-app:<handle>` under a name taken from its handle. The list of covered apps is at the end of this page. The Actor has no discovery mode: it analyzes the domains you give it, so bring the list (see the next section).

### Use it with other Actors

- **Upstream, a list of stores**: run any Actor that finds stores or websites (a store directory, a search results scraper, a list of your own), then give its dataset to this Actor with `datasetId`. The domain is read from `domainField`, or found among the usual fields (`domain`, `url`, `website`, `storeUrl`, `shopUrl`, `inputUrl`, `finalUrl`, `homepage`, `link`). Duplicates are merged, non-Shopify sites are free. A dataset id or name that does not exist is reported as "not found" (when `storeUrls` is empty the run stops with that sentence), and a dataset without any domain in the field says so; the Actor checks that the dataset exists before reading it, so an unknown name never creates an empty dataset in your account. Give the dataset ID shown in the Storage tab.
- **Downstream**: the `domain` column is the join key. Keep the rows with `status` `ok` (or `billed` true) and pass their domains to the next step of your pipeline, for example an Actor that finds the public contact page of a business. This Actor returns no personal data itself.
- **From your code**: call it through the Apify API and get the Overview as CSV in one request (the Actor ID is shown in the API tab of this page):

```bash
curl -X POST "https://api.apify.com/v2/acts/ACTOR_ID/run-sync-get-dataset-items?token=YOUR_API_TOKEN&view=overview&format=csv" \
  -H "Content-Type: application/json" \
  -d '{"datasetId": "ID_OF_THE_DATASET_OF_YOUR_OTHER_RUN", "domainField": "website", "appsToMatch": ["judgeme"]}'
```

This synchronous call suits a short list (it waits up to 5 minutes); for a long list, start a run (`/runs` instead of `/run-sync-get-dataset-items`) and read its dataset when it finishes. The same input works in a schedule, in an Apify integration (run this Actor when the other one finishes) or from the Apify MCP server.

### Statuses and billing

| `status` | Meaning | `isShopify` | Billed |
|---|---|---|---|
| `ok` | Shopify confirmed, home page and catalog read | `true` | yes |
| `partial` | Shopify confirmed, but the home page or the catalog could not be read completely after retries or within the time limit, or the shop description announces products and the product feed gave none (`product_feed` in `missingData`: no price, no date), or the shop description could not be read and the feed lists no product (`errorCode` `shop_description_unavailable`), or the feed is not a plausible Shopify feed (more than 250 products in a page, or items that are not products: `errorCode` `invalid_feed`) | `true` | no |
| `no_public_catalog` | Shopify confirmed, but the store exposes neither its shop description nor its product feed (typical of a headless storefront), or a headless storefront whose shop description and feed list no product (it sells through channels they do not list); the visible apps and tags are still returned | `true` | no |
| `empty_catalog` | Shopify confirmed and readable on a Liquid theme, but the store publishes no product (0 in its shop description, or no public shop description and an empty product feed); the theme, apps and tags are still returned. A row with no product is never `ok` | `true` | no |
| `store_unavailable` | A Shopify store that is closed or frozen: Shopify answers HTTP 423, or HTTP 404, with its "This store is unavailable" page, or HTTP 402 with a "store unavailable" page in the language of the shop. Running it again changes nothing | `true` (`null` without a Shopify answer) | no |
| `not_shopify` | Site read, no Shopify evidence | `false` | no |
| `headless_suspected` | A `myshopify.com` domain appears in the page but no public Shopify endpoint confirms it | `null` | no |
| `undetermined` | JavaScript storefront with e-commerce features and no identifiable platform: a headless Shopify store cannot be excluded | `null` | no |
| `password_protected` | Shopify store behind the password page | `true` | no |
| `blocked` | Bot wall or rate limit that could not be passed | `null` | no |
| `unreachable` | DNS error, timeout, server error, missing page, or no complete answer within the time limit; or a page so built that reading it exceeds the time limit of the analysis (`errorCode` `page_analysis_timeout`) | `null` | no |
| `invalid_input` | Not a usable public domain (empty, a few words, an IP address, a port, a part of the name longer than 63 characters, an entry longer than 2,048 characters) | `null` | no |
| `duplicate` | Another entry of the run redirected to the same store | `true` | no |

`statusDetail` explains the status in one sentence ("The domain does not exist or has no website (its name does not resolve).", "The site blocks automated visits (bot check).", "The product feed could not be read in time: the shop description announces 236 products, but no price or date could be read. Run it again later."). `errorCode` holds the technical cause for scripts. Only sentences that end with "Run it again later" are worth a second run.

Billing rules, in one place:

- One event only, **`store-analyzed`**, charged once per store with `status: ok`. The row is written first and its event is charged right after: a failure to charge never costs you a row. Each row says it in `billed`; the run summary (`RUN_SUMMARY`) gives `billedRows`, `chargedEvents` and, if the platform ever refused a charge after a row was written, `chargeFailures`.
- A Shopify store that publishes **no product** is returned as `empty_catalog` and is **not billed**: there is no catalog to analyze. A headless storefront whose shop description and feed list no product is returned as `no_public_catalog` (it may sell through channels they do not list) and is not billed either. A closed store (`store_unavailable`) is not billed.
- A row is billed only when the **essentials are present**: the home page (apps, theme, tags), the catalog (shop description or product feed) and at least one product of its feed (without it there is no price and no date): a store with no product read is never `ok`, never billed. If a response is cut by the network, the request is retried, and a feed page that does not come in time is asked again in smaller pages; if the data is still missing the row is `partial` and free. Missing secondary data (the sampled product page, a later page of a large feed) keeps the row `ok` and billed, with `dataQuality: "partial"` and the missing part in `missingData`.
- Each store has a time limit (90 seconds at the default settings). A store that does not answer in time gets one row saying so, never two rows.
- The Actor respects your maximum charge per run and stops cleanly when it is reached: the stores that were not analyzed by then have **no row** (the run summary counts them in `notAnalyzedBySpendingLimit`), and a row is never written for an event the platform would not charge.
- **A run that is migrated or restarted resumes.** The platform can move a run to another server or restart it. What was delivered is saved in the key-value store (`RUN_STATE`, every few rows and on the platform's own saving events) and read back from the dataset: a store that already has its row is neither analyzed, written nor charged a second time, and `RUN_SUMMARY` covers the whole run (`resumes` counts the restarts).

### How much does it cost?

**$3 per 1,000 stores analyzed** ($0.003 per store with `status: ok`). Sites that are not Shopify, unreachable, closed, blocked, partial, without a public catalog, with an empty catalog, duplicate or invalid are never charged, and there is no start fee. For example, a list of 1,000 domains of which 700 are Shopify stores costs about $2.10. Volume discounts, if any, are shown in the Pricing tab.

Measured on the Apify platform at the default 1,024 MB (second validation run, 65 entries): 149 seconds, about **2.3 seconds per entry**, so roughly **40 minutes for 1,000 entries**. The minimum memory is 1,024 MB: with less, Apify gives an Actor only a fraction of a CPU core, and the run is slower and costs more.

### How accurate is it?

Measured in October 2026 by independent evaluators on stores that no rule of the Actor had been tuned on, against an independent read of every store and, for the apps, a hand-made inventory written down before the first run.

**Apps, measured by two independent evaluations on 20 new stores each** (21 and 22 October 2026): each took 20 new Shopify stores among the most visited websites (Tranco top 1M) that the project had never seen, 10 US, 4 UK, 3 FR and 3 DE or NL of all sizes, wrote a hand inventory of their apps before the first run, then compared the Actor with it. The two evaluations measured two versions (1.1 and 1.2): the "both together" column mixes them, 40 stores in all.

| Measure (apps) | Evaluation 5, version 1.1 (20 stores) | Evaluation 6, version 1.2 (20 other stores) | Both together (40 stores) |
|---|---|---|---|
| Recall | **83.8 %** (186 of 222); **86.1 %** without apps proven only by a web pixel number or a link | **91.8 %** (190 of 207); **93.8 %** without apps proven only by a web pixel number or a link | **87.6 %** (376 of 429) |
| Recall by country | United States 81.5 %, United Kingdom 89.7 %, France 85.7 %, Germany and the Netherlands 89.5 % | United States 92.4 %, United Kingdom 88.6 %, France 88.5 %, Germany and the Netherlands 100 % (14 apps) | |
| Precision | **94.9 %** (203 of 214 detections) | **99.1 %** (212 of 214 detections) | **97.0 %** (415 of 428) |
| Duplicates (one app listed twice) | 3.7 % (8 of 214) | 0.5 % (1 of 214) | 2.1 % (9 of 428) |
| Left in `other` | 13.1 % on the 20 stores; **12.7 %** on 317 other new stores, mostly European, where 23.1 % of the detections are apps outside the table | 7.5 % on the 20 stores; **11.8 %** on 327 other new stores, half in the United States, where 20.7 % of the detections are apps outside the table | |
| Category right when the app is in the table | 97.5 % (159 of 163) | 99.4 % (176 of 177) | 98.5 % (335 of 340) |
| Name a buyer recognizes | 97.8 % (182 of 186) | 98.4 % (187 of 190) | 98.1 % (369 of 376) |
| Script tags and app pixel numbers (new in 1.1) | 82 of 86 hand-checked detections right (95.3 %); the 4 errors are duplicates, none is an app that is not there | 70 of 70 hand-checked detections are apps really installed, none listed twice (apps added to the table in 1.2 and script tags, random sample on the 327 stores) | |
| Stability | two runs identical | two runs identical | |

Read the recall as a range, not as one number: the same version 1.1 found 83.8 % of the apps on the stores of evaluation 5 and 91.3 % on those of evaluation 6 (same day, same stores as the 1.2 column, with the same precision), so the stores change the result by several points, more than an iteration does. On the 20 stores of evaluation 6, version 1.2 found 91.8 %: it mostly corrects duplicates, names, categories and closed stores, and adds long-tail apps. What is still missed is that long tail: small apps whose host, file or pixel number is not in the table yet, and apps known only by inline code. An app that leaves no trace in the HTML cannot be seen at all. Popular apps are seen almost without a miss: for 12 widespread apps (Judge.me, Klaviyo, Loox, Yotpo, Rebuy, Gorgias, Recharge, Stamped, Attentive, Okendo, Privy, Afterpay) no store was missed on 299 stores (evaluation 3) nor on the 20 stores of evaluation 6.

**Versions 1.3 and 1.4.** Version 1.3 corrects what evaluation 6 found: list marks in the entries (`- a.com`, `1. a.com`), a theme settings text and a sample address read as apps, a loader named under its former name, retired themes (signal `theme_retired`), atypical theme versions, and 28 more apps in the table. These corrections were made on the stores that evaluation measured, so **no accuracy figure is claimed for version 1.3**: the figures above are those of versions 1.1 and 1.2, measured on stores that no rule had been tuned on. Version 1.4, the current one, closes the findings of a security audit (hostile pages and feeds, billing edge cases, restarts, spreadsheet formulas, contact details in texts) and changes no detection rule: **no accuracy figure is claimed for version 1.4** either.

- **Shopify detection** (evaluation 3, section 3): on 70 new Shopify stores and 16 other new sites, none of the 16 non-Shopify sites was called Shopify and no Shopify store was called "not Shopify": 68 were recognized and the 2 headless fronts that expose nothing were reported as `null`. The platform of the others was named right 11 times out of 11. A later check on 200 other new non-Shopify sites found 8 that were wrongly called Shopify: a platform imitating the file paths of Shopify's storefront CDN, with no other trace of Shopify. This version no longer concludes on those paths and asks for a proof of Shopify's own: 0 of those 8 known cases are called Shopify after the correction. In evaluation 4, 50 Shopify stores out of 50 were recognized in a lead list, and none of the 50 other entries was called Shopify. In evaluation 5, none of 30 other merchant domains that are not Shopify was called Shopify. A store that cannot be decided is reported as `null`, not `false`.
- **Product count and prices** (evaluation 3, section 4): 45 of 45 stores within 2 % of an independent read of the store's own shop description (2 more, above 25,000 products, are reported as a lower bound). On the 21 stores read completely, the newest creation and publication dates, the oldest creation date, the share out of stock and the minimum, median and maximum prices equal an independent full read (21 of 21 each).
- **Theme** (evaluation 3, section 4): name, version and Theme Store id equal an independent read on 38 of 39 stores (the one gap was a version cut at 20 characters, fixed since); free or paid is right for all 13 Theme Store themes of the sample, renamed copies included. In evaluation 4, 60 % of the themes had no Theme Store id (`custom`) and 59 % of those carried the name of a Theme Store theme: they now say so in `basedOn`. In evaluation 5, `basedOn` was plausible for 29 of 30 custom themes read in their home page, the latest versions of 11 free themes equalled their public Theme Store pages (read on 21 October), and 12 `theme_outdated` signals checked were right in version and in gap (`theme.majorVersionsBehind` was added afterwards: in evaluation 6, on 327 new stores, 35 of the 49 `theme_outdated` signals were one version late, and the number agreed with the signal on every one of the 327 rows).
- **Marketing tags** (evaluation 3, section 5): 96.8 % of the flags raised are confirmed by an independent read (239 of 247); the wrong ones all came from a host quoted in a blocklist or a preload hint, which this version no longer counts.
- **Product page checks for Google Merchant Center**: on 2,459 other new stores, 168 of the 168 `product_structured_data` failures judged were right. The failures of availability and image were right 36 times out of 54 (66.7 %), and those of the price 5 times out of 8, because a page often carries a second Product block or microdata: those three checks are therefore reported as `info` and never as a failure.
- **Billing**: in the 10 runs of evaluation 4 (1,230 entries), billed events equalled the `ok` rows exactly; nothing was billed for non-Shopify, unreachable, blocked, empty, password-protected, duplicate or invalid entries. In the 10 runs of evaluation 5 (492 rows, version 1.1) the 423 `ok` rows, the 423 rows with `billed: true` and the 423 charged events were the same, and so they were in the 9 local runs of evaluation 6 (764 rows, version 1.2: 736 of each) and in its run on the platform at the real price (49 of each). The 14 closed stores of evaluation 6 (HTTP 402, 404 and 423) were all `store_unavailable` and none was billed.

The fixes made after each evaluation were tuned on stores that had already been measured, so no accuracy figure is claimed for them: the figures above are those of the evaluations, for the version they measured.

### Data and limits

- Only public business data. **No email address and no phone number** is returned: a text that holds one loses it before its row is written. A store is often named after its owner, so the name of a store can be the name of a person: the Actor does not promise that no personal name appears in a store or vendor name. Social links are the public pages that the store links to from its home page (a personal LinkedIn profile is never kept; an Instagram or TikTok account cannot be told apart from a brand page, so it is kept when the store links to it).
- **Hostile pages.** Pages are written by third parties, some on purpose to slow a reader down. The HTML of a store is read in a separate thread with a time limit (10 seconds for each reading), a page that cannot be read within it gives an `unreachable` row (`page_analysis_timeout`, never billed), and one store never holds up the others. A feed page is read product by product, so a hostile feed cannot fill the memory of the run: a page with more than 250 products, or with items that are not products, is refused (`invalid_feed`), and a product with an absurd number of variants (over 20,000 nested items) is skipped. A page is read up to 6 MB and a feed page up to 12 MB.
- **Network safety.** The Actor only visits public addresses on the ports 80 and 443: an address that resolves to a private or reserved network (checked again by the connection itself, for HTTP/1.1 and HTTP/2), a redirect or a sitemap address that points to another port or another host is refused. With a proxy, the proxy resolves the names; the literal internal addresses and local names are still refused here.
- Detection reads the public HTML of two pages. Apps loaded only after consent, inside checkout, or server side cannot be seen, and an app that leaves no trace is not listed: not detected does not mean absent. This is why the Actor gives no negative signal such as "no reviews app" or "no ad pixel": review widgets and tags are often injected after the page loads.
- Price statistics describe the products read. On a catalog larger than `maxProductsToScan`, `catalog.scanIsComplete` is `false` and the summary gives the median "among the N products read".
- `country` is the business address of the Shopify store that answered: a brand with one store per region exposes the address of that store.
- `theme.basedOn` is read from the schema name of the theme, which copies keep; `kind` stays `custom` because only the Theme Store id proves the product. The latest versions of the free Shopify themes come from their public Theme Store pages, read on 21 October 2026 (Dawn 16.1.0, Horizon 4.3.0).
- Headless storefronts that expose no public Shopify endpoint, neither on their own domain nor on `checkout.<domain>`, cannot be analyzed (`headless_suspected`, `undetermined` or `no_public_catalog`, never billed).
- A Liquid store with no published product is reported as `empty_catalog` and is not billed; a headless storefront that lists none is `no_public_catalog`, also not billed.
- The Actor has no discovery mode: it analyzes the domains you give it, or those of a dataset.

### FAQ

**Is it legal to analyze Shopify stores this way?** The Actor reads publicly available pages and endpoints that any visitor can open, at a low request rate (about one page per second per store), and returns business information only. You remain responsible for how you use the data.

**How many stores can I analyze, and how long does it take?** Lists of thousands of domains work. Count roughly 40 minutes per 1,000 entries at the default settings (measured on the Apify platform: 2.3 seconds per entry), and $3 per 1,000 Shopify stores analyzed.

**Why was my store `blocked`?** The store, or its CDN, challenged the request. Try again later or enable a proxy.

**Why is a row `partial` and not billed?** The network cut a response, the store did not answer within the time limit, or its product feed gave no product while the store announces some, and the retries did not complete it. `statusDetail` says which; when it ends with "Run it again later", run those domains again.

**What does `store_unavailable` mean?** The store was closed or frozen on Shopify: Shopify shows a "store unavailable" page (HTTP 423 or 404 with the page "This store is unavailable", HTTP 402 with the same kind of page in the shop's language) instead of the storefront. It is not billed, and running it again will not change it.

**What does `empty_catalog` mean?** The store is on Shopify (a Liquid theme) and answered, but it publishes no product (its shop description and its product feed both say 0). You get its theme, apps and tags, and it is not billed.

**What does `no_public_catalog` mean?** The store is on Shopify, but its storefront (often a headless Hydrogen front) does not expose the shop description or the product feed, or exposes them empty while the store sells through other channels, so the catalog cannot be read. You get the apps and tags that are visible, for free.

**Why is `apps` shorter than the real app list?** Only apps that leave a trace in the public HTML of the home page and one product page are visible, and the table of known apps is not exhaustive (see the measured recall above and the list of covered apps below). Apps that Shopify loads but that are not in the table are listed with `isKnown: false`, a category guessed from their name when possible, and `other` otherwise.

**An app I care about is missing. Can you add it?** Yes: open an issue in the Issues tab with the app name and a store that uses it. The table is updated regularly.

**How is a free theme recognized?** By the Theme Store id that Shopify exposes for the installed theme, compared with a table of the Theme Store themes and their price. A renamed copy keeps its id. A theme without an id is reported as `custom`; when its schema name is that of a Theme Store theme (an uploaded copy of Dawn, for example), `basedOn` names it and `basedOnIsFree` says whether it is free.

**Do I get one row per entry?** You get one row per distinct store. Entries that are the same store (scheme, `www`, case, path) are merged before anything is fetched and counted in the run summary; a second entry that redirects to a store already analyzed gets a free `duplicate` row. A line that holds several domains gives one row per domain.

**How do I analyze thousands of products per store?** Raise `maxProductsToScan` (up to 5000) and set the Actor memory to 2048 MB. Concurrency is lowered automatically for such runs.

### Which apps are covered?

The table behind the detection holds 912 known apps and services. An app that is not in the table is still listed when Shopify loads it for the store (`isKnown: false`). This list is generated from the table:

- **Reviews and ratings** (36): Air Reviews, Ali Reviews, ausgezeichnet.org, Avis Verifies / Skeepers, Bazaarvoice, Delighted, eKomi, Feedback Company, Feefo, Fera, Google Review Plus (Entangle Commerce), Judge.me, Junip, Kiyoh, Klaviyo Reviews, Loox, Okendo, OpenReviews, Opinew, PowerReviews, ProvenExpert, Repocean Google Reviews, Reputon, REVIEWS.io, Ryviu, Shopify Product Reviews (legacy), Shopper Approved, ShopVote, Société des Avis Garantis, Stamped.io, Trusted Shops, Trustoo, Trustpilot, TrustReviews, WebwinkelKeur, Yotpo
- **Email, SMS and marketing automation** (63): Achille, ActiveCampaign, AddShoppers, Aimtell, Attentive, Automizely, Bluecore, Braze, Brevo (Sendinblue), CartHike, CleverPush, CleverReach, CleverTap, ContactPigeon, Cordial, Customer.io, Customers.ai, Dotdigital, Drip, Electric SMS, Emarsys, Emotive, Endear, Flodesk, Hello Charles, Hive, HubSpot, Instant (instant.one), Iterable, Kit (ConvertKit), Klaviyo, Listrak, Mailchimp, MailerLite, MoEngage, Movable Ink, Ometria, Omnisend, OneSignal, OneText, Opensend, Postie, PostPilot, Postscript, Privy, Probance, PushAlert, PushOwl, Recart, Retention.com, Retextion, Sailthru, Seguno, Sendlane, Shopify Email, SMSBump (Yotpo SMS), Symplify, TxtCart, WAX (WhatsApp marketing), WebEngage, Wunderkind, Zappush, Zoko WhatsApp CRM
- **Subscriptions** (13): Appstle Subscriptions, Awtomic, Bold Subscriptions, Fragile, Loop Subscriptions, Ordergroove, PayWhirl, Recharge, Seal Subscriptions, Skio, Smartrr, Stay AI, Subscribfy
- **Customer support and chat** (50): Ada, Chatra, Chatty (Avada), Crisp, Dialog AI, DigitalGenius, Dixa, DocsBot, Drift, Enorm FAQ Page & Product FAQs (EnormApps), Envive (Spiffy), Fab Feedback Button, Freshworks (Freshchat, Freshdesk), GetButton, Gladly, Gorgias, Hark, Help Scout, Heyday, Homie AI, iAdvize, Intercom, Kustomer, LiveChat, LivePerson, ManyChat, Olark, QuickCEP Live Chat, Re:amaze, Remark, Resolvia, Richpanel, Salesforce Chat, Shopify Inbox, Smartsupp, Superchat, Tawk.to, Tidio, Tolk.ai, Trengo, Userlike, Wilford AI, Wizybot AI CRM & Chatbot, Ybug, Yuma AI, Zendesk, Zipchat AI, Zoho Desk, Zoho SalesIQ, Zowie
- **Upsell, cross-sell and bundles** (69): AfterSell, Also Bought, AMP Upsell (Upsell by AMP), AOV.ai Free Gifts (Avada), Aris Bundles, Biscuits Bundles, BOGOS (Buy X Get Y), Bold Discounts, Bold Upsell, Bundle Products - EA (EnormApps), Bundler, Bundles (Dynamic Revenue), Candy Rack, Card Isle (Print-on-Demand Greeting Cards), CartBot, Cartful, CartHook, CartMax Checkout Upsell, Conversion Bear, CornerCart, Cross Sell, DealEasy, Discount Hunt, Discount Kit, Discount On Cart Pro, Discounty, Easify Box Bundle Builder, EG Auto Add to Cart, EliteCart, ESC Cart Convert, Essential Cart Drawer, Essential Upsell, Fast Bundle, Frequently Bought Together, Gift Box, GiftKart, Gojiberry, Hoppy Free Shipping Bar, iCart, Kaching Bundles, Kaching Cart Drawer, LogBase Upsell, Monk Free Gift, Monster Upsells, MultiVariants, Order Value Booster (Essential Apps), OrderEditing.com, Orichi Quantity Breaks, Pear Commerce, Pickystory, Post Purchase Upsell Hero, Quantity Breaks Now, Rebuy, ReConvert, Regios Discounts, Revy Unlimited Bundles, Rokt, Salepify Free Gift (Tek Labs), Selleasy, Shopify Bundles, Simple Bundles, Slide Cart, Superchargify, UpCart, VidDay, Vitals, WideBundle, Wrapped, Zipify One Click Upsell
- **Loyalty, rewards and referrals** (28): Annex Cloud, Antavo, BLOY Loyalty Rewards, BON Loyalty, Devotion, Fondue Cashback, Friendbuy, Gift Up!, GoCertify, GovX ID, Groundswell, Growave, Inveterate, Joy Loyalty, Loloyal Loyalty & Referrals (CWILL), LoyaltyLion, Loyoly, Marsello, ReferralCandy, Rise.ai, Rivo, SC Conjured Referral Program, Smile.io, Student Beans, VerifyPass, Voucherify, Yotpo Loyalty, Zinrelo
- **Translation, currency and cross-border** (22): ConveyThis, Cozy Country Redirect, Currency Converter Plus, ESW (eShopWorld), eTranslate, Geo Pro Geolocation, GeoIP Country Redirect, Geolizr, Global-e, Glopal Translate, GTranslate, Langify, Langshop, Localize, Orbe Geolocation, Selecty, Shopify Geolocation, T Lab AI Language Translate, Transcy, Translate and Adapt (Shopify), Weglot, Zonos
- **Shipping, tracking and returns** (57): 17TRACK, 8returns, AfterShip, AusPost Collect, Baback, Bigblue, Boxtal, Colissimo, Corso, CWILL Order Tracking (formerly ParcelPanel), EasyPost, Easyship, Estimated Delivery Date, Extend, Fenix Commerce, Happy Colissimo, Happy Returns, InPost, Karla, Loop Returns, Mondial Relay, Narvar, Navidium, Onward, Order Deadline, Orderify, Outvio, Paazl, Packeta, Packlink, parcelLab, PDQ Shipping, Redo, Refundid, Returnflows, ReturnGO, Returnly, Revers.io, Reversia, Route, SavedBy, Seel, Sendcloud, ShipBob, Shiphero, ShipInsure, Shippo, ShipProtect, Shiprocket, ShipStation, Shipup, Shipway, Swap, TrackingMore, Vify Order Printer PDF Invoice, what3words, Wonderment
- **Buy now, pay later** (22): Affirm, Afterpay / Clearpay, Alma, Bread Pay, humm, Katapult, Klarna, Kueski Pay, Laybuy, Mondu, Oney, Openpay, Partial.ly, PayTomorrow Messaging, Pledg, Scalapay, seQura, Sezzle, Splitit, Tabby, Tamara, Zip (Quadpay)
- **Payments and checkout** (18): Adyen, Amazon Pay, Bolt, Buy with Prime, Checkout.com, Gift Card Hero, Gift Card Pro (Gist), Givex Gift Cards, HidePay, Just, Magical Surcharge + Extra Fees, Mollie, Oceanpayment, PayPal, Payplug, Stripe, Truemed, Vify Gift Cards
- **Search and product discovery** (27): Algolia, Bloomreach, Boost AI Search and Filter, Constructor, Depict, Doofinder, Essential Search Optimization, Findify, Findter Filter & Search, GrapheneHC, Instant Search+ (Fast Simon), Jedi Collection Sort (Jedi Apps), Kimonix, Klevu, Marqo, Product Filters & Search, Searchanise, Searchspring, Shopify Search and Discovery, Shoply AI Search & AI Chatbot, Sortd Merchandiser, Sortwise (Mezereon), Swiftype (Elastic), Tagalys, Unbxd, ViSenze, Yext
- **Personalization and recommendations** (27): Black Crow AI, BlueConic, Clerk.io, Crossing Minds, Dynamic Yield, Fibr, Findation, Insider, Jurni, LimeSpot, Monetate, Monocle, Nosto, Octane AI, Optimove, Particular Audience, Perfect Corp (virtual try-on), Personizely, Potions, QuizKit, RevenueHunt, Salesfire, Salesforce Interaction Studio, Stylitics, Stylux, Tangiblee, Yieldify
- **Page builders** (15): Beae, Design Packs: Theme Sections, EComposer, FERMÀT, GemPages, Instant, LayoutHub, PageFly, Qikify Smart Menu, Replo, Shogun, Widgetic, XO Builder, XO Gallery, Zipify Pages
- **A/B testing and CRO** (24): AB Genius, AB Tasty, ABConvert, ABlyft, Convert Experiences, Elevate A/B Testing, Essential A/B Testing, Google Optimize, GrowthBook, Intelligems, Kameleoon, LaunchDarkly, Mida, Moonshot AI, Omniconvert, Optimizely, Shoplift, Statsig, Stellar, Unbounce, Varify.io, Visually, VWO, Webyn
- **Popups and conversion widgets** (33): AIVA Labs, Alia, Boost and Sales Announcement Bar, Countdown Timer by Samita, CrazyRocket, Digioh, EcomSend Popups & Email, Embeddables, Essential Countdown Timer, Flair: Product Labels & Badges, Getsitecontrol, GSC Countdown Timer, Hextom (shipping bar, countdown), Justuno, Mailmunch, OnVoard, OptiMonk, Pop Convert, POWR, Sami Product Labels & Badges, SC Sales Motivator (Shop Circle), Shopify Forms, Sticky Add to Cart Booster, Sumo, TA Product Labels & Badges (formerly BSS Product Labels), Trust Hero, TrustedSite (McAfee SECURE), UpSellit, ViralSweep, Wheelio, Wisepops, Wishpond, Zotabox
- **Social proof, UGC and shoppable video** (32): Bambuser, CareCart Sales Pop, Conversion Bear Trust Badges, Covet.pics, Dash Hudson, Elfsight, Firework, Flowbox, Fomo, Foursixty, GSC Instagram Feed, Instafeed, Instafeed+Story, iShorts, Join Stories, Juicer, Nudgify, Olapic (Photorank), Pixlee (Emplifi), PlayShorts, ProveSource, Quinn Shoppable Videos, Reelfy: Shoppable Videos Reels, ReelUp, ShareThis Share Buttons, Shop The Look, Spark Shoppable Videos (Storeprops), Storyly, Taggbox, Tolstoy, Videowise, Vidjet
- **Analytics and attribution** (97): AdBeacon, Addingwell, Adobe Analytics, AdTrace, Adtribute, Ahrefs Web Analytics, Amplitude, Analyzify, Apex by Drip, AppsFlyer, Attribuly, Attributy, Bloom Analytics, Blotout EdgeTag, CANDDi, Chord, Ciblelink, Contentsquare, Converge, Cookieless Data, Crazy Egg, Decify, Dema, Easy Reports & Data Export (DevCloud), Easy Tag (GTM data layer), Elevar, Fairing, Fivetran, Fospha, FullStory, Glassbox, Heap, Heatmap, Hotjar, Hue, Hyros, Ignite (formerly Fueled), Innkeepr, Klar, KnoCommerce, Kochava, LeadsMonitor, Lexer, Littledata, LogRocket, Lucky Orange, Lytics, Mable, Magellan AI, Matomo, MerchCloud, Microsoft Clarity, Mixpanel, Mouseflow, mParticle, Noibu, Northbeam, OuterSignal, PebblePost, Peel Insights, Pendo, Piano, Piwik PRO, Pixee Facebook Pixel, Plausible, Podscribe, Polar Analytics, PostHog, ProfitMetrics, ProfitPeak, Promptwatch, Propel Replays, Quanticfy (Quanticside), Quantum Metric, Refunnel, RudderStack, Segment, Sentry, Singular, SlideRule Analytics, Smartlook, Snowplow, Stape, TAGGRS Server Side Tracking, Tatari, Tealium, TrackAnalyse, Triple Whale, TrueProfit, TVSquared, Unifida, Upstack Data Tracking Pixel, Utmify, Veoxa, wetracked.io, Wicked Reports, Zigpoll
- **Affiliate and influencer** (48): ADCELL, Adtraction, Affilae, Aspire (AspireIQ), AvantLink, Awin, CJ Affiliate, Commission Factory, CreatorIQ, Effiliation, Effinity, ExpertVoice, Fluenceur, GoAffPro, GRIN, Growi, GrowthHero, impact.com, Influencer Hero, Klickly Connector, Kolsquare, Kwanko, Linkster, LTK (rewardStyle), Marques de France, Modash, Narrativ, Partnerize, Pepperjam, Rakuten Advertising, ReferrLy Affiliate Marketing (Affiliatery), Refersion, Selecdoo, ShareASale, Shopify Collabs, ShopMy, Simple Affiliate, Social Snowball, Superfiliate, Tapfiliate, Tradedoubler, TradeTracker, Trybe (UGC attribution), TUNE, Upfluence, UpPromote, vCommission, Webgains
- **Cookie consent and privacy** (36): Alfright, Avada Cookie Bar, Axeptio, CheckMate Terms and Conditions, Clarip, Commanders Act, Complianz Consent, Consentik GDPR Cookie Banner, consentmanager, Consentmo, Cookie Information, Cookie-Script, Cookiebot, CookieFirst, CookieHub, CookieYes, DataGrail, Didomi, EasyGDPR, Enzuzo, iubenda, Ketch, Nova: GDPR Cookie Consent, OneTrust, Osano, Pandectes GDPR, Quantcast Choice, Sirdata, Sourcepoint, tarteaucitron.js, Termly, TinyCookie GDPR Cookies Banner, Transcend, TrueVault Polaris, TrustArc, Usercentrics
- **Wishlist, back in stock and pre-order** (25): Amp Back in Stock, Appikon Back in Stock, Appmate (wishlist, back in stock), Back in Stock, Drop a Hint, EcomSend Back in Stock, Globo Pre-order, iWish, Kbite Back in Stock (CartBite), Notify Me! Back in Stock, Ordersify Product Alerts, Oxi Social Login & Wishlist, Pre-Order Manager, Preorder Wolf, PreProduct, Purple Dot, SetuBridge Coming Soon Products, Smart Wishlist, Stoq (formerly Restock Rocket), Swym Wishlist Plus, Timesact Pre-order, Wishlist Hero, Wishlist Power, XB Wishlist, Zooomy Back in Stock
- **Size and fit** (13): Auglio, Avada Size Chart, ESC Size Charts, Faslet: Size Advisor, Fit Analytics (Snap), Fitle, Fittingbox, Kiwi Sizing, Kleep, Size chart app, Sizekick, True Fit, Wair
- **Product options and forms** (36): Accentuate Custom Fields, Aris Product Options, Best Custom Product Options (YMQ), Bold Product Options, Camouflage: Hide Sold Variants, Customily Product Personalizer, Globo (product options, forms), Hulk Apps, Hulk Form Builder, Improved Contact Form, Infinite Options by ShopPad, Kickflip (MyCustomizer), LinkedOption, Magical Custom Fields & Upload, One Click Social Login, Pareto Order Limits, Powerful Form Builder, Print.App, Printful, Printzware, Product Customizer, Product Essentials, Request a Quote & Hide Price (Omega), StarApps Studio, Swatch King, Tabs (TK Digital), Tabs Studio (Station), Tally, Textbox by Textify, Typeform, Variant Option Product Options (Relentless), Variantify, VIM Variant Image Module, W3Apps, Zakeke Product Customizer, Zepto Product Personalizer
- **SEO** (28): Ablestar Link Manager, Avada SEO, Booster SEO, Crush.pics, DropInBlog, Easy Redirects, flareAI SEO, GPTLab: ChatGPT Claude AEO SEO, GWA Hreflang Manager, Hreflang Tags, Interlinks, JSON-LD for SEO, Noindexly, NotFoundBot, Plug in SEO, Redirect Hero, Redirectify, Schema Plus, SearchPie, SEO Manager, SEOWILL / SEOAnt, SEOWILL Redirects, Sitemap & NoIndex Pro SEO, Smart SEO (Sherpas), Tapita SEO, TinySEO (TinyIMG), Webrex SEO, Yoast SEO
- **Product feeds and ad channels** (18): AdRoll, AdScale AI Ads, Channable, DataFeedWatch, Facebook and Instagram (sales channel), Feedoptimise, GoDataFeed, Google and YouTube (sales channel), MNTN, Multifeeds, Mulwi, Nabu / AdNabu, Omega (product feeds and pixels), Pinterest (sales channel), Product Ads by Criteo, Salsify, Simprosys Google Shopping Feed, TikTok (sales channel)
- **Accessibility** (13): Accessez, accessiBe, Accessibly, All in One Accessibility (Skynet Technologies), AudioEye, Avada Accessibility, Equalweb, eSSENTIAL Accessibility, Eye-Able, Nagich, Patrol, UsableNet, UserWay
- **Fraud prevention and security** (22): Blockify Fraud Filter, Blocky Fraud Blocker, BM Country Blocker, DMCA.com Protection Badge, Forter, Fraud Deflect, FUGU, Kedra Fraud Filter, IP Blocker, Kount, Locksmith, Lunio, MarqVision, MIDA Fraud Filter, Bot Blocker, Negate, NoFraud (Wyllo), PPC Protect, Riskified, Securify, SEON, Sift, Signifyd, Yofi (BotNot)
- **Site speed and image optimization** (9): Auditzy, Edgemesh, FixVitals, Flash Page Speed Optimizer, Pingdom Real User Monitoring, SEOWILL Page Speed Optimizer, Speed Kit, SpeedSize, Yottaa
- **Mobile shopping apps** (3): Fuego, Stikky, Tapcart
- **Store locators and local commerce** (6): GeoStock, Mappy Store Locator, Multi Location Inventory Info, Multi-Location Stock Info, Progus Store Locator, SoCloz
- **Booking, scheduling and delivery slots** (7): Acuity Scheduling, BookThatApp, Booxi, Calendly, Cowlendar Appointment Booking, Sesami, Tote
- **Donations, carbon offset, resale and impact** (11): Beam Impact, Change, DailyKarma (Shop for Good), EcoCart, GoodAPI Sprout, GreenStory, Nopli, Provenance, Renoon, ShoppingGives, Trove
- **Other (Shopify app embed, category unknown)** (4): Libautech Amazon Buy Button, Minmaxify Order Limits, Moast, OHI

# Actor input Schema

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

Domains or URLs of the stores to analyze, one per line, for example `allbirds.com` or `https://www.gymshark.com`. A line copied from a spreadsheet or an email is cleaned (quotes, angle brackets, punctuation at the end, a Markdown link, a label such as `Website: allbirds.com`, a URL encoded as a whole), and a line that holds several domains separated by commas, semicolons, bars or spaces gives one row per domain. Scheme, www, case, paths and query strings are ignored: entries that are the same store are merged before anything is fetched, so you get one row per distinct store. Every distinct store that is not billed (not Shopify, unreachable, password protected, closed, blocked, headless without a public catalog, data that could not be read completely, or a second entry that redirected to a store already analyzed) still gets a row with the matching `status`. Can be left empty when a dataset is given below.

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

ID or name of a dataset, for example the output of another Actor that finds or lists stores, or of an earlier run of this one. The domain of each item is read from the field below and added to the store domains above (duplicates are merged). Up to 200,000 items are read.

## `domainField` (type: `string`):

Name of the field that holds the domain or URL in the items of the dataset above, with dots for a nested field (`store.url`). When empty, the first of these fields that holds a text is used: domain, url, website, storeUrl, shopUrl, inputUrl, finalUrl, homepage, link.

## `appsToMatch` (type: `array`):

App ids or names to look for, for example `judgeme` or `Klaviyo` (case, spaces and punctuation are ignored; a name must be the whole name of the app, not a part of it). Each row then says which of them were detected (`matchedApps`) and whether at least one was (`hasMatchedApp`); open the Apps to match view to filter. It changes nothing to the analysis or to the price.

## `maxProductsToScan` (type: `integer`):

How many products to read from each store's public product feed to compute prices, discounts, stock and dates. The total product count does not depend on this value when the store exposes it (`catalog.productCountMethod` says how it was obtained). When a catalog is larger than this value, price statistics and dates are computed on the products read (`catalog.scanIsComplete` is false). Reading more is slower and returns bigger payloads (250 is one request). Above 1000 products per store, concurrency is lowered to 3 and a memory setting of 2048 MB is advised.

## `includeProducts` (type: `boolean`):

Add a `productsSample` field to each row with the first products of the feed (title, URL, vendor, price, availability, dates, image).

## `productsSampleSize` (type: `integer`):

How many products to include in `productsSample` when the option above is enabled (50 at most: each product takes 12 columns in a CSV or Excel export, and Apify exports at most 2,000 columns).

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

How many stores to analyze at the same time. Requests to one store are always rate limited (about 2 per second for JSON endpoints and 1 per second for pages), so a higher value only helps with long lists. The default memory (1024 MB) is sized for 10 to 20 stores in parallel at 250 products each.

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

Optional. No proxy is used by default. Enable it if many stores answer `blocked`, which usually means the platform's datacenter IPs were challenged.

## Actor input object example

```json
{
  "storeUrls": [
    "allbirds.com",
    "https://www.gymshark.com"
  ],
  "appsToMatch": [
    "judgeme",
    "Klaviyo"
  ],
  "maxProductsToScan": 250,
  "includeProducts": false,
  "productsSampleSize": 20,
  "maxConcurrency": 10,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Dataset with one row per store: status in plain English, billed or not, platform, theme (and the Theme Store theme a custom copy is based on), apps with their evidence, catalog, prices, dates, markets, marketing tags, social pages and product-page data checks.

## `apps` (type: `string`):

One line per detected app: store, app, category and the evidence found in the page.

## `runSummary` (type: `string`):

JSON with the count of stores per status, billed stores, stores stopped by the per-store time limit, requests made and run duration.

# 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": [
        "studioproper.com",
        "graza.co",
        "rarebeauty.com"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("maxbuildog/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 = {
    "storeUrls": [
        "studioproper.com",
        "graza.co",
        "rarebeauty.com",
    ],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("maxbuildog/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 '{
  "storeUrls": [
    "studioproper.com",
    "graza.co",
    "rarebeauty.com"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call maxbuildog/shopify-store-analyzer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,maxbuildog/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/03bNHZX80GBRYMBsJ/builds/lOaxf32TRgi9uWfsh/openapi.json
