# Shopify Products Scraper & AI Shopping Readiness Audit (`yourevenings/shopify-catalogue-audit`) Actor

Shopify products scraper and catalog export with a 0-100 AI shopping readiness audit. Every store gets a row explaining its result; can read past Shopify's 25,000-product feed limit and says if the catalogue is complete.

- **URL**: https://apify.com/yourevenings/shopify-catalogue-audit.md
- **Developed by:** [Your Evenings](https://apify.com/yourevenings) (community)
- **Categories:** E-commerce, SEO tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.64 / 1,000 product returneds

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 Products Scraper & AI Shopping Readiness Audit

**Shopify Products Scraper & AI Shopping Readiness Audit** turns a list of Shopify store domains into two things:

1. **A Shopify catalog export**: one clean row per product with title, vendor (brand), product type, tags, variant count, price range, availability, SKUs, barcodes, image count, product URL and dates.
2. **An AI shopping readiness audit** for each store: how much of the catalogue is missing the product data that AI shopping assistants and product feeds rely on (GTIN/barcode, brand, product type, a real description, image alt text, SKUs, unique titles), rolled up into a **0–100 readiness score** with the top issues and example product handles to fix first.

### Why this one

- **Find variants with missing, invalid or duplicate barcodes across the whole catalogue.** Turn on `readAllProductDetails` and every variant's barcode is checked: missing, invalid (wrong length or check digit for GTIN-8, 12, 13 or 14), or the same barcode on more than one variant. You get a status on each variant, totals in the audit row and a `BARCODE-ISSUES` CSV listing only the problem variants. It's read-only: nothing in the store is changed.
- **No run ends with unexplained zero results.** Every store gets a row: its audit, or a status with a plain-English `note` (not Shopify, password-protected or headless, no products, blocked, or disallowed by robots.txt).
- **Whole catalogues, with completeness you can check.** Paging continues past short pages until the feed is really empty, and above Shopify's 25,000-product feed limit the rest comes from the store's public sitemap, collection feeds and product pages. Each audit row says whether the whole catalogue was read (`catalogueComplete`, `true` only when verified) and the store's known product total; if anything was missed, the note says what and why.
- **Free "is this a Shopify store?" check.** Paste any domain; sites that aren't Shopify return one free `not_shopify` row.
- **Your limits hold, and status rows are free.** `maxProductsPerStore` is a hard cap on products returned and charged per store. You pay for product rows and store audits plus the small per-run start fee; status rows are never charged.
- **Every image and every variant, not just a summary.** Each product row lists all image URLs in position order and every variant with SKU, price, compare-at price, options and availability, and there's a one-row-per-variant view. Turn on `readAllProductDetails` to add barcodes, image alt text, weights and stock counts where the store publishes them. Nothing is estimated.
- **Polite by default.** robots.txt (including `Crawl-delay`) is respected, requests are limited to one per second per store, and the Actor backs off when a store answers HTTP 429.

### Quick start

Paste this into the **JSON** view of the Input tab and press **Start**. It reads up to 20 products from one public Shopify store and returns a row per product plus the store's AI shopping readiness audit.

```json
{
  "stores": [
    "partakefoods.com"
  ],
  "maxProductsPerStore": 20
}
```

For more ready-made inputs, see the [example tasks](https://apify.com/yourevenings/shopify-catalogue-audit/examples).

**Works with:** Shopify stores

**Runs on** <img src="https://yourevenings.com/logos/apify-logo-dark.svg" alt="Apify" height="20"> Apify. Use it from Make, Zapier, <img src="https://yourevenings.com/logos/n8n-logo-black.svg" alt="n8n" height="16"> n8n or the Apify API.

<sub>Product names and logos belong to their owners. Your Evenings Ltd is not affiliated with or endorsed by them.</sub>

It reads only the **public storefront product JSON** that Shopify stores serve (`/products.json`, `/collections.json`, each product's own `.json` and the store's public `/meta.json`), plus the store's public product sitemap for catalogues above 25,000 products. No login, no browser, no proxies, and no customer data.

### What you can use it for

- **Shopify products scraper / catalog export.** Pull a competitor's or supplier's full public catalogue into a spreadsheet, a database or a price-monitoring pipeline.
- **AI shopping readiness checks.** See which catalogue data gaps commonly keep products from showing up well in AI shopping answers and product feeds, and which products to fix first.
- **Agency and freelancer audits.** Run it over a prospect list and lead with a concrete, measured data-quality finding per store.
- **Product feed QA.** Check GTIN, brand and product-type coverage before sending a catalogue to Google Merchant Center or other feeds.

### Example tasks (ready-made inputs)

- [Find Shopify variants with missing or invalid barcodes](https://apify.com/yourevenings/shopify-catalogue-audit/examples/shopify-barcode-issues)
- [Export a full Shopify catalogue with all images and variants](https://apify.com/yourevenings/shopify-catalogue-audit/examples/full-catalogue-images-variants)
- [Export product catalogues from DTC Shopify brands](https://apify.com/yourevenings/shopify-catalogue-audit/examples/dtc-shopify-catalog-export)
- [Audit Shopify stores for AI shopping readiness](https://apify.com/yourevenings/shopify-catalogue-audit/examples/ai-shopping-readiness-audit)
- [Compare product ranges of Shopify beauty brands](https://apify.com/yourevenings/shopify-catalogue-audit/examples/beauty-brand-catalogues)
- [Find Shopify products missing GTIN barcodes](https://apify.com/yourevenings/shopify-catalogue-audit/examples/missing-gtin-barcodes)
- [Track food and drink brand prices on Shopify](https://apify.com/yourevenings/shopify-catalogue-audit/examples/coffee-brand-prices)

### What the audit checks

All rules are deterministic (no AI model is involved), so the same catalogue always gets the same score.

| Check | What counts as a problem | Measured on | Weight |
|---|---|---|---|
| GTIN / barcode | Variant has no barcode, or the barcode is not a valid GTIN-8, UPC-A (12), EAN-13 or GTIN-14 check digit | Detail sample of variants | 20 |
| Description | Product has no description or fewer than **30 words** after removing HTML | All scanned products | 20 |
| Product type | Product type is blank | All scanned products | 10 |
| Vendor (brand) | Vendor is blank | All scanned products | 10 |
| Image alt text | Image has empty alt text | Detail sample of images | 10 |
| SKU | Variant SKU is blank | All scanned variants | 10 |
| Images | Product has no image | All scanned products | 10 |
| Duplicate titles | Title is identical to another product's (case and spacing ignored) | All scanned products | 10 |

**Readiness score formula (version 1.0):**

```
score = round( 100 × (1 − Σ(weight × problem% / 100) / Σ weight) )
```

The sums run over the checks that were measured. If a check can't be measured (for example, the detail sample is turned off), it drops out and the remaining weights are rescaled. Grades: **good** 90–100, **fair** 70–89, **needs work** 50–69, **poor** under 50. Every audit row includes a `scoreBreakdown` with each check's weight, problem percentage and penalty points, and `topIssues` ranked by how many score points each issue costs.

**Why a detail sample?** Shopify's catalogue-wide `/products.json` doesn't include variant barcodes or image alt text. Those are only in each product's own JSON, so the Actor reads that for a sample of products spread evenly across the catalogue (20 by default, up to 250). The GTIN and alt-text percentages are measured on that sample, and the sample size is in every audit row. Products in the sample have `barcodeChecked: true` and `detailRead: true`, with their barcode and alt-text fields filled. With `readAllProductDetails` on, every product's own JSON is read and the checks cover the whole set of products read.

**What the score is not.** It measures catalogue data quality only. It is not a ranking factor of any AI assistant or search engine and doesn't guarantee that products will appear in ChatGPT, Google or any other shopping surface. Those platforms also weigh eligibility, merchant settings, reviews, pricing and much else.

### Barcode check: missing, invalid and duplicate barcodes

The Actor checks the barcode of every variant it reads from each product's own public JSON. With `readAllProductDetails` on, that's every variant of every product read. With it off, it's the detail sample only (`detailSampleSize`, 20 products by default). Each checked variant gets a `barcodeStatus`:

- `valid`: a GTIN-8, UPC-A (12 digits), EAN-13 or GTIN-14 with a correct check digit.
- `missing`: the variant has no barcode.
- `invalid`: the barcode isn't a GTIN of those lengths, or its check digit is wrong.
- `duplicate`: an earlier variant in the same store has the same barcode (a 12-digit UPC and the same code as a 13-digit EAN count as the same). `barcodeDuplicateOf` names that earlier variant, and `barcodeValidGtin` still says whether the code itself is a valid GTIN.

Where to find the results:

- **`BARCODE-ISSUES` CSV** in the run's key-value store (linked from the Output tab and the run's status message): one line per problem variant, with store, product title, handle, product URL, variant ID and title, options, SKU, barcode and status. For each duplicated barcode, the first variant that has it is listed too (with `variantsWithThisBarcode`), so every variant sharing a barcode is in the file. If nothing is wrong, the CSV has only its header line.
- **Audit row** `barcodeCheck`: variants checked and how many are valid, missing, invalid or duplicate, plus the number of duplicated barcodes and up to 10 examples. The `valid` count includes the first variant of each duplicated barcode.
- **Variants view**: every variant with its `barcodeStatus`.

To check barcodes only, run with `includeCatalogueRows: false` and `readAllProductDetails: true`. You pay only the store audit (no product rows), and the product JSON requests are free. The check reads only what the store publishes and changes nothing in Shopify.

### Input

| Field | What it does | Default |
|---|---|---|
| `stores` | Shopify store domains or URLs, one per line | 3 example stores |
| `includeCatalogueRows` | One row per product | on |
| `includeAudit` | One readiness-audit row per store | on |
| `maxProductsPerStore` | Stop after this many products per store (0 = whole catalogue, up to 100,000) | 250 |
| `readAllProductDetails` | Read every product's own JSON, so every row gets barcodes, image alt text, weights and inventory fields where the store publishes them. One extra request per product (about 1 s each); raise the run timeout for big catalogues. No extra charge | off |
| `detailSampleSize` | Products checked for barcodes and image alt text (0 = skip these two checks) | 20 |
| `requestTimeoutSecs` | Per-request timeout; failed requests are retried 3 times with backoff | 15 |

Example input:

```json
{
  "stores": ["beardbrand.com", "https://www.allbirds.com/collections/mens", "partakefoods.com"],
  "includeCatalogueRows": true,
  "includeAudit": true,
  "maxProductsPerStore": 250,
  "detailSampleSize": 20
}
```

Paths in URLs are ignored: the whole store is read. Audit-only runs (`includeCatalogueRows: false`) are the cheapest way to score many stores.

### Output

The dataset has three kinds of rows, told apart by `recordType`. The Output tab has **Products**, **Variants** (one row per variant), **Readiness audits** and **Store status** views.

A product row (`recordType: "product"`):

```json
{
  "recordType": "product",
  "status": "ok",
  "store": "partakefoods.com",
  "storeUrl": "https://partakefoods.com",
  "productId": "7776301088866",
  "handle": "girl-scouts-sparkables",
  "title": "Girl Scout Sparkables™",
  "vendor": "Partake Foods",
  "productType": "Cookies",
  "tags": ["no_purchase"],
  "variantsCount": 1,
  "availableVariantsCount": 1,
  "anyAvailable": true,
  "priceMin": 0,
  "priceMax": 0,
  "compareAtPriceMax": null,
  "currency": "USD",
  "options": [],
  "skus": [],
  "variantsMissingSku": 1,
  "barcodeChecked": true,
  "barcodes": [],
  "variantsWithBarcode": 0,
  "variantsWithValidGtin": 0,
  "imageCount": 4,
  "imagesMissingAlt": 4,
  "featuredImageUrl": "https://cdn.shopify.com/s/files/1/0012/2296/7353/files/Girl_Scout_Sparkables_Partake_Cookie.png?v=1787935354",
  "images": [
    { "imageId": "35504456368226", "position": 1, "url": "https://cdn.shopify.com/s/files/1/0012/2296/7353/files/Girl_Scout_Sparkables_Partake_Cookie.png?v=1787935354", "width": 3000, "height": 3000, "alt": null, "variantIds": [] },
    "… 3 more images"
  ],
  "variants": [
    { "variantId": "43229654909026", "title": "Default Title", "position": 1, "sku": null, "barcode": null, "barcodeStatus": "missing", "barcodeValidGtin": null, "barcodeDuplicateOf": null, "price": 0, "compareAtPrice": null, "options": {}, "available": true, "grams": null, "weight": null, "weightUnit": null, "inventoryQuantity": null, "inventoryTracked": null, "sellsWhenOutOfStock": null, "requiresShipping": false, "imageId": null }
  ],
  "detailRead": true,
  "descriptionWordCount": 120,
  "url": "https://partakefoods.com/products/girl-scouts-sparkables",
  "createdAt": "2026-08-21T13:00:05.000Z",
  "updatedAt": "2026-10-21T17:40:01.000Z",
  "publishedAt": "2026-09-12T17:03:23.000Z"
}
```

An audit row (`recordType: "store-audit"`, shortened):

```json
{
  "recordType": "store-audit",
  "status": "ok",
  "store": "partakefoods.com",
  "storeName": "Partake Foods",
  "currency": "USD",
  "publishedProductsCount": 15,
  "catalogueComplete": true,
  "totalProductsKnown": 15,
  "totalProductsKnownSource": "products.json (read to the end)",
  "productsRead": 15,
  "productSources": { "products.json": 15 },
  "productsScanned": 15,
  "variantsScanned": 28,
  "detailSampleProducts": 15,
  "pctVariantsMissingBarcode": 71.4,
  "pctVariantsWithoutValidGtin": 71.4,
  "pctProductsMissingVendor": 0,
  "pctProductsMissingProductType": 26.7,
  "pctProductsMissingOrThinDescription": 0,
  "duplicateTitleGroups": 0,
  "pctImagesMissingAlt": 100,
  "pctProductsWithoutImages": 0,
  "pctVariantsMissingSku": 17.9,
  "readinessScore": 71,
  "readinessGrade": "fair",
  "topIssues": [
    {
      "issue": "missingGtin",
      "description": "Variants without a valid GTIN/barcode (UPC, EAN or ISBN)",
      "affectedPercent": 71.4,
      "affectedCount": 20,
      "outOf": 28,
      "unit": "variants (detail sample)",
      "impactPoints": 14.3,
      "exampleHandles": ["girl-scouts-sparkables", "mini-chocolate-chip-grahams", "crunchy-ginger-snap"]
    }
  ],
  "collectionsCount": 35,
  "thinDescriptionThresholdWords": 30,
  "scoreFormulaVersion": "1.0"
}
```

**Images and variants.** `images` lists every product image in position order (`url`, `width`, `height`, `variantIds`). `variants` lists every variant with `sku`, `price`, `compareAtPrice`, `options` (for example `{"Size": "M", "Colour": "Red"}`), `available` and `grams`, all from the store's product feed. Some fields are only in each product's own JSON, so they are filled only when that was read (`detailRead: true`, which is every product with `readAllProductDetails` on, or the audit sample otherwise): image `alt`, variant `barcode`, `weight` with `weightUnit`, and the inventory fields.

**Stock.** Shopify's public feed shows only whether each variant is `available`. Some stores also publish a stock count in each product's own JSON. Where one is published, `inventoryQuantity` copies it, along with `inventoryTracked` and `sellsWhenOutOfStock` (Shopify's "continue selling when out of stock" setting). Many stores hide these, and the store controls the number, so it may be rounded, capped or out of date. When it isn't published, the field is `null`. Stock counts are never estimated.

**One row per variant.** The **Variants** view in the Output tab shows one row per variant. To export that shape, add `unwind=variants` to the dataset items API URL (for example `https://api.apify.com/v2/datasets/{datasetId}/items?format=csv&unwind=variants`). You're charged per product row either way.

A status row (`recordType: "store"`) is returned, free of charge, for inputs that can't be read:

- `not_shopify`: no Shopify product feed and no Shopify markers on the homepage.
- `shopify_products_unavailable`: the site is a Shopify store, but its public product feed is closed (password-protected, headless or blocked).
- `blocked_by_robots`: the store's `robots.txt` disallows `/products.json` or `/products/` for this crawler (or for all crawlers), so it was skipped without reading the catalogue.
- `blocked`: the site refused the requests (for example HTTP 429), so it couldn't be checked. Try again later.
- `no_products`, `duplicate` (same store as an earlier line), `invalid_input` or `error` (with a `note`).
- `catalogue_incomplete`: only when `includeAudit` is off. The catalogue couldn't be read in full, and the row carries the same completeness fields and note as the audit row would.

A run summary (status, product count, score and timing per store) is saved as `SUMMARY` in the run's key-value store.

### Pricing

This Actor uses pay-per-event pricing. You pay for results, not for compute:

| Event | When it is charged | Price |
|---|---|---|
| Actor start | Once per run (per GB of memory; the default 256 MB run counts as one start) | $0.00005 |
| Product | Each product row returned | $0.0008 |
| Store audit | Each store readiness-audit row returned | $0.02 |

Status rows (`not_shopify`, `shopify_products_unavailable`, errors and so on) are never charged. Higher Apify plans get tier discounts (Silver −10%, Gold −20%). Prices on the Actor's Pricing tab are authoritative.

Examples at the base price:

- Audit only, 100 stores: 100 × $0.02 + $0.00005 = about $2.00.
- Catalogue and audit for 10 stores of 250 products each: 2,500 × $0.0008 + 10 × $0.02 = about $2.20.
- The default input (3 stores, 128 products when tested): 128 × $0.0008 + 3 × $0.02 = about $0.16.

The Actor respects the **maximum cost per run** you set. When the limit is close it stops adding product rows, still delivers the audit for the store it's working on if that fits, and says so in the status message.

### Large catalogues and completeness

Shopify's public product feed lists 250 products per page and stops at page 100 (25,000 products); page 101 returns an error. Some stores also return short pages in the middle of the catalogue, so the Actor keeps paging until a page is **empty** rather than stopping at the first short page. It also stops if a page only repeats products it has already read.

Above 25,000 products the Actor:

1. Reads the store's public product sitemap (`/sitemap.xml` → `sitemap_products_N.xml`) to get the full list of product handles.
2. Reads public collection feeds (`/collections/{handle}/products.json`), largest first, to pick up products the main feed didn't reach. A collection that only repeats products already read is skipped after three pages, and collections stop altogether once they find fewer new products than requests.
3. Reads each product that is still missing from its own public JSON (`/products/{handle}.json`).

Every audit row reports completeness honestly:

| Field | Meaning |
|---|---|
| `catalogueComplete` | `true` only when verified: the main feed was read to an empty page (or to the exact count in `/meta.json`), or every product in the store's sitemap was found. `false` when `maxProductsPerStore`, the run budget, the run timeout, robots.txt or an error stopped it. |
| `totalProductsKnown` | The store's product total when it's known: the count read when the catalogue is complete, otherwise `/meta.json` (exact only below 25,000; Shopify reports 25001 for anything larger) or the sitemap. `null` when unknown. |
| `totalProductsKnownSource` | Where that total came from: `products.json (read to the end)`, `meta.json` or `sitemap`. |
| `productsRead`, `productSources` | Products read, and how many came from each route. |

When the catalogue is truncated, the audit's `note` says so, for example "Shopify's public feed stops at 25,000 products; catalogue truncated…", with how many products the sitemap lists and how many weren't read.

**Run time.** Requests are rate-limited (one per second per store, or longer if robots.txt asks), so about 25,000 products take at least 100 seconds through the main feed, and anything above that takes one request per collection page or per product. The Actor's default run timeout is 300 seconds. For catalogues above about 20,000 products, raise the run timeout in the run options (in our test, 35,140 products took about 30 minutes). If time runs out, the Actor stops fetching about 45 seconds before the timeout, still delivers the audit, and marks the catalogue incomplete.

### Speed and politeness

Each store's `robots.txt` is read first and respected: stores that disallow the product feed are skipped, disallowed paths (including collection feeds, sitemaps and product pages) are never fetched, and a `Crawl-delay` for this crawler or for all crawlers is honoured. Requests to each store are limited to **one per second**, with at most three stores in parallel, a 15-second timeout, and retries with backoff on HTTP 429 and 5xx (using `Retry-After` when the store sends it). In local tests (21–22 Oct 2026, 256 MB memory), a 250-product store with the default 20-product detail sample took about 22–27 seconds, a full 9,357-product catalogue took about 50 seconds, and a full 35,140-product catalogue (above the 25,000 feed limit) took about 30 minutes.

### Use it from code, spreadsheets or an AI agent

- **Schedule it**: Console > Schedules, for example weekly, to re-export catalogues or re-check readiness scores.

- **Google Sheets, Make, Zapier, n8n, Slack**: use the Actor's Integrations tab to send each run's dataset where you need it.

- **API (Python)**:

  ```python
  from apify_client import ApifyClient
  client = ApifyClient("<YOUR_APIFY_TOKEN>")
  run = client.actor("yourevenings/shopify-catalogue-audit").call(run_input={"stores": ["allbirds.com"]})
  for item in client.dataset(run["defaultDatasetId"]).iterate_items():
      print(item["recordType"], item.get("store"), item.get("title"), item.get("readinessScore"))
  ```

- **AI agents**: the Actor can be run through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp), so Claude, ChatGPT or Cursor agents can call it with their own input.

#### AI agents can pay for runs themselves

AI agents can run this Actor and pay with stablecoins such as USDC through the x402 or MPP protocols, with no Apify account needed. The agent buys a prepaid, spend-capped Apify API token from Apify's Agent General Interface (AGI) and then runs the Actor with it as normal. Prepaid tokens only run Actors that use limited permissions, and this Actor does. Agentic payments are an experimental Apify feature, and the terms (minimum amount, expiry, usage limits) are set by Apify. See Apify's docs: [Let your AI agent pay for Actor runs](https://docs.apify.com/agents/payments), [x402](https://docs.apify.com/integrations/x402) and [MPP](https://docs.apify.com/integrations/mpp).

### FAQ

**Is this an official Shopify, OpenAI or Google product?**
No. It's an independent tool and isn't affiliated with or endorsed by Shopify, OpenAI, Google or any store it reads. The names are used only to describe what it works with.

**Where does the data come from?**
From the public storefront JSON that Shopify stores serve to anyone: `/products.json`, `/collections.json`, `/collections/{handle}/products.json`, `/products/{handle}.json` and `/meta.json`, plus the public product sitemap for catalogues above 25,000 products. It doesn't log in, use the Admin API or read checkout, cart or customer data. From `/meta.json` it uses only the store name, currency and published product count; it never outputs the store's address, city or contact details.

**Does it collect personal data or emails?**
No. It reads published product information (merchants' business data) only. It never collects customer data, reviews or email addresses.

**Why is a store returned as `shopify_products_unavailable`?**
Some Shopify stores close their public product feed (password pages, headless storefronts, or bot protection). There's nothing to read, so the row is free.

**Will fixing the issues get my products into ChatGPT or Google AI shopping?**
Nobody can promise that. Complete GTINs, brands, product types, descriptions and alt text are the kind of structured product data AI shopping and product feeds commonly depend on, so the audit tells you where those gaps are. Inclusion and ranking are decided by each platform.

**Why do barcode and alt-text percentages use a sample?**
Shopify only publishes them in each product's own JSON, which means one request per product. The sample keeps runs fast and polite. Set `detailSampleSize` up to 250 for a fuller check.

**Did I get the whole catalogue?**
Check `catalogueComplete` on the audit row. It's `true` only when the Actor verified it read every product; `totalProductsKnown` and the `note` tell you what's missing when it isn't. With the default `maxProductsPerStore` of 250, larger stores are deliberately capped; set it to 0 for the whole catalogue (and raise the run timeout for very large stores).

**How do I check if a store is on Shopify?**
Paste the domain: stores that aren't Shopify return one free `not_shopify` row. Shopify stores that close their public product feed return a free `shopify_products_unavailable` row instead.

**Is it legal to use this data?**
The Actor reads only product information that merchants publish on their public storefronts, with polite rate limits. You're responsible for using the data in line with the laws that apply to you and the terms of the sites you use it with.

### Feedback

Found a store that should work, a wrong figure or a check you'd like added? Open an issue on the Issues tab with the store and run ID, and it'll be looked at quickly.

# Changelog

This Actor's version history is a separate document: https://apify.com/yourevenings/shopify-catalogue-audit/changelog.md

# Actor input Schema

## `stores` (type: `array`):

One store per line: a domain such as <code>beardbrand.com</code>, a store URL such as <code>https://www.example.com/collections/all</code> or a <code>\*.myshopify.com</code> address. Paths are ignored; the whole store is read. Sites that aren't Shopify stores, or whose public product feed is closed, return one free status row.

## `includeCatalogueRows` (type: `boolean`):

Return a row for every product: title, vendor, product type, tags, variant count, price range, availability, SKUs, image count, URL and dates. Turn off if you only want the per-store audit (product rows are charged per row).

## `includeAudit` (type: `boolean`):

Return one audit row per store with data-quality percentages (missing GTIN/barcode, brand, product type, thin descriptions, duplicate titles, image alt text, SKUs), a 0-100 readiness score and the top issues with example product handles.

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

Stop after this many products in each store. The audit covers the products scanned. 0 means the whole catalogue (up to 100,000 products per store). Requests are rate-limited to one per second per store, so large catalogues take about 1 second per 250 products through the main feed. Above Shopify's 25,000-product feed limit the rest is read from the public sitemap, collection feeds and product pages, which is much slower: raise the run timeout for very large stores. The audit row's catalogueComplete field says whether the whole catalogue was read.

## `readAllProductDetails` (type: `boolean`):

Off: barcodes, image alt text, weight units and inventory fields are read for the audit sample only (detailSampleSize). On: each product's own public JSON is read too, so the barcode check (missing, invalid or duplicate barcodes) covers every variant of every product read, the GTIN and alt-text checks cover every product, and every row gets barcodes, alt text, weights and inventory fields where the store publishes them. Costs one extra request per product (about 1 second each, more if the store's robots.txt asks for a Crawl-delay), so raise the run timeout for big catalogues; products not reached in time are counted in the note. No extra charge.

## `detailSampleSize` (type: `integer`):

Barcodes and image alt text are only in each product's own JSON, so the Actor fetches that for a sample of products spread evenly across the catalogue and measures GTIN and alt-text gaps on that sample. Each sampled product costs one extra request (about 1 second). 0 turns these two checks off.

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

Timeout for each request. Failed, rate-limited or timed-out requests are retried up to 3 times with backoff.

## Actor input object example

```json
{
  "stores": [
    "partakefoods.com",
    "drinkolipop.com",
    "beardbrand.com"
  ],
  "includeCatalogueRows": true,
  "includeAudit": true,
  "maxProductsPerStore": 250,
  "readAllProductDetails": false,
  "detailSampleSize": 20,
  "requestTimeoutSecs": 15
}
```

# Actor output Schema

## `products` (type: `string`):

All dataset rows (recordType product, store-audit or store). Opens on the Products view.

## `audits` (type: `string`):

One row per store: data-quality percentages, 0-100 readiness score, score breakdown and top issues.

## `barcodeIssues` (type: `string`):

BARCODE-ISSUES: one line per variant with a missing, invalid or duplicate barcode (store, product, handle, product URL, variant, options, SKU, barcode, status). Covers the variants checked: every variant of every product read with readAllProductDetails on, otherwise the detail sample.

## `status` (type: `string`):

Stores that could not be read (not_shopify, shopify_products_unavailable, blocked, blocked_by_robots, no_products, duplicate, invalid_input, error).

## `summary` (type: `string`):

SUMMARY record: status, product count, readiness score, requests and timing per 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 = {
    "stores": [
        "partakefoods.com",
        "drinkolipop.com",
        "beardbrand.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("yourevenings/shopify-catalogue-audit").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 = { "stores": [
        "partakefoods.com",
        "drinkolipop.com",
        "beardbrand.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("yourevenings/shopify-catalogue-audit").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 '{
  "stores": [
    "partakefoods.com",
    "drinkolipop.com",
    "beardbrand.com"
  ]
}' |
apify call yourevenings/shopify-catalogue-audit --silent --output-dataset

```

## MCP server setup

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

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/bWDE937LvMO6bHZQK/builds/Zhf9eUmkbSTbyzxae/openapi.json
