# Shopify Variant Listing Drift Audit (`practical_ai_lab_jp/pal-shopify-variant-listing-drift-audit`) Actor

Compare two dated Shopify variant catalogs for title, barcode, size-color option and published-flag drift, with a partial-coverage warning.

- **URL**: https://apify.com/practical_ai_lab_jp/pal-shopify-variant-listing-drift-audit.md
- **Developed by:** [Practical AI Lab](https://apify.com/practical_ai_lab_jp) (community)
- **Categories:** E-commerce, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 drift audit completeds

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 Variant Listing Drift Audit

Compare two dated Shopify catalog captures and get a row-by-row list of what drifted at product and variant level, plus a summary with a partial-coverage warning.

### What it checks

| Area | Findings |
| --- | --- |
| Product title | `title_changed` (whitespace noise ignored) |
| Barcode completeness | `barcode_added`, `barcode_removed`, `barcode_changed`, plus completeness percentage per snapshot |
| Size and color options | `option_names_changed`, `option_value_changed` (labelled `size:` / `color:` / `other:`) |
| Published flags | `published_changed` (variant `published` if present, otherwise the product `published_at`); `availability_changed` is reported separately as stock status |
| Structure | `product_added`, `product_removed`, `variant_added`, `variant_removed` |

Every run also writes one `summary` row (first in the dataset) and an `OUTPUT` record with coverage status, warnings, change counts, severity counts and barcode completeness before/after.

### Input

- **baselineDate** and **baselineProducts**: an earlier capture in Shopify `products.json` format. A past state cannot be fetched live, so the baseline is always supplied (for example saved from an earlier run or export).
- **currentProducts** (pasted) or **currentStoreUrl** (public HTTPS store root, read from its public `/products.json` feed).
- Limits: `maxProducts`, `maxPages`, `requestDelaySeconds`, `maxChanges`.

Copyable sample (the prefilled input in the Console works as is):

```json
{
  "baselineDate": "2026-10-01",
  "baselineProducts": [
    {"id": 1001, "handle": "city-tee", "title": "City Tee", "published_at": "2026-09-01T00:00:00Z",
     "options": [{"name": "Size"}, {"name": "Color"}],
     "variants": [{"id": 5001, "sku": "TEE-S-BLK", "barcode": "4006381333931", "option1": "S", "option2": "Black", "available": true}]}
  ],
  "currentDate": "2026-11-01",
  "currentProducts": [
    {"id": 1001, "handle": "city-tee", "title": "City Tee - Organic Cotton", "published_at": null,
     "options": [{"name": "Size"}, {"name": "Color"}],
     "variants": [{"id": 5001, "sku": "TEE-S-BLK", "barcode": "", "option1": "S", "option2": "Black", "available": true}]}
  ]
}
```

This sample yields a title change, a removed barcode and a published-flag change.

### Output example

```json
{"recordType": "change", "changeType": "barcode_removed", "severity": "high",
 "handle": "city-tee", "sku": "TEE-S-BLK", "field": "barcode",
 "before": "4006381333931", "after": null, "possibleCoverageArtifact": false}
```

### Partial-coverage warning

`coverageStatus` is `partial` when either snapshot was cut by a limit, or when fewer than 80% of products overlap between snapshots. In that case added/removed rows are flagged `possibleCoverageArtifact` because they may reflect different slices of the catalog rather than real changes. Even without a warning, completeness cannot be proven from the data alone.

### Live fetching rules

Only a public HTTPS site root is accepted (no credentials, no custom port, no IP addresses, no private or local resolution). `robots.txt` is checked first and an unreadable or disallowing file stops the run. Requests are paged, delayed, size-capped and never follow redirects.

### Pricing and errors

The code references one pay-per-event name, `drift-audit-completed`, emitted once after a successful audit. No payable plan is configured in this repository. Input errors, robots refusals and fetch failures end the run without charging.

### Limitations

- Results are only as good as the two inputs; the Actor cannot verify that a capture is complete or accurate.
- Small catalogs (under 30 products) give indicative rates only; a warning is added.
- The public `products.json` feed may hide products, omit barcodes or reflect market-specific data. It has no per-variant publication field, so variant publication falls back to the product `published_at` unless you supply a `published` value.
- Live fetching resolves the host before connecting; it cannot fully exclude DNS changes between check and connection.
- Products are matched by Shopify id (or handle if id is absent); re-created products appear as removed plus added.
- It does not scrape marketplaces, collect personal data, or estimate revenue.

# Actor input Schema

## `baselineDate` (type: `string`):

Date (YYYY-MM-DD) when the baseline catalog was captured. Must not be later than the current snapshot date.

## `baselineProducts` (type: `array`):

Products from an earlier capture, in Shopify products.json format (a list of product objects, or the whole {"products": \[...]} object saved as one item). Each product needs an id or handle, a title, options and variants.

## `currentDate` (type: `string`):

Date (YYYY-MM-DD) of the current catalog. Leave empty to use today's UTC date.

## `currentProducts` (type: `array`):

Current products in Shopify products.json format. Used when no current store URL is given.

## `currentStoreUrl` (type: `string`):

Public HTTPS Shopify store root, for example https://example-store.com. When set, the current catalog is read from its public /products.json feed (robots.txt is checked first) and currentProducts is ignored. The baseline cannot be fetched live because past states are not available.

## `maxProducts` (type: `integer`):

Upper bound of products read per snapshot. Supplied lists longer than this are cut and flagged as partial coverage.

## `maxPages` (type: `integer`):

Upper bound of /products.json pages (250 products each) requested from the live store.

## `requestDelaySeconds` (type: `integer`):

Pause between live page requests.

## `maxChanges` (type: `integer`):

Upper bound of change rows written to the dataset. Counts in the summary always cover all changes.

## Actor input object example

```json
{
  "baselineDate": "2026-10-01",
  "baselineProducts": [
    {
      "id": 1001,
      "handle": "city-tee",
      "title": "City Tee",
      "published_at": "2026-09-01T00:00:00Z",
      "options": [
        {
          "name": "Size"
        },
        {
          "name": "Color"
        }
      ],
      "variants": [
        {
          "id": 5001,
          "sku": "TEE-S-BLK",
          "barcode": "4006381333931",
          "option1": "S",
          "option2": "Black",
          "available": true
        },
        {
          "id": 5002,
          "sku": "TEE-M-BLK",
          "barcode": "",
          "option1": "M",
          "option2": "Black",
          "available": true
        }
      ]
    },
    {
      "id": 1002,
      "handle": "canvas-tote",
      "title": "Canvas Tote",
      "published_at": "2026-09-01T00:00:00Z",
      "options": [
        {
          "name": "Color"
        }
      ],
      "variants": [
        {
          "id": 5101,
          "sku": "TOTE-NAT",
          "barcode": "5012345678900",
          "option1": "Natural",
          "available": true
        }
      ]
    }
  ],
  "currentDate": "2026-11-01",
  "currentProducts": [
    {
      "id": 1001,
      "handle": "city-tee",
      "title": "City Tee - Organic Cotton",
      "published_at": "2026-09-01T00:00:00Z",
      "options": [
        {
          "name": "Size"
        },
        {
          "name": "Colour"
        }
      ],
      "variants": [
        {
          "id": 5001,
          "sku": "TEE-S-BLK",
          "barcode": "4006381333931",
          "option1": "S",
          "option2": "Black",
          "available": true
        },
        {
          "id": 5002,
          "sku": "TEE-M-BLK",
          "barcode": "4006381333948",
          "option1": "M",
          "option2": "Black",
          "available": true
        },
        {
          "id": 5003,
          "sku": "TEE-L-BLK",
          "barcode": "",
          "option1": "L",
          "option2": "Black",
          "available": false
        }
      ]
    },
    {
      "id": 1002,
      "handle": "canvas-tote",
      "title": "Canvas Tote",
      "published_at": null,
      "options": [
        {
          "name": "Color"
        }
      ],
      "variants": [
        {
          "id": 5101,
          "sku": "TOTE-NAT",
          "barcode": "5012345678900",
          "option1": "Natural",
          "available": true
        }
      ]
    }
  ],
  "maxProducts": 500,
  "maxPages": 5,
  "requestDelaySeconds": 2,
  "maxChanges": 2000
}
```

# Actor output Schema

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

Download rows from the latest run in JSON/CSV.

# 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 = {
    "baselineDate": "2026-10-01",
    "baselineProducts": [
        {
            "id": 1001,
            "handle": "city-tee",
            "title": "City Tee",
            "published_at": "2026-09-01T00:00:00Z",
            "options": [
                {
                    "name": "Size"
                },
                {
                    "name": "Color"
                }
            ],
            "variants": [
                {
                    "id": 5001,
                    "sku": "TEE-S-BLK",
                    "barcode": "4006381333931",
                    "option1": "S",
                    "option2": "Black",
                    "available": true
                },
                {
                    "id": 5002,
                    "sku": "TEE-M-BLK",
                    "barcode": "",
                    "option1": "M",
                    "option2": "Black",
                    "available": true
                }
            ]
        },
        {
            "id": 1002,
            "handle": "canvas-tote",
            "title": "Canvas Tote",
            "published_at": "2026-09-01T00:00:00Z",
            "options": [
                {
                    "name": "Color"
                }
            ],
            "variants": [
                {
                    "id": 5101,
                    "sku": "TOTE-NAT",
                    "barcode": "5012345678900",
                    "option1": "Natural",
                    "available": true
                }
            ]
        }
    ],
    "currentDate": "2026-11-01",
    "currentProducts": [
        {
            "id": 1001,
            "handle": "city-tee",
            "title": "City Tee - Organic Cotton",
            "published_at": "2026-09-01T00:00:00Z",
            "options": [
                {
                    "name": "Size"
                },
                {
                    "name": "Colour"
                }
            ],
            "variants": [
                {
                    "id": 5001,
                    "sku": "TEE-S-BLK",
                    "barcode": "4006381333931",
                    "option1": "S",
                    "option2": "Black",
                    "available": true
                },
                {
                    "id": 5002,
                    "sku": "TEE-M-BLK",
                    "barcode": "4006381333948",
                    "option1": "M",
                    "option2": "Black",
                    "available": true
                },
                {
                    "id": 5003,
                    "sku": "TEE-L-BLK",
                    "barcode": "",
                    "option1": "L",
                    "option2": "Black",
                    "available": false
                }
            ]
        },
        {
            "id": 1002,
            "handle": "canvas-tote",
            "title": "Canvas Tote",
            "published_at": null,
            "options": [
                {
                    "name": "Color"
                }
            ],
            "variants": [
                {
                    "id": 5101,
                    "sku": "TOTE-NAT",
                    "barcode": "5012345678900",
                    "option1": "Natural",
                    "available": true
                }
            ]
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("practical_ai_lab_jp/pal-shopify-variant-listing-drift-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 = {
    "baselineDate": "2026-10-01",
    "baselineProducts": [
        {
            "id": 1001,
            "handle": "city-tee",
            "title": "City Tee",
            "published_at": "2026-09-01T00:00:00Z",
            "options": [
                { "name": "Size" },
                { "name": "Color" },
            ],
            "variants": [
                {
                    "id": 5001,
                    "sku": "TEE-S-BLK",
                    "barcode": "4006381333931",
                    "option1": "S",
                    "option2": "Black",
                    "available": True,
                },
                {
                    "id": 5002,
                    "sku": "TEE-M-BLK",
                    "barcode": "",
                    "option1": "M",
                    "option2": "Black",
                    "available": True,
                },
            ],
        },
        {
            "id": 1002,
            "handle": "canvas-tote",
            "title": "Canvas Tote",
            "published_at": "2026-09-01T00:00:00Z",
            "options": [{ "name": "Color" }],
            "variants": [{
                    "id": 5101,
                    "sku": "TOTE-NAT",
                    "barcode": "5012345678900",
                    "option1": "Natural",
                    "available": True,
                }],
        },
    ],
    "currentDate": "2026-11-01",
    "currentProducts": [
        {
            "id": 1001,
            "handle": "city-tee",
            "title": "City Tee - Organic Cotton",
            "published_at": "2026-09-01T00:00:00Z",
            "options": [
                { "name": "Size" },
                { "name": "Colour" },
            ],
            "variants": [
                {
                    "id": 5001,
                    "sku": "TEE-S-BLK",
                    "barcode": "4006381333931",
                    "option1": "S",
                    "option2": "Black",
                    "available": True,
                },
                {
                    "id": 5002,
                    "sku": "TEE-M-BLK",
                    "barcode": "4006381333948",
                    "option1": "M",
                    "option2": "Black",
                    "available": True,
                },
                {
                    "id": 5003,
                    "sku": "TEE-L-BLK",
                    "barcode": "",
                    "option1": "L",
                    "option2": "Black",
                    "available": False,
                },
            ],
        },
        {
            "id": 1002,
            "handle": "canvas-tote",
            "title": "Canvas Tote",
            "published_at": None,
            "options": [{ "name": "Color" }],
            "variants": [{
                    "id": 5101,
                    "sku": "TOTE-NAT",
                    "barcode": "5012345678900",
                    "option1": "Natural",
                    "available": True,
                }],
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("practical_ai_lab_jp/pal-shopify-variant-listing-drift-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 '{
  "baselineDate": "2026-10-01",
  "baselineProducts": [
    {
      "id": 1001,
      "handle": "city-tee",
      "title": "City Tee",
      "published_at": "2026-09-01T00:00:00Z",
      "options": [
        {
          "name": "Size"
        },
        {
          "name": "Color"
        }
      ],
      "variants": [
        {
          "id": 5001,
          "sku": "TEE-S-BLK",
          "barcode": "4006381333931",
          "option1": "S",
          "option2": "Black",
          "available": true
        },
        {
          "id": 5002,
          "sku": "TEE-M-BLK",
          "barcode": "",
          "option1": "M",
          "option2": "Black",
          "available": true
        }
      ]
    },
    {
      "id": 1002,
      "handle": "canvas-tote",
      "title": "Canvas Tote",
      "published_at": "2026-09-01T00:00:00Z",
      "options": [
        {
          "name": "Color"
        }
      ],
      "variants": [
        {
          "id": 5101,
          "sku": "TOTE-NAT",
          "barcode": "5012345678900",
          "option1": "Natural",
          "available": true
        }
      ]
    }
  ],
  "currentDate": "2026-11-01",
  "currentProducts": [
    {
      "id": 1001,
      "handle": "city-tee",
      "title": "City Tee - Organic Cotton",
      "published_at": "2026-09-01T00:00:00Z",
      "options": [
        {
          "name": "Size"
        },
        {
          "name": "Colour"
        }
      ],
      "variants": [
        {
          "id": 5001,
          "sku": "TEE-S-BLK",
          "barcode": "4006381333931",
          "option1": "S",
          "option2": "Black",
          "available": true
        },
        {
          "id": 5002,
          "sku": "TEE-M-BLK",
          "barcode": "4006381333948",
          "option1": "M",
          "option2": "Black",
          "available": true
        },
        {
          "id": 5003,
          "sku": "TEE-L-BLK",
          "barcode": "",
          "option1": "L",
          "option2": "Black",
          "available": false
        }
      ]
    },
    {
      "id": 1002,
      "handle": "canvas-tote",
      "title": "Canvas Tote",
      "published_at": null,
      "options": [
        {
          "name": "Color"
        }
      ],
      "variants": [
        {
          "id": 5101,
          "sku": "TOTE-NAT",
          "barcode": "5012345678900",
          "option1": "Natural",
          "available": true
        }
      ]
    }
  ]
}' |
apify call practical_ai_lab_jp/pal-shopify-variant-listing-drift-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,practical_ai_lab_jp/pal-shopify-variant-listing-drift-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/ZGucgBwLH06VXVVbt/builds/rL9M6ayP4GucVcIRS/openapi.json
