# Shopify Bundle Component Availability Audit (`practical_ai_lab_jp/pal-shopify-bundle-component-availability-audit`) Actor

Compare a bundle BOM with two dated Shopify variant availability exports and flag bundles blocked by missing components.

- **URL**: https://apify.com/practical_ai_lab_jp/pal-shopify-bundle-component-availability-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 bundle auditeds

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 Bundle Component Availability Audit

Give this Actor your bundle bill of materials (BOM) and two dated exports of published Shopify variant availability. It tells you which bundles are blocked by an unavailable component, which became blocked between the two dates, which recovered, and which cannot be judged because component data is missing.

It works only from **published true/false availability**. It never reads or infers private inventory quantities.

### Who it is for

Shopify merchants and operators who sell kits, gift sets or multipacks built from separate component SKUs and need a quick, repeatable check of which listings may be unsellable.

### Input

Copy the prefilled sample input in the Apify Console and replace it with your data.

| Field | Description |
| --- | --- |
| `bom` | Rows `{bundleSku, componentSku, quantity}`. Quantity is units per bundle and is shown for reference only. |
| `bomCsv` | Optional CSV text with header `bundle_sku,component_sku,quantity`; merged with `bom`. |
| `before` / `after` | Snapshot objects with optional `label`, optional ISO `capturedAt`, and exactly one source: `rows` (`sku`, `available`), `csv` (header `sku,available`) or `productsJsonUrl`. |
| `includeStableBundles` | Write bundles that are available in both snapshots to the dataset (default on). |
| `maxProductPages` | Page cap (1-10, 250 products each) for `productsJsonUrl` sources. |

Availability values accepted: `true/false`, `yes/no`, `1/0`, `available/unavailable`, `in stock/out of stock`, `sold out`.

#### Optional public feed source

`productsJsonUrl` may point to a public `https://<store-domain>/products.json`. The Actor validates the URL (https only, no credentials, no IP literals, only publicly routable addresses, no redirects), reads `robots.txt` first and stops if fetching is disallowed or robots.txt cannot be read, waits 1 second between pages, and caps pages and response size. Use only stores you are permitted to check. A live feed is a single moment in time, so save your own dated exports if you want a reliable earlier snapshot.

### Output

One dataset item per bundle, plus an `OUTPUT` record in the key-value store with counts, snapshot metadata, warnings, component coverage and limitations.

Classifications:

- `newly_blocked` (high risk): all components were available or unknown before, now at least one is unavailable.
- `still_blocked` (medium): blocked in both snapshots.
- `unverifiable` (medium): nothing unavailable, but some components are absent from the later snapshot.
- `recovered` (low): was blocked, now no component is unavailable.
- `stable_available` (none).

Each item also lists blocking, newly unavailable, newly available and unknown components, how many bundles share each blocking component, and whether the bundle's own SKU is still published as available while blocked (only when the bundle SKU appears in the later snapshot).

### Pricing event

The code records one `bundle-audited` event per bundle audited, only after results are stored. Invalid input ends the run as failed with no charge. No payable plan is configured in this repository; pricing is set separately.

### Limitations

- Availability is a published flag. A "true" does not mean stock is deep, and "false" does not say why (stock-out, hidden, or policy).
- No quantity logic: a BOM quantity of 3 is not checked against anything.
- SKUs missing from a snapshot are "unknown". Duplicate SKUs in one export are merged conservatively.
- Two snapshots give one comparison. They do not establish a trend, and short-lived changes between the dates are invisible.
- Matching is by SKU (case-insensitive, trimmed). Variants without SKUs cannot be used.
- Live feed fetches check DNS before connecting but do not pin the connection to the checked address.
- No claim is made about sales impact or demand; the output is a checklist for human review.
- Not tested against real stores at the time of writing; verify results on a small bundle set first.

### Local tests

Offline unit tests live in `tests/` (`python -m unittest discover -s tests -t .`). They use no network access.

# Actor input Schema

## `bom` (type: `array`):

Rows with bundleSku, componentSku and optional quantity (units per bundle; used for display only). Alternatively use the CSV field below.

## `bomCsv` (type: `string`):

CSV with header bundle_sku,component_sku,quantity. Used in addition to the rows field.

## `before` (type: `object`):

Object with a label, optional capturedAt (ISO date/time) and exactly one source: rows (sku, available), csv (text with header sku,available) or productsJsonUrl (public https Shopify /products.json).

## `after` (type: `object`):

Same structure as the earlier snapshot, captured later.

## `includeStableBundles` (type: `boolean`):

If off, bundles that are fully available in both snapshots are counted in the summary but not written to the dataset.

## `maxProductPages` (type: `integer`):

Only applies when a snapshot uses productsJsonUrl. Each page holds up to 250 products.

## Actor input object example

```json
{
  "bom": [
    {
      "bundleSku": "KIT-STARTER",
      "componentSku": "MUG-BLUE",
      "quantity": 1
    },
    {
      "bundleSku": "KIT-STARTER",
      "componentSku": "TEA-SAMPLER",
      "quantity": 2
    },
    {
      "bundleSku": "KIT-GIFT",
      "componentSku": "MUG-BLUE",
      "quantity": 1
    },
    {
      "bundleSku": "KIT-GIFT",
      "componentSku": "CANDLE-S",
      "quantity": 1
    },
    {
      "bundleSku": "KIT-TRAVEL",
      "componentSku": "BOTTLE-500",
      "quantity": 1
    },
    {
      "bundleSku": "KIT-TRAVEL",
      "componentSku": "POUCH-GREY",
      "quantity": 1
    }
  ],
  "before": {
    "label": "Monday export",
    "capturedAt": "2026-11-02T09:00:00Z",
    "rows": [
      {
        "sku": "MUG-BLUE",
        "available": true
      },
      {
        "sku": "TEA-SAMPLER",
        "available": true
      },
      {
        "sku": "CANDLE-S",
        "available": true
      },
      {
        "sku": "BOTTLE-500",
        "available": false
      },
      {
        "sku": "KIT-STARTER",
        "available": true
      },
      {
        "sku": "KIT-GIFT",
        "available": true
      },
      {
        "sku": "KIT-TRAVEL",
        "available": true
      }
    ]
  },
  "after": {
    "label": "Thursday export",
    "capturedAt": "2026-11-05T09:00:00Z",
    "rows": [
      {
        "sku": "MUG-BLUE",
        "available": false
      },
      {
        "sku": "TEA-SAMPLER",
        "available": true
      },
      {
        "sku": "CANDLE-S",
        "available": true
      },
      {
        "sku": "BOTTLE-500",
        "available": true
      },
      {
        "sku": "KIT-STARTER",
        "available": true
      },
      {
        "sku": "KIT-GIFT",
        "available": true
      },
      {
        "sku": "KIT-TRAVEL",
        "available": true
      }
    ]
  },
  "includeStableBundles": true,
  "maxProductPages": 4
}
```

# 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 = {
    "bom": [
        {
            "bundleSku": "KIT-STARTER",
            "componentSku": "MUG-BLUE",
            "quantity": 1
        },
        {
            "bundleSku": "KIT-STARTER",
            "componentSku": "TEA-SAMPLER",
            "quantity": 2
        },
        {
            "bundleSku": "KIT-GIFT",
            "componentSku": "MUG-BLUE",
            "quantity": 1
        },
        {
            "bundleSku": "KIT-GIFT",
            "componentSku": "CANDLE-S",
            "quantity": 1
        },
        {
            "bundleSku": "KIT-TRAVEL",
            "componentSku": "BOTTLE-500",
            "quantity": 1
        },
        {
            "bundleSku": "KIT-TRAVEL",
            "componentSku": "POUCH-GREY",
            "quantity": 1
        }
    ],
    "before": {
        "label": "Monday export",
        "capturedAt": "2026-11-02T09:00:00Z",
        "rows": [
            {
                "sku": "MUG-BLUE",
                "available": true
            },
            {
                "sku": "TEA-SAMPLER",
                "available": true
            },
            {
                "sku": "CANDLE-S",
                "available": true
            },
            {
                "sku": "BOTTLE-500",
                "available": false
            },
            {
                "sku": "KIT-STARTER",
                "available": true
            },
            {
                "sku": "KIT-GIFT",
                "available": true
            },
            {
                "sku": "KIT-TRAVEL",
                "available": true
            }
        ]
    },
    "after": {
        "label": "Thursday export",
        "capturedAt": "2026-11-05T09:00:00Z",
        "rows": [
            {
                "sku": "MUG-BLUE",
                "available": false
            },
            {
                "sku": "TEA-SAMPLER",
                "available": true
            },
            {
                "sku": "CANDLE-S",
                "available": true
            },
            {
                "sku": "BOTTLE-500",
                "available": true
            },
            {
                "sku": "KIT-STARTER",
                "available": true
            },
            {
                "sku": "KIT-GIFT",
                "available": true
            },
            {
                "sku": "KIT-TRAVEL",
                "available": true
            }
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("practical_ai_lab_jp/pal-shopify-bundle-component-availability-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 = {
    "bom": [
        {
            "bundleSku": "KIT-STARTER",
            "componentSku": "MUG-BLUE",
            "quantity": 1,
        },
        {
            "bundleSku": "KIT-STARTER",
            "componentSku": "TEA-SAMPLER",
            "quantity": 2,
        },
        {
            "bundleSku": "KIT-GIFT",
            "componentSku": "MUG-BLUE",
            "quantity": 1,
        },
        {
            "bundleSku": "KIT-GIFT",
            "componentSku": "CANDLE-S",
            "quantity": 1,
        },
        {
            "bundleSku": "KIT-TRAVEL",
            "componentSku": "BOTTLE-500",
            "quantity": 1,
        },
        {
            "bundleSku": "KIT-TRAVEL",
            "componentSku": "POUCH-GREY",
            "quantity": 1,
        },
    ],
    "before": {
        "label": "Monday export",
        "capturedAt": "2026-11-02T09:00:00Z",
        "rows": [
            {
                "sku": "MUG-BLUE",
                "available": True,
            },
            {
                "sku": "TEA-SAMPLER",
                "available": True,
            },
            {
                "sku": "CANDLE-S",
                "available": True,
            },
            {
                "sku": "BOTTLE-500",
                "available": False,
            },
            {
                "sku": "KIT-STARTER",
                "available": True,
            },
            {
                "sku": "KIT-GIFT",
                "available": True,
            },
            {
                "sku": "KIT-TRAVEL",
                "available": True,
            },
        ],
    },
    "after": {
        "label": "Thursday export",
        "capturedAt": "2026-11-05T09:00:00Z",
        "rows": [
            {
                "sku": "MUG-BLUE",
                "available": False,
            },
            {
                "sku": "TEA-SAMPLER",
                "available": True,
            },
            {
                "sku": "CANDLE-S",
                "available": True,
            },
            {
                "sku": "BOTTLE-500",
                "available": True,
            },
            {
                "sku": "KIT-STARTER",
                "available": True,
            },
            {
                "sku": "KIT-GIFT",
                "available": True,
            },
            {
                "sku": "KIT-TRAVEL",
                "available": True,
            },
        ],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("practical_ai_lab_jp/pal-shopify-bundle-component-availability-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 '{
  "bom": [
    {
      "bundleSku": "KIT-STARTER",
      "componentSku": "MUG-BLUE",
      "quantity": 1
    },
    {
      "bundleSku": "KIT-STARTER",
      "componentSku": "TEA-SAMPLER",
      "quantity": 2
    },
    {
      "bundleSku": "KIT-GIFT",
      "componentSku": "MUG-BLUE",
      "quantity": 1
    },
    {
      "bundleSku": "KIT-GIFT",
      "componentSku": "CANDLE-S",
      "quantity": 1
    },
    {
      "bundleSku": "KIT-TRAVEL",
      "componentSku": "BOTTLE-500",
      "quantity": 1
    },
    {
      "bundleSku": "KIT-TRAVEL",
      "componentSku": "POUCH-GREY",
      "quantity": 1
    }
  ],
  "before": {
    "label": "Monday export",
    "capturedAt": "2026-11-02T09:00:00Z",
    "rows": [
      {
        "sku": "MUG-BLUE",
        "available": true
      },
      {
        "sku": "TEA-SAMPLER",
        "available": true
      },
      {
        "sku": "CANDLE-S",
        "available": true
      },
      {
        "sku": "BOTTLE-500",
        "available": false
      },
      {
        "sku": "KIT-STARTER",
        "available": true
      },
      {
        "sku": "KIT-GIFT",
        "available": true
      },
      {
        "sku": "KIT-TRAVEL",
        "available": true
      }
    ]
  },
  "after": {
    "label": "Thursday export",
    "capturedAt": "2026-11-05T09:00:00Z",
    "rows": [
      {
        "sku": "MUG-BLUE",
        "available": false
      },
      {
        "sku": "TEA-SAMPLER",
        "available": true
      },
      {
        "sku": "CANDLE-S",
        "available": true
      },
      {
        "sku": "BOTTLE-500",
        "available": true
      },
      {
        "sku": "KIT-STARTER",
        "available": true
      },
      {
        "sku": "KIT-GIFT",
        "available": true
      },
      {
        "sku": "KIT-TRAVEL",
        "available": true
      }
    ]
  }
}' |
apify call practical_ai_lab_jp/pal-shopify-bundle-component-availability-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-bundle-component-availability-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/G3piA5a8MwVxgpmNS/builds/TXJFi4wfPWmeXG3gs/openapi.json
