# Shopify Merchant Diagnostics (`saimislam/shopify-merchant-diagnostics`) Actor

Diagnose Shopify feed disagreements with original HTTP offers and rendered variant, currency, price and stock evidence. Captures subscription purchase context, screenshots and load failures. Experimental beta with explicit theme selectors; unknown evidence stays inconclusive.

- **URL**: https://apify.com/saimislam/shopify-merchant-diagnostics.md
- **Developed by:** [Saim Islam](https://apify.com/saimislam) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 product diagnostics

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

Learn more: https://docs.apify.com/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 Merchant Diagnostics

Find where a supplied merchant feed row disagrees with a Shopify product page. This beta captures the original HTTP response, matches its structured offer, and samples the rendered product's price, currency, variant and purchase state in Chromium.

Use it to investigate price mismatches, sold-out pages, delayed subscription widgets and variant selection problems. The report preserves uncertainty and relevant page failures so a working browser page cannot silently become a clean verdict.

### Quick start

1. Run the example input to see the report format. The example is a saved public merchant feed row; the Actor does not fetch a feed or connect to Google Merchant Center.
2. Replace `feed` with an actual row from your exported feed. Use separate decimal `price` and ISO `currency`, plus `availability`, `link`, and `variant_id` or `sku`.
3. Configure the product's selectors and exact resource origins. Selectors are specific to the store/theme. This beta does not automatically configure arbitrary themes.
4. Open Results and Run summary. The default key-value store contains source HTML, rendered HTML, a screenshot and the complete report for each product.

No store account or Google account is required. Runs use fresh browser contexts without signed-in sessions. Start with one product and the example's 2 GB memory setting.

### Inputs

Each entry in `products` contains:

| Field | Meaning |
| --- | --- |
| `feed` | `price`, `currency`, `availability`, `link`, and `variant_id` or `sku`; optional `id`, `mpn`, `sale_price`, `sale_price_effective_date`, `selling_plan_id` |
| `selectors.root` | One product container; `body` is usable when the other selectors uniquely identify the product |
| `selectors.price` | The visible price for the selected purchase option; avoid crossed-out regular prices |
| `selectors.currency` | Optional visible ISO currency label; a bare `$` cannot establish USD |
| `selectors.variant` | The selected variant control inside the product form; required when a variant is expected |
| `selectors.purchase` | Optional bound cart button; recommended for verified rendered availability |
| `selectors.availability` | Optional explicit visible stock label |
| `selectors.variant_data` | Optional DOM JSON block declaring inventory and selling-plan allocations; script `application/json` or an explicit textarea JSON block |
| `selectors.variant_data_assignment` | Optional plain assignment identifier for JSON embedded in a script; parsing never executes the script |
| `selectors.sku`, `selectors.locale` | Optional visible SKU and number locale; default locale `en-US` |
| `allowed_origins` | 1–40 exact HTTPS origins permitted for GET/HEAD resource loading, including the product origin |
| `sample_ms` | 2–10 increasing offsets after DOMContentLoaded, between 0 and 10000 ms |
| `feed_provenance` | Optional record of where and when the supplied row was obtained |

Top-level `navigationTimeoutSeconds` is 5–60 (default 45); `maxRunSeconds` is 30–600 (default 180). A run checks 1–10 products sequentially. Input is capped at 200 KB. Choose a platform timeout at least as long as `maxRunSeconds`.

A price inside an open shadow root can use an explicit `>>>` boundary. For example, `recharge-subscription-widget >>> .rc-purchase-option:has(input[name="purchaseOption"]:checked) .rc-price:not(.strike-through)` reads the selected Recharge price. Closed shadow roots are unsupported.

`mpn` matches explicit structured-data MPN identity; it is never converted to SKU. An arbitrary feed item ID does not establish a SKU. Record any enrichment of feed identity in `feed_provenance`.

Sale windows use `start/end` ISO timestamps. Only an active `sale_price` replaces `price`. Invalid or conflicting identities fail validation. A selling-plan expectation must match the URL's `selling_plan` when both are supplied.

### Reading the result

| Result | Interpretation |
| --- | --- |
| `consistent_in_sample` | Required captured fields agree within the configured sample; unresolved load failures are absent |
| `mismatch` | At least one observed disagreement or change during loading; inspect `complete` and `unknown` too |
| `inconclusive` | Identity, a required field, capture or relevant loading evidence could not be established |
| `complete` | All required comparisons and the scoped load audit completed; this is separate from whether values agree |
| `findings` | Layer, field, expected value and observed value for each disagreement |
| `unknown` | Reasons the comparison could not be completed |
| `warnings` / `load_audit` | Positively identified out-of-scope telemetry or checkout failures, and unresolved failures that prevent completeness |

HTTP-source offers are matched by explicit variant, SKU or MPN evidence. Conflicting offers, ranges, missing currency and ambiguous selectors remain unknown. Prices are normalized decimal strings. Multiple samples expose changes; they do not establish behavior outside that time window.

#### Rendered stock and subscriptions

An enabled Add to cart button establishes orderability. Corroborated `in_stock` additionally requires the selected variant's declared Shopify-managed quantity, deny overselling policy and enough quantity for the current form. A disabled bound Sold Out button establishes `out_of_stock`. Busy or unbound controls remain unknown.

For a selected recurring subscription, the exact plan must have one allocation, declare recurring deliveries, have equal total and per-delivery price, and match the visible selected price. Prepaid plans, unknown allocations and inconsistent prices remain unsupported. Current declared variant stock does not verify future subscription deliveries.

A limited single-variant fallback supports themes that omit the sold-out option ID: one disabled Sold Out form, one disabled option without a value, and exactly one unavailable variant declared in a JSON block inside that same form, with an exact matching option title. The report labels this inferred identity. It cannot establish an enabled purchase or infer between multiple variants.

The purchase-context finding distinguishes a one-time expectation from a page that defaults to a selling plan. A subscription discount versus a regular-price expectation is not, by itself, proof that the merchant submitted an incorrect feed.

### Evidence and limits

Each product saves `source.html`, `rendered.html`, `rendered.png` and `report.json`. Dataset rows identify the record keys and original-response SHA-256. `OUTPUT` records runtime version, browser and totals. Use JSON export for full nested evidence; the table is an overview.

The Actor permits HTTPS public destinations on port 443. Each connection resolves and pins a public IP, rejects private/reserved or mixed DNS answers, and keeps normal TLS certificate verification. POST requests and WebSockets are blocked; cart, checkout, login and customer actions are not performed. Some apps require those requests and will therefore remain inconclusive. Resource origins and browser samples are recorded in the report.

Known telemetry and isolated Shopify web-pixel failures are retained as scoped warnings. Unknown scripts, HTTP failures and unclassified errors remain blocking. A completed run can contain inconclusive products: process success means evidence was stored, not that the store is healthy.

This tool does not verify Google feed ingestion, crawling, account approval, checkout prices, regional fulfillment, warehouse inventory or future availability. Feed transport is not verified by this run. Supply a current, genuine feed row for a meaningful feed comparison. Example rows and live store values can change after capture.

Version 0.3 is an experimental diagnostic. Validation covers controlled error cases and a small set of real Shopify pages; it does not establish coverage of every Shopify theme. The configured beta price is $0.02 per stored product diagnostic, including inconclusive reports, plus $0.00005 per GB of run memory for Actor start (minimum one start event). Platform usage is included in this pricing model. Inspect the current Apify pricing panel before running; pricing can change. The Actor stops starting new products when the SDK reports that another result would exceed the run charge limit. No paying-demand claim is made.

The pinned Playwright 1.62.1 build includes a narrow guard in its service-worker blocker: opaque sandbox frames already deny access to `navigator.serviceWorker`, so that SecurityError is handled while registration remains blocked. Other exceptions and store-script errors are preserved. Upgrading Playwright requires reviewing this patch.

# Actor input Schema

## `products` (type: `array`):

1–10 product checks with supplied feed values, explicit selectors and exact allowed origins. The example is a captured public merchant feed row; its values can become stale.

## `navigationTimeoutSeconds` (type: `integer`):

Maximum wait for DOMContentLoaded; 5–60 seconds.

## `maxRunSeconds` (type: `integer`):

Whole-run budget including evidence storage; 30–600 seconds.

## Actor input object example

```json
{
  "products": [
    {
      "feed": {
        "id": "7953688035491_43213374390435",
        "price": "25.00",
        "currency": "USD",
        "availability": "out of stock",
        "variant_id": "43213374390435",
        "link": "https://www.rykergames.com/products/catan-card-sleeve-kit?variant=43213374390435&currency=USD",
        "mpn": "BK0013"
      },
      "selectors": {
        "root": "body",
        "price": "#ProductPrice-7953688035491",
        "currency": "button[aria-controls=\"CurrencyList-toolbar\"] .disclosure-list__label",
        "availability": "#AddToCartText-7953688035491",
        "purchase": "#AddToCart-7953688035491",
        "variant": "#AddToCartForm-7953688035491 [name=\"id\"]",
        "locale": "en-US",
        "variant_data": "#AddToCartForm-7953688035491 #VariantsJson-7953688035491"
      },
      "allowed_origins": [
        "https://www.rykergames.com",
        "https://cdn.shopify.com",
        "https://fonts.shopifycdn.com",
        "https://ajax.googleapis.com",
        "https://loox.io",
        "https://maps.googleapis.com",
        "https://maps.gstatic.com",
        "https://productreviews.shopifycdn.com",
        "https://shop.app",
        "https://monorail-edge.shopifysvc.com",
        "https://otlp-http-production.shopifysvc.com",
        "https://error-analytics-sessions-production.shopifysvc.com",
        "https://extensions.shopifycdn.com",
        "https://connect.facebook.net",
        "https://www.googletagmanager.com",
        "https://cf.geekdo-images.com",
        "https://fonts.loox.io",
        "https://images.loox.io",
        "https://static.klaviyo.com",
        "https://cdn.nfcube.com",
        "https://fonts.gstatic.com"
      ],
      "sample_ms": [
        3000,
        5000,
        8000,
        10000
      ],
      "feed_provenance": {
        "url": "https://www.rykergames.com/collections/google-xml-product-feed",
        "final_url": "https://www.rykergames.com/collections/google-xml-product-feed",
        "status": 200,
        "started_at": "2026-10-01T08:00:57.580638+00:00",
        "captured_at": "2026-10-01T08:01:08.118411+00:00",
        "sha256": "3825c5dbdf39965527ff92e8b75397c3360a2f1bd70a1fdbc4803637438f603f",
        "bytes": 274232,
        "type": "captured_public_google_rss_feed",
        "original_product_link": "https://www.rykergames.com/products/catan-card-sleeve-kit",
        "note": "Genuine merchant-published row. Variant cross-checked with product API; explicit variant and USD query appended. No Google ingestion verification. MPN retained as MPN; source matches explicit Product.mpn, without converting it to SKU."
      }
    }
  ],
  "navigationTimeoutSeconds": 45,
  "maxRunSeconds": 180
}
```

# Actor output Schema

## `results` (type: `string`):

No description

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

No description

## `evidence` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

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

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "products": [
        {
            "feed": {
                "id": "7953688035491_43213374390435",
                "price": "25.00",
                "currency": "USD",
                "availability": "out of stock",
                "variant_id": "43213374390435",
                "link": "https://www.rykergames.com/products/catan-card-sleeve-kit?variant=43213374390435&currency=USD",
                "mpn": "BK0013"
            },
            "selectors": {
                "root": "body",
                "price": "#ProductPrice-7953688035491",
                "currency": "button[aria-controls=\"CurrencyList-toolbar\"] .disclosure-list__label",
                "availability": "#AddToCartText-7953688035491",
                "purchase": "#AddToCart-7953688035491",
                "variant": "#AddToCartForm-7953688035491 [name=\"id\"]",
                "locale": "en-US",
                "variant_data": "#AddToCartForm-7953688035491 #VariantsJson-7953688035491"
            },
            "allowed_origins": [
                "https://www.rykergames.com",
                "https://cdn.shopify.com",
                "https://fonts.shopifycdn.com",
                "https://ajax.googleapis.com",
                "https://loox.io",
                "https://maps.googleapis.com",
                "https://maps.gstatic.com",
                "https://productreviews.shopifycdn.com",
                "https://shop.app",
                "https://monorail-edge.shopifysvc.com",
                "https://otlp-http-production.shopifysvc.com",
                "https://error-analytics-sessions-production.shopifysvc.com",
                "https://extensions.shopifycdn.com",
                "https://connect.facebook.net",
                "https://www.googletagmanager.com",
                "https://cf.geekdo-images.com",
                "https://fonts.loox.io",
                "https://images.loox.io",
                "https://static.klaviyo.com",
                "https://cdn.nfcube.com",
                "https://fonts.gstatic.com"
            ],
            "sample_ms": [
                3000,
                5000,
                8000,
                10000
            ],
            "feed_provenance": {
                "url": "https://www.rykergames.com/collections/google-xml-product-feed",
                "final_url": "https://www.rykergames.com/collections/google-xml-product-feed",
                "status": 200,
                "started_at": "2026-10-01T08:00:57.580638+00:00",
                "captured_at": "2026-10-01T08:01:08.118411+00:00",
                "sha256": "3825c5dbdf39965527ff92e8b75397c3360a2f1bd70a1fdbc4803637438f603f",
                "bytes": 274232,
                "type": "captured_public_google_rss_feed",
                "original_product_link": "https://www.rykergames.com/products/catan-card-sleeve-kit",
                "note": "Genuine merchant-published row. Variant cross-checked with product API; explicit variant and USD query appended. No Google ingestion verification. MPN retained as MPN; source matches explicit Product.mpn, without converting it to SKU."
            }
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("saimislam/shopify-merchant-diagnostics").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 = { "products": [{
            "feed": {
                "id": "7953688035491_43213374390435",
                "price": "25.00",
                "currency": "USD",
                "availability": "out of stock",
                "variant_id": "43213374390435",
                "link": "https://www.rykergames.com/products/catan-card-sleeve-kit?variant=43213374390435&currency=USD",
                "mpn": "BK0013",
            },
            "selectors": {
                "root": "body",
                "price": "#ProductPrice-7953688035491",
                "currency": "button[aria-controls=\"CurrencyList-toolbar\"] .disclosure-list__label",
                "availability": "#AddToCartText-7953688035491",
                "purchase": "#AddToCart-7953688035491",
                "variant": "#AddToCartForm-7953688035491 [name=\"id\"]",
                "locale": "en-US",
                "variant_data": "#AddToCartForm-7953688035491 #VariantsJson-7953688035491",
            },
            "allowed_origins": [
                "https://www.rykergames.com",
                "https://cdn.shopify.com",
                "https://fonts.shopifycdn.com",
                "https://ajax.googleapis.com",
                "https://loox.io",
                "https://maps.googleapis.com",
                "https://maps.gstatic.com",
                "https://productreviews.shopifycdn.com",
                "https://shop.app",
                "https://monorail-edge.shopifysvc.com",
                "https://otlp-http-production.shopifysvc.com",
                "https://error-analytics-sessions-production.shopifysvc.com",
                "https://extensions.shopifycdn.com",
                "https://connect.facebook.net",
                "https://www.googletagmanager.com",
                "https://cf.geekdo-images.com",
                "https://fonts.loox.io",
                "https://images.loox.io",
                "https://static.klaviyo.com",
                "https://cdn.nfcube.com",
                "https://fonts.gstatic.com",
            ],
            "sample_ms": [
                3000,
                5000,
                8000,
                10000,
            ],
            "feed_provenance": {
                "url": "https://www.rykergames.com/collections/google-xml-product-feed",
                "final_url": "https://www.rykergames.com/collections/google-xml-product-feed",
                "status": 200,
                "started_at": "2026-10-01T08:00:57.580638+00:00",
                "captured_at": "2026-10-01T08:01:08.118411+00:00",
                "sha256": "3825c5dbdf39965527ff92e8b75397c3360a2f1bd70a1fdbc4803637438f603f",
                "bytes": 274232,
                "type": "captured_public_google_rss_feed",
                "original_product_link": "https://www.rykergames.com/products/catan-card-sleeve-kit",
                "note": "Genuine merchant-published row. Variant cross-checked with product API; explicit variant and USD query appended. No Google ingestion verification. MPN retained as MPN; source matches explicit Product.mpn, without converting it to SKU.",
            },
        }] }

# Run the Actor and wait for it to finish
run = client.actor("saimislam/shopify-merchant-diagnostics").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 '{
  "products": [
    {
      "feed": {
        "id": "7953688035491_43213374390435",
        "price": "25.00",
        "currency": "USD",
        "availability": "out of stock",
        "variant_id": "43213374390435",
        "link": "https://www.rykergames.com/products/catan-card-sleeve-kit?variant=43213374390435&currency=USD",
        "mpn": "BK0013"
      },
      "selectors": {
        "root": "body",
        "price": "#ProductPrice-7953688035491",
        "currency": "button[aria-controls=\\"CurrencyList-toolbar\\"] .disclosure-list__label",
        "availability": "#AddToCartText-7953688035491",
        "purchase": "#AddToCart-7953688035491",
        "variant": "#AddToCartForm-7953688035491 [name=\\"id\\"]",
        "locale": "en-US",
        "variant_data": "#AddToCartForm-7953688035491 #VariantsJson-7953688035491"
      },
      "allowed_origins": [
        "https://www.rykergames.com",
        "https://cdn.shopify.com",
        "https://fonts.shopifycdn.com",
        "https://ajax.googleapis.com",
        "https://loox.io",
        "https://maps.googleapis.com",
        "https://maps.gstatic.com",
        "https://productreviews.shopifycdn.com",
        "https://shop.app",
        "https://monorail-edge.shopifysvc.com",
        "https://otlp-http-production.shopifysvc.com",
        "https://error-analytics-sessions-production.shopifysvc.com",
        "https://extensions.shopifycdn.com",
        "https://connect.facebook.net",
        "https://www.googletagmanager.com",
        "https://cf.geekdo-images.com",
        "https://fonts.loox.io",
        "https://images.loox.io",
        "https://static.klaviyo.com",
        "https://cdn.nfcube.com",
        "https://fonts.gstatic.com"
      ],
      "sample_ms": [
        3000,
        5000,
        8000,
        10000
      ],
      "feed_provenance": {
        "url": "https://www.rykergames.com/collections/google-xml-product-feed",
        "final_url": "https://www.rykergames.com/collections/google-xml-product-feed",
        "status": 200,
        "started_at": "2026-10-01T08:00:57.580638+00:00",
        "captured_at": "2026-10-01T08:01:08.118411+00:00",
        "sha256": "3825c5dbdf39965527ff92e8b75397c3360a2f1bd70a1fdbc4803637438f603f",
        "bytes": 274232,
        "type": "captured_public_google_rss_feed",
        "original_product_link": "https://www.rykergames.com/products/catan-card-sleeve-kit",
        "note": "Genuine merchant-published row. Variant cross-checked with product API; explicit variant and USD query appended. No Google ingestion verification. MPN retained as MPN; source matches explicit Product.mpn, without converting it to SKU."
      }
    }
  ]
}' |
apify call saimislam/shopify-merchant-diagnostics --silent --output-dataset

```

## MCP server setup

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

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/AghmPddc15tH551TX/builds/zvzrJ0iw2zmF6xcIS/openapi.json
