# Shopify Collection Revenue Exposure Audit (`practical_ai_lab_jp/pal-shopify-collection-revenue-exposure-audit`) Actor

Compare two dated Shopify collection snapshots with your sales weights to quantify delistings and sold-out flags.

- **URL**: https://apify.com/practical_ai_lab_jp/pal-shopify-collection-revenue-exposure-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 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 Collection Revenue Exposure Audit

Compare two dated snapshots of a Shopify collection and your own product sales weights to see how much of your historical sales weight sits on products that were **delisted** or are now **published as sold out**. The Actor also tells you which affected products have **no weight** and why on-hand stock stays **unknown**.

### What it does

1. Reads a baseline and a current product list. Each side is either inline JSON or a public `products.json` collection feed.
2. Matches products by handle and classifies each baseline product as `delisted`, `newly_sold_out`, `still_sold_out` or `restocked`.
3. Adds up your supplied weights (units sold, revenue, or any non-negative measure) for the affected products.
4. Reports the new exposure and its share of the weighted baseline total, plus warnings about missing weights, truncated feeds and small samples.

This is not a general product scraper. It does not export catalog data; it produces an audit of change between two dates.

### Sample input

```json
{
  "baselineDate": "2026-10-01",
  "currentDate": "2026-11-01",
  "baselineProducts": [
    {"handle": "trail-bottle", "title": "Trail Bottle", "variants": [{"available": true}]},
    {"handle": "canvas-tote", "title": "Canvas Tote", "variants": [{"available": true}]},
    {"handle": "desk-lamp", "title": "Desk Lamp", "variants": [{"available": true}]},
    {"handle": "wool-socks", "title": "Wool Socks", "variants": [{"available": true}]}
  ],
  "currentProducts": [
    {"handle": "trail-bottle", "title": "Trail Bottle", "variants": [{"available": true}]},
    {"handle": "canvas-tote", "title": "Canvas Tote", "variants": [{"available": false}]},
    {"handle": "wool-socks", "title": "Wool Socks", "variants": [{"available": true}]}
  ],
  "salesWeights": [
    {"handle": "trail-bottle", "weight": 400},
    {"handle": "canvas-tote", "weight": 250},
    {"handle": "desk-lamp", "weight": 120}
  ],
  "weightUnit": "units_90d"
}
```

Here `canvas-tote` is newly sold out (250), `desk-lamp` is delisted (120) and `wool-socks` is unaffected but has no weight. New exposure is 370 of a weighted baseline total of 770.

#### Using live feeds

Instead of inline products, set `baselineFeedUrl` and/or `currentFeedUrl` to a public `https://<shop>/products.json` or `https://<shop>/collections/<handle>/products.json` URL and set `confirmFeedPermission` to `true`. A live URL only shows the shop as it is now, so the baseline normally comes from an earlier saved snapshot: every run stores a compact `CURRENT_SNAPSHOT` in its key-value store that you can paste into `baselineProducts` next time.

### Output

The dataset holds one `summary` item followed by one `finding` item per affected product, sorted by weight.

Key summary fields: `newExposureWeight`, `newExposureShareOfBaselineWeight`, `delistedCount`, `newlySoldOutCount`, `standingSoldOutWeight`, `missingWeightAffectedHandles`, `exposureIsLowerBound`, `hiddenStockUnknownCount`, `unmatchedWeightHandles`, `warnings` and `limitations`.

Key finding fields: `handle`, `status`, `weight`, `weightMissing`, `confidence`, `stockNote`.

### Safety and fair use

- Only `https` URLs of the form `/products.json` or `/collections/<handle>/products.json` are accepted. Credentials, custom ports, IP addresses, query strings and hosts resolving to non-public addresses are rejected, and redirects are not followed.
- `robots.txt` is checked before the feed is requested; a disallow or an unreachable robots file stops the run.
- Requests are bounded by `maxPages` (up to 10 pages of 250 products), a delay between pages, a timeout and a response size cap.
- No logins, proxies or personal data are involved.

### Pricing event

The code emits one `audit-completed` event after a run has stored its results. Failed or invalid runs emit nothing. Pricing is configured separately by the account owner.

### Limitations and sample-size caveats

- Public feeds show an availability flag per variant, not inventory counts. "Sold out" can still mean backorders are allowed, and "available" can mean one unit. Hidden stock is always reported as unknown.
- A missing product could be unpublished, deleted, removed from the collection, or beyond the page cap. Truncated feeds lower the confidence of delisting findings.
- Exposure is a share of the historical weights you provide, not predicted lost revenue. If affected products lack weights, the figure is a lower bound.
- With fewer than about 30 products or 20 weighted products, one item can dominate the share; the Actor warns in these cases.
- The robots.txt check uses Python's standard parser, which matches path prefixes and does not interpret wildcard patterns. DNS is checked before the request, but the connection resolves the host again, so a hostile DNS change between the two is not fully excluded.
- Product handles must be unchanged between snapshots; renamed handles appear as a delisting plus a new product.

### Local development

Tests run offline with the standard library: `python -m unittest discover -s tests`.

# Actor input Schema

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

Date (YYYY-MM-DD) the baseline feed was captured. Must be earlier than the current date.

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

Date (YYYY-MM-DD) the current feed was captured.

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

Earlier products.json 'products' list, or the CURRENT_SNAPSHOT saved by a previous run. Use this OR the baseline URL.

## `baselineFeedUrl` (type: `string`):

Public https URL ending in /products.json or /collections/<handle>/products.json. Use this OR inline baseline products.

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

Later products.json 'products' list. Use this OR the current feed URL.

## `currentFeedUrl` (type: `string`):

Public https collection feed to read now (robots.txt is checked). Use this OR inline current products.

## `salesWeights` (type: `array`):

List of {handle, weight} objects, e.g. units sold or revenue over a period you choose. Products without a weight are reported as missing.

## `weightUnit` (type: `string`):

Free-text label for your weights, for example units_90d or usd_revenue_90d.

## `confirmFeedPermission` (type: `boolean`):

Required only when a feed URL is used.

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

Each page holds up to 250 products. Feeds longer than this are marked truncated.

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

Pause between page requests to the same shop.

## Actor input object example

```json
{
  "baselineDate": "2026-10-01",
  "currentDate": "2026-11-01",
  "baselineProducts": [
    {
      "handle": "trail-bottle",
      "title": "Trail Bottle",
      "variants": [
        {
          "available": true
        }
      ]
    },
    {
      "handle": "canvas-tote",
      "title": "Canvas Tote",
      "variants": [
        {
          "available": true
        },
        {
          "available": true
        }
      ]
    },
    {
      "handle": "desk-lamp",
      "title": "Desk Lamp",
      "variants": [
        {
          "available": true
        }
      ]
    },
    {
      "handle": "wool-socks",
      "title": "Wool Socks",
      "variants": [
        {
          "available": true
        }
      ]
    },
    {
      "handle": "sample-mug",
      "title": "Sample Mug",
      "variants": [
        {
          "available": false
        }
      ]
    }
  ],
  "currentProducts": [
    {
      "handle": "trail-bottle",
      "title": "Trail Bottle",
      "variants": [
        {
          "available": true
        }
      ]
    },
    {
      "handle": "canvas-tote",
      "title": "Canvas Tote",
      "variants": [
        {
          "available": false
        },
        {
          "available": false
        }
      ]
    },
    {
      "handle": "wool-socks",
      "title": "Wool Socks",
      "variants": [
        {
          "available": true
        }
      ]
    },
    {
      "handle": "sample-mug",
      "title": "Sample Mug",
      "variants": [
        {
          "available": false
        }
      ]
    },
    {
      "handle": "new-scarf",
      "title": "New Scarf",
      "variants": [
        {
          "available": true
        }
      ]
    }
  ],
  "salesWeights": [
    {
      "handle": "trail-bottle",
      "weight": 400
    },
    {
      "handle": "canvas-tote",
      "weight": 250
    },
    {
      "handle": "desk-lamp",
      "weight": 120
    },
    {
      "handle": "sample-mug",
      "weight": 30
    }
  ],
  "weightUnit": "units",
  "confirmFeedPermission": false,
  "maxPages": 4,
  "requestDelaySeconds": 2
}
```

# 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",
    "currentDate": "2026-11-01",
    "baselineProducts": [
        {
            "handle": "trail-bottle",
            "title": "Trail Bottle",
            "variants": [
                {
                    "available": true
                }
            ]
        },
        {
            "handle": "canvas-tote",
            "title": "Canvas Tote",
            "variants": [
                {
                    "available": true
                },
                {
                    "available": true
                }
            ]
        },
        {
            "handle": "desk-lamp",
            "title": "Desk Lamp",
            "variants": [
                {
                    "available": true
                }
            ]
        },
        {
            "handle": "wool-socks",
            "title": "Wool Socks",
            "variants": [
                {
                    "available": true
                }
            ]
        },
        {
            "handle": "sample-mug",
            "title": "Sample Mug",
            "variants": [
                {
                    "available": false
                }
            ]
        }
    ],
    "currentProducts": [
        {
            "handle": "trail-bottle",
            "title": "Trail Bottle",
            "variants": [
                {
                    "available": true
                }
            ]
        },
        {
            "handle": "canvas-tote",
            "title": "Canvas Tote",
            "variants": [
                {
                    "available": false
                },
                {
                    "available": false
                }
            ]
        },
        {
            "handle": "wool-socks",
            "title": "Wool Socks",
            "variants": [
                {
                    "available": true
                }
            ]
        },
        {
            "handle": "sample-mug",
            "title": "Sample Mug",
            "variants": [
                {
                    "available": false
                }
            ]
        },
        {
            "handle": "new-scarf",
            "title": "New Scarf",
            "variants": [
                {
                    "available": true
                }
            ]
        }
    ],
    "salesWeights": [
        {
            "handle": "trail-bottle",
            "weight": 400
        },
        {
            "handle": "canvas-tote",
            "weight": 250
        },
        {
            "handle": "desk-lamp",
            "weight": 120
        },
        {
            "handle": "sample-mug",
            "weight": 30
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("practical_ai_lab_jp/pal-shopify-collection-revenue-exposure-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",
    "currentDate": "2026-11-01",
    "baselineProducts": [
        {
            "handle": "trail-bottle",
            "title": "Trail Bottle",
            "variants": [{ "available": True }],
        },
        {
            "handle": "canvas-tote",
            "title": "Canvas Tote",
            "variants": [
                { "available": True },
                { "available": True },
            ],
        },
        {
            "handle": "desk-lamp",
            "title": "Desk Lamp",
            "variants": [{ "available": True }],
        },
        {
            "handle": "wool-socks",
            "title": "Wool Socks",
            "variants": [{ "available": True }],
        },
        {
            "handle": "sample-mug",
            "title": "Sample Mug",
            "variants": [{ "available": False }],
        },
    ],
    "currentProducts": [
        {
            "handle": "trail-bottle",
            "title": "Trail Bottle",
            "variants": [{ "available": True }],
        },
        {
            "handle": "canvas-tote",
            "title": "Canvas Tote",
            "variants": [
                { "available": False },
                { "available": False },
            ],
        },
        {
            "handle": "wool-socks",
            "title": "Wool Socks",
            "variants": [{ "available": True }],
        },
        {
            "handle": "sample-mug",
            "title": "Sample Mug",
            "variants": [{ "available": False }],
        },
        {
            "handle": "new-scarf",
            "title": "New Scarf",
            "variants": [{ "available": True }],
        },
    ],
    "salesWeights": [
        {
            "handle": "trail-bottle",
            "weight": 400,
        },
        {
            "handle": "canvas-tote",
            "weight": 250,
        },
        {
            "handle": "desk-lamp",
            "weight": 120,
        },
        {
            "handle": "sample-mug",
            "weight": 30,
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("practical_ai_lab_jp/pal-shopify-collection-revenue-exposure-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",
  "currentDate": "2026-11-01",
  "baselineProducts": [
    {
      "handle": "trail-bottle",
      "title": "Trail Bottle",
      "variants": [
        {
          "available": true
        }
      ]
    },
    {
      "handle": "canvas-tote",
      "title": "Canvas Tote",
      "variants": [
        {
          "available": true
        },
        {
          "available": true
        }
      ]
    },
    {
      "handle": "desk-lamp",
      "title": "Desk Lamp",
      "variants": [
        {
          "available": true
        }
      ]
    },
    {
      "handle": "wool-socks",
      "title": "Wool Socks",
      "variants": [
        {
          "available": true
        }
      ]
    },
    {
      "handle": "sample-mug",
      "title": "Sample Mug",
      "variants": [
        {
          "available": false
        }
      ]
    }
  ],
  "currentProducts": [
    {
      "handle": "trail-bottle",
      "title": "Trail Bottle",
      "variants": [
        {
          "available": true
        }
      ]
    },
    {
      "handle": "canvas-tote",
      "title": "Canvas Tote",
      "variants": [
        {
          "available": false
        },
        {
          "available": false
        }
      ]
    },
    {
      "handle": "wool-socks",
      "title": "Wool Socks",
      "variants": [
        {
          "available": true
        }
      ]
    },
    {
      "handle": "sample-mug",
      "title": "Sample Mug",
      "variants": [
        {
          "available": false
        }
      ]
    },
    {
      "handle": "new-scarf",
      "title": "New Scarf",
      "variants": [
        {
          "available": true
        }
      ]
    }
  ],
  "salesWeights": [
    {
      "handle": "trail-bottle",
      "weight": 400
    },
    {
      "handle": "canvas-tote",
      "weight": 250
    },
    {
      "handle": "desk-lamp",
      "weight": 120
    },
    {
      "handle": "sample-mug",
      "weight": 30
    }
  ]
}' |
apify call practical_ai_lab_jp/pal-shopify-collection-revenue-exposure-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-collection-revenue-exposure-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/iXCzUECQWzHAiIUp8/builds/rvF3kSPlqTmdqnDgq/openapi.json
