# Supplier Catalog Price Change Audit (`anthonyyrosas/supplier-catalog-price-audit`) Actor

Compare supplier CSV or JSON catalogs by SKU. Find exact price changes, added/removed products and duplicate warnings. The dataset is a summary; get full SKU details from the run's key-value store record OUTPUT.

- **URL**: https://apify.com/anthonyyrosas/supplier-catalog-price-audit.md
- **Developed by:** [Anthony Rosas](https://apify.com/anthonyyrosas) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.25 / completed catalog audit

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Supplier Catalog Price Change Audit

Compare two supplier catalog exports by SKU and review price increases, price decreases, added products, removed products, and records that need attention.

This tool is designed for a recurring task: a supplier sends an updated price list, and you need to understand what changed before updating your own catalog. It uses explicit identifiers and decimal arithmetic. It does not guess product matches or update your store.

$0.25 per completed audit, including platform usage. See the live Pricing tab.

A completed audit is charged once when paid pricing is enabled, including audits that report data-quality issues or exclude all supplied records. An input-validation failure that prevents an audit from being produced does not emit a completed-audit charge. Review the issues and excluded-record totals alongside the changes.

### Quick start

Supply the previous and current CSV contents, including a header row. The default column names are `sku` and `price`.

```json
{
  "beforeCsv": "sku,price\n00123,12.50\n00456,8.00\nOLD-1,5.00\nDUP-1,1.00\nDUP-1,2.00\n",
  "afterCsv": "sku,price\n00123,13.25\n00456,7.50\nNEW-1,4.00\nDUP-1,1.50\n",
  "currency": "USD"
}
```

This example contains:

- SKU `00123`: price increases by `0.75`, or 6%.
- SKU `00456`: price decreases by `0.50`, or 6.25%.
- `NEW-1`: added to the current file.
- `OLD-1`: absent from the current file.
- `DUP-1`: ambiguous because the previous file contains two rows with that SKU. Its records need review; the audit does not select a price arbitrarily.

Leading zeros in `00123` and `00456` are part of the identifiers.

You can also supply arrays of objects. Use strings for identifiers and prices so earlier software does not remove leading zeros or round decimal amounts.

```json
{
  "beforeRows": [
    { "sku": "00123", "price": "12.50" },
    { "sku": "00456", "price": "8.00" }
  ],
  "afterRows": [
    { "sku": "00123", "price": "13.25" },
    { "sku": "00456", "price": "7.50" }
  ],
  "currency": "USD"
}
```

Provide exactly one previous source (`beforeCsv` or `beforeRows`) and exactly one current source (`afterCsv` or `afterRows`). You can mix formats between the two sides. The first release accepts pasted or programmatically supplied contents. It does not fetch URLs, download files, or connect to supplier accounts.

### Column mapping

If both files use different names from the defaults:

```json
{
  "beforeCsv": "Product Code,Unit Cost\nA-100,19.95\n",
  "afterCsv": "Product Code,Unit Cost\nA-100,20.45\n",
  "skuColumn": "Product Code",
  "priceColumn": "Unit Cost",
  "currency": "USD"
}
```

If names differ between the two exports, use the corresponding overrides:

| Input | Purpose | Default |
|---|---|---|
| `skuColumn` | Shared identifier column | `sku` |
| `priceColumn` | Shared price column | `price` |
| `beforeSkuColumn`, `afterSkuColumn` | Identifier column for one side | Shared identifier column |
| `beforePriceColumn`, `afterPriceColumn` | Price column for one side | Shared price column |
| `currency` | Currency label attached to the audit; does not convert values | `USD` |
| `delimiter` | CSV separator: `,`, `;`, or a tab character (`\t` in JSON) | `,` |
| `decimalSeparator` | Decimal point convention: `.` or `,` | `.` |
| `trimSkus` | Ignore leading and trailing whitespace around identifiers | `true` |
| `caseSensitiveSkus` | Treat uppercase and lowercase identifiers as distinct | `true` |

Trimming or ignoring case can make two previously distinct identifiers collide. Such collisions are reported as duplicate identifiers rather than silently merged. Header names must match the column mapping you provide.

### Reading the audit

The run's key-value store contains:

| Record | Contents |
|---|---|
| `OUTPUT` | JSON object with `summary`, `changes`, and `issues`. |
| `changes.csv` | Downloadable product comparison report. |
| `issues.csv` | Downloadable issues report. |

The Actor also writes an audit summary row to the default dataset, including when no prices changed or all records need review. The complete JSON and CSV reports remain in the key-value store. Downloadable CSV reports are intended for review in spreadsheet applications; formula-like text, including negative delta strings, receives a leading apostrophe. Use the canonical JSON values for numeric processing.

When importing a CSV into a spreadsheet, set the SKU column to text so the spreadsheet preserves leading zeros. The JSON report retains SKU strings without relying on spreadsheet import settings.

Each change record contains `status`, `sku`, `oldPrice`, `newPrice`, `deltaAmount`, `deltaPercent`, `beforeRow`, `afterRow`, and `currency`. Added and removed records have null deltas. Monetary values are strings with unnecessary trailing zeros removed. For example, a change from `12.50` to `13.25` has `deltaAmount` equal to `"0.75"`.

The summary reports `beforeRows`, `afterRows`, `beforeValidRows`, `afterValidRows`, `comparedSkus`, `unchanged`, `increased`, `decreased`, `added`, `removed`, `excludedSkus`, `excludedRows`, `issueCount`, and `hasIssues`, plus `currency` and `priceScale`. `comparedSkus` includes valid matches whose prices are unchanged. `excludedSkus` counts identifiable excluded keys; missing identifiers contribute to `excludedRows` only.

Issues include a `code`, `severity`, `source`, `sku`, `row`, `rows`, and `message`. Issue codes are `MISSING_SKU`, `INVALID_PRICE`, `DUPLICATE_SKU`, and `ZERO_BASELINE`. Row numbers refer to logical data records with a header offset: the first data record is row 2 for both CSV and arrays. They are not physical line numbers when a CSV contains blank lines or quoted multiline values.

Price differences use exact decimal amounts with up to six fractional digits. Percentage changes are derived from the previous price and rounded to two decimal places, with halves rounded up in magnitude. When that price is zero, `deltaPercent` is null and a `ZERO_BASELINE` warning explains why.

Duplicate or missing identifiers cannot be confidently matched. Invalid prices are reported as issues rather than treated as zero. Check the issues and excluded-record counts alongside the price report before acting on the results.

An item missing from the new export is **absent from that export**. It is not proof that the supplier discontinued it; an incomplete export can produce the same result.

### Repeat the workflow

1. Keep the previous complete supplier export.
2. Obtain the current export using the same scope and price convention.
3. Submit both contents with the saved column mapping.
4. Review issues and price changes before making downstream changes.
5. Save the new export as the baseline for the next comparison.

The Actor does not retain a managed baseline between runs. Your existing automation supplies both versions each time. Reports stay in the run's Apify storage and follow that account's storage and retention settings.

#### Run from an automation or AI agent

Use the Actor name `anthonyyrosas/supplier-catalog-price-audit`. Supply both complete snapshots on every call; rerunning unchanged saved input compares the same files again. Set a maximum run cost of $0.25 and wait for the run to succeed before retrieving its reports.

For a Node.js workflow using the official `apify-client` package, set your own `APIFY_TOKEN` environment variable and read the two local CSV files:

```javascript
import { readFile } from 'node:fs/promises';
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const input = {
  beforeCsv: await readFile('previous.csv', 'utf8'),
  afterCsv: await readFile('current.csv', 'utf8'),
  currency: 'USD',
};
const run = await client.actor('anthonyyrosas/supplier-catalog-price-audit')
  .call(input, { build: 'latest', memory: 512, timeout: 60,
    maxTotalChargeUsd: 0.25 });
if (run.status !== 'SUCCEEDED') throw new Error(`Audit ${run.id}: ${run.status}`);
const report = await client.keyValueStore(run.defaultKeyValueStoreId)
  .getRecord('OUTPUT');
if (!report) throw new Error(`Audit ${run.id}: report unavailable`);
const { summary, changes, issues } = report.value;
console.log(JSON.stringify({ summary, changes, issues }, null, 2));
```

The default dataset contains **summary counts only**. To answer which SKUs changed, retrieve `OUTPUT` from that run's default key-value store. AI clients using Apify MCP can inspect the Actor with `fetch-actor-details` and execute it with `call-actor`. With the relevant run and storage tools enabled, use `get-actor-run` to obtain `defaultKeyValueStoreId`, then `get-key-value-store-record` for `OUTPUT`. A dataset-only preview is insufficient for individual SKU details. Authentication, supplier data access and spending permission come from the caller's own account.

Keep your API token in your automation's secret settings. Schedule the surrounding workflow at the supplier's actual update cadence, then review exclusions before making changes to a store.

### Scope and limits

- Maximum **10,000 combined data records** across the previous and current inputs, subject to the **5 MiB combined input limit** (5,242,880 UTF-8 bytes). Arrays are measured as their JSON representation.
- At most **200 columns** per row and **65,536 characters** per header or text field.
- UTF-8 CSV contents or arrays of flat objects; no Excel workbooks, PDFs, OCR, URLs, or external API connections.
- One currency and one price basis per audit. Compare equivalent units, tax treatment, and pack sizes. The currency field labels values and does not verify or convert them.
- Nonnegative plain decimal prices, at most six fractional digits and 24 integer digits. Surrounding whitespace and a leading `+` are accepted. Use the declared decimal separator for text prices; JSON numeric prices use the standard dot. Remove currency symbols, thousands separators and exponent notation in the source export.
- Matching by one explicitly selected identifier, with no fuzzy matching or cross-supplier product reconciliation.
- Reports explain the supplied data. They do not certify Shopify import compatibility or write changes to any store.

For a useful issue report, provide a small synthetic or redacted example and the input settings. Avoid publishing a private supplier price list in a public support thread.

# Actor input Schema

## `beforeCsv` (type: `string`):

Previous catalog as UTF-8 CSV text. Supply this or beforeRows.

## `afterCsv` (type: `string`):

Current catalog as UTF-8 CSV text. Supply this or afterRows.

## `beforeRows` (type: `array`):

Alternative to beforeCsv. Keep SKU and price values as strings to preserve leading zeros and exact decimal values.

## `afterRows` (type: `array`):

Alternative to afterCsv. Keep SKU and price values as strings.

## `skuColumn` (type: `string`):

Common column name identifying each product.

## `priceColumn` (type: `string`):

Common column name containing the product price.

## `beforeSkuColumn` (type: `string`):

Optional SKU column name for the previous catalog.

## `afterSkuColumn` (type: `string`):

Optional SKU column name for the current catalog.

## `beforePriceColumn` (type: `string`):

Optional price column name for the previous catalog.

## `afterPriceColumn` (type: `string`):

Optional price column name for the current catalog.

## `currency` (type: `string`):

Three-letter currency label applied to reports; no currency conversion or detection is performed.

## `delimiter` (type: `string`):

Both CSV inputs use this field separator. JSON row inputs ignore it.

## `decimalSeparator` (type: `string`):

Price decimal separator. Thousands separators and currency symbols are rejected.

## `trimSkus` (type: `boolean`):

Remove outer whitespace from SKUs before comparison.

## `caseSensitiveSkus` (type: `boolean`):

Treat ABC and abc as different products. Turn off only if your source uses case-insensitive identifiers.

## Actor input object example

```json
{
  "beforeCsv": "sku,price\n001-A,10.00\n002-B,25.00\n003-C,8.50",
  "afterCsv": "sku,price\n001-A,11.25\n002-B,24.50\n004-D,9.99",
  "skuColumn": "sku",
  "priceColumn": "price",
  "currency": "USD",
  "delimiter": ",",
  "decimalSeparator": ".",
  "trimSkus": true,
  "caseSensitiveSkus": true
}
```

# Actor output Schema

## `audit` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `issues` (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 = {
    "beforeCsv": `sku,price
001-A,10.00
002-B,25.00
003-C,8.50`,
    "afterCsv": `sku,price
001-A,11.25
002-B,24.50
004-D,9.99`
};

// Run the Actor and wait for it to finish
const run = await client.actor("anthonyyrosas/supplier-catalog-price-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 = {
    "beforeCsv": """sku,price
001-A,10.00
002-B,25.00
003-C,8.50""",
    "afterCsv": """sku,price
001-A,11.25
002-B,24.50
004-D,9.99""",
}

# Run the Actor and wait for it to finish
run = client.actor("anthonyyrosas/supplier-catalog-price-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 '{
  "beforeCsv": "sku,price\\n001-A,10.00\\n002-B,25.00\\n003-C,8.50",
  "afterCsv": "sku,price\\n001-A,11.25\\n002-B,24.50\\n004-D,9.99"
}' |
apify call anthonyyrosas/supplier-catalog-price-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,anthonyyrosas/supplier-catalog-price-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/xmoZurFI9oXh6mZcX/builds/txSkOUqsZr7VccS31/openapi.json
