# E-commerce Platform Detector: Identify & Extract Any Store (`arman-bd/ecommerce-platform-detector`) Actor

Point it at any online store and it identifies the platform (Shopify, WooCommerce, BigCommerce, Squarespace or Magento) then extracts the catalogue using that platform's public API.

- **URL**: https://apify.com/arman-bd/ecommerce-platform-detector.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** E-commerce, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.74 / 1,000 record scrapeds

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

## E-commerce Platform Detector: Identify & Extract Any Store

![E-commerce Platform Detector: Point it at any storefront and get the platform, a confidence score and the evidence, then the catalogue pulled through that platform's own API](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/ecommerce-platform-detector.jpg)

**E-commerce Platform Detector** takes a storefront URL, works out which platform it runs on, **Shopify, WooCommerce, BigCommerce, Squarespace or Magento**, and then pulls the catalogue through that platform's own public endpoint. One input, one output shape, whatever the store is built on.

Every record carries the platform, a confidence score and the exact evidence behind it, so you can audit the detection instead of trusting it.

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/ecommerce-platform-detector.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/ecommerce-platform-detector.md
```

### How detection works

Two stages, both cheap:

1. **Passive fingerprinting**, the store's home page is fetched once and scored against response headers (`powered-by: Shopify`, `server: Squarespace`, `x-magento-*`), `Set-Cookie` names (`_shopify_essential`, `woocommerce_*`, `Shopper-Pref`), and markup markers (`cdn11.bigcommerce.com`, `/wp-content/plugins/woocommerce`, `Static.SQUARESPACE_CONTEXT`, `/static/version*/frontend/`).
2. **Active confirmation**, the candidate platform's catalogue endpoint is probed directly. A hit both proves the platform and proves the catalogue is reachable, which is why probes outweigh any single passive marker.

Stage 2 matters more than it sounds. A headless storefront can serve markup with **zero** platform markers in it, `bulk.com` renders a custom front end whose HTML mentions Magento nowhere, and is still identified with confidence 70 because `/graphql` and `/rest/V1/directory/currency` both answer.

Scores are additive and capped at 100. Below 30 the store is reported as `platform: "unknown"` with whatever evidence was found, **never as an error**.

### What you get

| Output field | Meaning |
|---|---|
| `store`, `storeUrl` | Hostname and canonical origin after redirects |
| `detectedPlatform` | `shopify`, `woocommerce`, `bigcommerce`, `squarespace`, `magento` or `unknown` |
| `confidence` | 0-100. Anything at 30 or above named a platform |
| `detectionSignals` | The individual markers that produced the score, each tagged `header:`, `cookie:`, `html:` or `probe:` |
| `extractionMethod` | Which route the products came from, see the table below |
| `productId`, `sku`, `handle` | Platform-native identifiers |
| `title`, `vendor`, `productType` | Product name, brand and category, where the platform exposes them |
| `price`, `compareAtPrice`, `currency` | Lowest current price, the struck-through price if on sale, and the ISO currency code |
| `available` | Whether any variant is purchasable |
| `variantCount` | Number of variants |
| `images` | Up to 10 image URLs |
| `url` | Direct link to the product page |
| `scrapedAt` | Run timestamp |

In `detectOnly` mode each record instead carries `platformScores` (every platform's score, not just the winner), `httpStatus` and `finalUrl`.

A `RUN_SUMMARY` record in the key-value store holds a per-store breakdown, a platform histogram, the filters used and any store that failed.

### Extraction routes

| Platform | Method | Endpoint |
|---|---|---|
| Shopify | `shopify-products-json` | `/products.json?limit=250&page=N`, currency from `/meta.json` |
| WooCommerce | `woocommerce-store-api` | `/wp-json/wc/store/v1/products` (falls back to the pre-v1 path) |
| Squarespace | `squarespace-format-json` | Store collections discovered from the sitemap, read via `?format=json` |
| Magento | `magento-graphql` | `POST /graphql` with a paged `products(filter: {})` query |
| BigCommerce, unknown, or any closed API | `jsonld-sitemap` | Product URLs from `robots.txt` / `sitemap.xml` / `xmlsitemap.php`, then schema.org `Product` JSON-LD from each page |

**Extraction degrades. It does not fail.** If a store is confidently Shopify but has `/products.json` switched off, the Actor logs that and falls back to sitemap + JSON-LD rather than returning nothing. BigCommerce has no anonymous catalogue API at all, so it always takes the JSON-LD route, and still returns structured products.

### Common use cases

**Audit a competitor set on mixed platforms.** One input, one output schema, regardless of what each competitor is built on.

```json
{
 "storeUrls": [
 "https://www.allbirds.com",
 "https://barefootbuttons.com",
 "https://www.berlinpackaging.com",
 "https://www.aaksonline.com"
 ],
 "maxProductsPerStore": 250
}
```

**Qualify e-commerce leads by platform.** Detection only, one cheap record per domain, no catalogue.

```json
{
 "storeUrls": ["allbirds.com", "gymshark.com", "bulk.com", "aaksonline.com"],
 "detectOnly": true
}
```

**Build a universal price monitor.** Restrict to the platforms whose APIs give you exact prices, and run it on a schedule.

```json
{
 "storeUrls": ["https://www.allbirds.com", "https://barefootbuttons.com"],
 "platforms": ["shopify", "woocommerce"],
 "maxProductsPerStore": 1000
}
```

### Quick start

One store, first 100 products:

```json
{
 "storeUrls": ["https://www.allbirds.com"]
}
```

Just tell me what it runs on:

```json
{
 "storeUrls": ["allbirds.com"],
 "detectOnly": true
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `storeUrls` | array | - | **Required.** Store home pages. Bare domains work; redirects are followed. |
| `detectOnly` | boolean | `false` | Emit one detection record per store and skip extraction. |
| `maxProductsPerStore` | integer | `100` | Cap per store. `0` = no limit. |
| `platforms` | array | `[]` | Restrict extraction to these platforms. Empty = all. Add `unknown` to keep unidentified stores. |

Combinations that make sense together:

- **`detectOnly: true` + a long `storeUrls` list**: the lead-qualification mode. One record and roughly two to four requests per domain.
- **`platforms` + a long list**: everything is still *detected* (and reported in `RUN_SUMMARY.stores`), but only matching stores are extracted. You get the tech-stack census for free while paying for products from just the platforms you care about.
- **A locale sub-path in `storeUrls`.** Pass `ikea.com/us/en/` rather than `ikea.com`. Detection normalises to the origin, but URLs under the sub-path you gave are preferred during sitemap extraction and used when building Magento product links.
- **`maxProductsPerStore: 0` on a big Shopify store**: expect tens of thousands of products; raise the run timeout to match.

### Output example

```json
{
 "store": "www.allbirds.com",
 "storeUrl": "https://www.allbirds.com",
 "detectedPlatform": "shopify",
 "confidence": 100,
 "detectionSignals": [
 "header: powered-by: Shopify response header",
 "cookie: _shopify_* session cookie",
 "html: cdn.shopify.com asset host",
 "probe: /products.json returns a Shopify product array",
 "probe: /meta.json exposes a myshopify_domain"
 ],
 "extractionMethod": "shopify-products-json",
 "productId": 7292464955472,
 "title": "Men's Cruiser - Shadow Blue (Natural White Sole)",
 "handle": "mens-cruiser-shadow-blue-natural-white-sole",
 "vendor": "Allbirds",
 "productType": "Shoes",
 "price": 105,
 "compareAtPrice": null,
 "currency": "USD",
 "available": true,
 "sku": "A12856M080",
 "variantCount": 13,
 "images": ["https://cdn.shopify.com/s/files/1/1104/4168/files/All-birds_0010.png?v=1783535519"],
 "url": "https://www.allbirds.com/products/mens-cruiser-shadow-blue-natural-white-sole",
 "scrapedAt": "2026-08-06T11:49:23.335Z"
}
```

A `detectOnly` record:

```json
{
 "store": "www.bulk.com",
 "storeUrl": "https://www.bulk.com",
 "detectedPlatform": "magento",
 "confidence": 70,
 "detectionSignals": [
 "probe: Magento GraphQL products query succeeds at /graphql",
 "probe: Magento REST answers at /rest/V1/directory/currency"
 ],
 "platformScores": { "magento": 70 },
 "extractionMethod": "magento-graphql",
 "httpStatus": 200,
 "finalUrl": "https://www.bulk.com/uk/",
 "scrapedAt": "2026-08-06T11:52:04.118Z"
}
```

`RUN_SUMMARY` looks like this:

```json
{
 "storesRequested": 5,
 "storesFailed": 0,
 "failures": [],
 "stores": [
 { "store": "www.allbirds.com", "detectedPlatform": "shopify", "confidence": 100, "detectionSignals": ["."], "extractionMethod": "shopify-products-json", "products": 291 },
 { "store": "barefootbuttons.com", "detectedPlatform": "woocommerce", "confidence": 100, "detectionSignals": ["."], "extractionMethod": "woocommerce-store-api", "products": 61 },
 { "store": "www.berlinpackaging.com", "detectedPlatform": "bigcommerce", "confidence": 100, "detectionSignals": ["."], "extractionMethod": "jsonld-sitemap", "products": 60 }
 ],
 "platformBreakdown": { "shopify": 1, "woocommerce": 1, "bigcommerce": 1 },
 "itemsSaved": 412,
 "filters": { "detectOnly": false, "maxProductsPerStore": 0, "platforms": [] },
 "finishedAt": "2026-08-06T11:53:41.902Z"
}
```

### Limits and behaviour

- **An unknown platform is a result, not an error.** Stores below the confidence threshold are reported as `platform: "unknown"` with their evidence, and still go through sitemap + JSON-LD extraction. In testing, `ikea.com`, which matches none of the five fingerprints, returned fully structured products this way.
- **Confidence is additive and capped at 100.** Two independent probes plus a header marker will hit the cap; a single weak HTML marker will not clear the threshold of 30 on its own.
- **WordPress is not WooCommerce.** Generic WordPress markers score low on purpose; the decisive signal is the Store API answering at `/wp-json/wc/store/v1/products`.
- **Prices are normalised.** WooCommerce's Store API returns money in minor units (`"195"` with `currency_minor_unit: 2`); this Actor divides it out, so you get `1.95`. Squarespace variant prices come from `priceMoney`, not the frequently-zero product-level field.
- **`price` is the lowest current variant price**, not the price of a specific variant. `compareAtPrice` is the struck-through original where the platform reports one.
- **Magento product URLs are the Magento-canonical path.** For a standard install that is the live URL. Heavily customised or headless storefronts sometimes route products elsewhere, in which case the link points at the canonical path rather than the custom one, the price, SKU and stock status are still exact.
- **JSON-LD extraction is only as good as the store's markup.** Products without a schema.org `Product` block are skipped rather than guessed at, and the Actor reports how many sitemap URLs it scanned in the log and in the failure note.
- **Sitemap crawling is bounded.** At most 12 sitemap files and 6 child sitemaps from an index, with product-shaped URLs prioritised.
- **Bare domains are recovered.** Plenty of stores serve a valid certificate on `www.` and a broken chain on the apex, `berlinpackaging.com` is one. If the host you gave fails outright, the other variant is tried once before the store is written off.
- **One store failing never aborts the run.** Detection and extraction failures are recorded in `RUN_SUMMARY.failures` and the run continues.
- **Transient errors are retried.** 429 and 5xx get up to three attempts with linear backoff. A 4xx from a probe is treated as a signal (that platform is absent), not a failure.
- **Public catalogue data only.** No authentication, no personal data, no access-control bypass, every endpoint used is one the platform publishes for front-end code to read.

### Verified against real stores

Detection and extraction were run against five live storefronts on five different platforms, plus one store matching none of the fingerprints:

| Store | Detected | Confidence | Route | Result |
|---|---|---|---|---|
| `allbirds.com` | shopify | 100 | `shopify-products-json` | 291 products, `powered-by` header + `_shopify_*` cookies + both probes |
| `barefootbuttons.com` | woocommerce | 100 | `woocommerce-store-api` | 61 products, plugin asset path + Store API probe |
| `berlinpackaging.com` | bigcommerce | 100 | `jsonld-sitemap` | 60/60 requested products, all with price, SKU and images; no anonymous API, so JSON-LD was used |
| `aaksonline.com` | squarespace | 100 | `squarespace-format-json` | 41 products, `server: Squarespace` + `?format=json` probe |
| `bulk.com/uk/` | magento | 70 | `magento-graphql` | **Zero HTML markers**, headless front end; identified entirely by the `/graphql` and `/rest/V1` probes |
| `ikea.com/us/en/` | unknown | 0 | `jsonld-sitemap` | No fingerprint matched; products still extracted from schema.org markup, in USD from the `/us/en/` catalogue |

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~ecommerce-platform-detector/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "storeUrls": ["https://www.allbirds.com", "https://barefootbuttons.com"],
 "maxProductsPerStore": 50
 }'
```

### JavaScript example

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/ecommerce-platform-detector').call({
 storeUrls: ['allbirds.com', 'barefootbuttons.com', 'bulk.com'],
 detectOnly: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const s of items) console.log(`${s.store} → ${s.detectedPlatform} (${s.confidence})`);
```

**Defaults:** 2 GB memory, 30 min timeout.

### FAQ

**Do I need a proxy?** No. Proxy configuration is not required to run this Actor.

**Do I need an account on the source?** No, on any of the five platforms.

**What happens if a store is unavailable?** It is recorded in `RUN_SUMMARY.failures` and the run continues with the rest. The Actor only throws if no store could be processed at all.

**Why did a store come back as `unknown`?** Either it runs on a platform outside the five fingerprinted here (Salesforce Commerce Cloud, Wix, Ecwid, custom builds), or it is a headless front end whose APIs are not at the standard paths. Check `platformScores` in `detectOnly` mode to see whether anything scored at all, products may still have been extracted from JSON-LD.

**Why does a BigCommerce store use `jsonld-sitemap`?** BigCommerce's storefront GraphQL API requires a bearer token that stores do not publish, so there is no anonymous catalogue endpoint. Sitemap plus schema.org markup is the honest route, and it works.

**Can I get variant-level prices?** Not as separate records, `price` is the lowest live variant price and `variantCount` tells you how many there are. This keeps one row per product across all five platforms.

**Why is `currency` sometimes null?** Not every store declares one in a machine-readable place. Shopify reads it from `/meta.json`, WooCommerce and Squarespace return it per product, and JSON-LD supplies `priceCurrency` when the store publishes it.

**Can I schedule it?** Yes, it is designed for scheduled runs. Diff on `productId` plus `price` for price monitoring, or on `detectedPlatform` to catch re-platforming.

**Can I integrate it with something else?** Yes, Apify API, client libraries, webhooks, scheduled runs, dataset exports (JSON/CSV/Excel) or MCP. Output is structured JSON.

# Actor input Schema

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

The home page of each store. 'https://www.allbirds.com' or just 'allbirds.com'. Point at a locale sub-path such as 'ikea.com/us/en/' when a store serves several regions, and that region is preferred. Redirects are followed and the 'www.' variant is tried automatically if the bare domain fails, so a bare domain is fine.

## `detectOnly` (type: `boolean`):

Return one record per store with the detected platform, a confidence score and the evidence behind it. and skip catalogue extraction entirely. This is the cheap mode for qualifying a list of leads by tech stack.

## `maxProductsPerStore` (type: `integer`):

Cap the catalogue pulled from each store. Large Shopify and Magento stores run to tens of thousands of products, so leave a cap in place unless you really want everything. Set 0 for no limit.

## `platforms` (type: `array`):

Extract only from stores detected as one of these. Everything else is detected, logged in RUN_SUMMARY and then skipped. useful when you are auditing a mixed list but only care about, say, Shopify. Leave empty to process every store. Include 'unknown' to keep stores whose platform could not be identified.

## Actor input object example

```json
{
  "storeUrls": [
    "allbirds.com",
    "https://www.berlinpackaging.com"
  ],
  "detectOnly": false,
  "maxProductsPerStore": 100,
  "platforms": [
    "shopify",
    "woocommerce"
  ]
}
```

# Actor output Schema

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

Every record the run produced.

## `runsummary` (type: `string`):

The RUN_SUMMARY record from the run's key-value store.

# 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": [
        "https://www.allbirds.com",
        "https://barefootbuttons.com"
    ],
    "maxProductsPerStore": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/ecommerce-platform-detector").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": [
        "https://www.allbirds.com",
        "https://barefootbuttons.com",
    ],
    "maxProductsPerStore": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/ecommerce-platform-detector").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": [
    "https://www.allbirds.com",
    "https://barefootbuttons.com"
  ],
  "maxProductsPerStore": 100
}' |
apify call arman-bd/ecommerce-platform-detector --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/ecommerce-platform-detector"
        }
    }
}
```

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/NzlbsV7PF7jWLefxB/builds/mGkgTfPQnstW5q2ar/openapi.json
