# Supplier Catalog Cleanup (`h_murdock/supplier-catalog-cleanup`) Actor

Normalize catalog titles, match reference products, flag duplicates and review uncertain matches with evidence. CSV or JSON input; JSON, CSV and HTML output.

- **URL**: https://apify.com/h\_murdock/supplier-catalog-cleanup.md
- **Developed by:** [Gilad Ronen](https://apify.com/h_murdock) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.25 / completed catalog

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

### What does Supplier Catalog Cleanup do?

**Clean supplier product catalogs, find likely duplicates, and match products against your master catalog.** Paste CSV or JSON and receive normalized titles, suggested categories, field-level match evidence, and a separate review queue. Use it from Apify Console or through the API.

This rules-based beta is designed for wholesalers, ecommerce teams, and agencies preparing supplier data for human review. It uses no external AI service or API key. Every decision includes traceable evidence. Your source catalog is preserved; the Actor does not update your inventory.

### How to clean a supplier catalog

1. Open the **Input** tab and run the prefilled synthetic example to see the report.
2. Replace the example with your products in **Supplier catalog (JSON)**, or clear that field and paste **Supplier catalog (CSV)**. Supply one format at a time.
3. Optionally add your master catalog under **Reference catalog**. Clear the sample reference if you do not need matching.
4. Map column names if needed and supply your own category keywords. Clear the sample category rules before using a different taxonomy.
5. Start the Actor. In **Output**, inspect the summary and product decisions, then download the cleaned CSV, review queue, JSON, or interactive HTML report.
6. Review flagged products before importing any suggestions into a business system.

### What can you use it for?

- Prepare inconsistent supplier exports for inventory onboarding.
- Compare supplier products with an existing master catalog.
- Find likely duplicate products while preserving pack, size, color, and model distinctions.
- Apply your own category vocabulary through keyword rules.
- Build a recurring catalog-review workflow through Apify schedules, API calls, or integrations.

### Input fields and limits

Use `catalog` or `catalogCsv`. The optional master catalog uses `reference` or `referenceCsv`. CSV is pasted as text; remote URLs and uploaded spreadsheet files are not fetched. See the **Input** tab for all settings.

| Product field | How it is used |
| --- | --- |
| `title` | Product name; required for a usable row. |
| `id` | Your product identifier; reference IDs must be unique. |
| `brand` | Explicit brand used in matching. |
| `gtin` | GTIN-8/12/13/14 as text, including leading zeros; checksum checked. |
| `mpn` | Manufacturer part number, scoped to the same brand. |
| `sku`, `supplier` | SKU scoped to the same supplier. |
| `size`, `packSize`, `color` | Variant attributes; missing values are unknown. |
| `category` | Existing category preserved as supplied. |

Maximum: **2,000 supplier rows plus 2,000 reference rows**, 4 MB input, 50 columns per row, 500 characters per title, and 200 characters per other recognized field. Rows must be flat. Unused columns are ignored. Reports over 8 MB or any export over 9 MB require a smaller batch.

For different headers, set `catalogColumns`, for example `{"id":"Item Code","title":"Product Name","brand":"Manufacturer"}`. `referenceColumns` works the same way. CSV supports comma, semicolon, and tab delimiters, quoted fields, and UTF-8 BOM.

A small input example:

```json
{
  "catalog": [{"id":"S-1","title":"Acme Water 0.5 L","brand":"Acme"}],
  "reference": [{"id":"M-1","title":"Acme Water 500ml","brand":"Acme","category":"Beverages"}]
}
```

Category rules use `taxonomy`, for example `[{"category":"Kitchen","keywords":["mixing bowl","saucepan"]}]`. A supplied category takes precedence, followed by an accepted reference category, then keyword suggestions. Ties remain unresolved.

### Output and match evidence

The default dataset contains **one complete catalog report**, with a summary and a `rows` array. The product view displays one product per table row. Use the dedicated CSV exports for a flat spreadsheet.

| Output field | Meaning |
| --- | --- |
| `originalTitle`, `normalizedTitle` | Source text and normalized product title. |
| `status` | `matched`, `review`, `new`, or `invalid`. |
| `matchedReferenceId` | Accepted reference suggestion, when unambiguous. |
| `candidates` | Up to three candidate matches with evidence and conflicts. |
| `duplicates`, `duplicateCount` | Up to five duplicate examples and the full count. |
| `category` | Category value, source, and supporting keywords. |
| `needsReview`, `reviewReasons` | Whether and why a product needs review. |

Download **catalog-cleaned.csv**, **review-queue.csv**, **OUTPUT** (full JSON), and **report.html**. The HTML report supports search, filtering, evidence inspection, and downloads without network requests. Download and open it locally if Console serves it as an attachment. Spreadsheet formula-like strings are escaped in CSV; JSON preserves the original values.

The prefilled 12-row synthetic example produces 3 reference matches, 1 potential duplicate pair, and 8 rows needing review. A matched product can still need review because it has a potential duplicate.

### How does product matching work?

Normalization handles Unicode, spacing, selected spelling variants, and common units such as 0.5 L and 500 ml. Matching checks validated GTIN, brand plus MPN, supplier plus SKU, or identical title tokens with an explicit brand. Conflicting variant attributes, missing comparison attributes, and competing identifiers prevent automatic acceptance.

Fuzzy title overlap ranks candidates but never accepts a match on its own. Scores are ranking heuristics, not probabilities. Duplicate suggestions are direct pairs; products are never merged automatically. Category suggestions use your keyword rules.

### How much does catalog cleanup cost?

The launch price is **$0.25 per completed catalog**, within the limits above. One `catalog-processed` event covers the whole report; there is no separate charge per product, candidate, category, or duplicate. The pricing panel is the authoritative source for current prices and any startup fee.

Invalid input is rejected before a catalog event is charged. A configured startup fee may still apply. A new run is a new billable catalog, even for identical input. Resuming the same run reuses its verified result and recorded charge. Platform costs are included in the event price; there is no external model bill.

### Frequently asked questions

#### Can it handle different sizes, packs, or colors?

It compares explicit attributes and recognizes common unit, pack, and English color expressions. Provide structured attributes for best results. Product-specific variants beyond the supported fields may need manual review.

#### Does “new” mean a product is unique?

No. It means no plausible reference candidate was found, or no reference was supplied. It is not a global uniqueness check.

#### Does it use AI or translate product names?

This version uses deterministic rules. It preserves Unicode text but does not translate, interpret images, infer brands, or provide semantic matching across languages. Some spelling variations and near matches will be missed.

#### Is it ready for automatic inventory updates?

It is a human-review aid. Suggestions have not been benchmarked on customer catalogs. Validate a representative sample before relying on them. The report does not record approval decisions or perform inventory updates.

#### Where is my catalog stored?

Input and output are stored in your Apify run storage under its access and retention settings. The Actor uses only its run's storage and sends no catalog data to an external AI service. Only submit data you are authorized to process.

#### How do I get help or integrate it?

Use the **Issues** tab for bugs and feature requests; include a small anonymized example and expected result. Use the **API** tab for the generated request examples. Do not post confidential catalog data in public issues.

# Actor input Schema

## `catalog` (type: `array`):

Up to 2000 flat product rows. Provide this OR Catalog CSV. Each usable row requires a title. See README for fields.

## `catalogCsv` (type: `string`):

CSV text with headers. Provide this OR Supplier catalog JSON. Max total input is 4 MB.

## `reference` (type: `array`):

Optional master catalog, up to 2000 rows. Each row needs a unique id and title.

## `referenceCsv` (type: `string`):

Optional reference CSV. Provide this OR Reference catalog JSON.

## `catalogColumns` (type: `object`):

Canonical field to input header, e.g. {"title":"Product Name","id":"Item Code"}. Unmapped fields use canonical names.

## `referenceColumns` (type: `object`):

Canonical field to input header mapping for reference rows.

## `csvDelimiter` (type: `string`):

Delimiter for both CSV inputs.

## `taxonomy` (type: `array`):

Optional \[{"category":"Kitchen","keywords":\["mixing bowl"]}]. Max 100 categories with 50 keywords each. Ties require review.

## Actor input object example

```json
{
  "catalog": [
    {
      "id": "SUP-001",
      "title": "ACME Sparkling Water 0.5 L",
      "brand": "ACME",
      "gtin": "4006381333931",
      "packSize": "1"
    },
    {
      "id": "SUP-002",
      "title": "Sparkling Water ACME 500ml",
      "brand": "ACME",
      "gtin": "4006381333931",
      "packSize": "1"
    },
    {
      "id": "SUP-003",
      "title": "ACME Sparkling Water 6 x 500 ml",
      "brand": "ACME",
      "packSize": "6"
    },
    {
      "id": "SUP-004",
      "title": "ACME Sparkling Water 1 litre",
      "brand": "ACME",
      "packSize": "1"
    },
    {
      "id": "SUP-005",
      "title": "Northstar USB-C Cable 1m black",
      "brand": "Northstar",
      "mpn": "CAB-10"
    },
    {
      "id": "SUP-006",
      "title": "Northstar USB-C Cable 1m blue",
      "brand": "Northstar",
      "mpn": "CAB-10"
    },
    {
      "id": "SUP-007",
      "title": "Lumo Adjustable Desk Lamp",
      "brand": "Lumo"
    },
    {
      "id": "SUP-008",
      "title": "Leaf Organic Chamomile Tea 20 pack",
      "brand": "Leaf"
    },
    {
      "id": "SUP-009",
      "title": "Stainless steel mixing bowl",
      "brand": "Kitchen Works"
    },
    {
      "id": "SUP-010",
      "title": "",
      "brand": "Unknown"
    },
    {
      "id": "SUP-011",
      "title": "Garden cotton gloves",
      "gtin": "12345"
    },
    {
      "id": "SUP-012",
      "title": "מחברת כחולה",
      "brand": "Paper House"
    }
  ],
  "reference": [
    {
      "id": "MASTER-WATER",
      "title": "ACME Sparkling Water 500 ml",
      "brand": "ACME",
      "gtin": "4006381333931",
      "packSize": "1",
      "category": "Beverages > Water"
    },
    {
      "id": "MASTER-CABLE",
      "title": "Northstar USB C Cable 100cm Black",
      "brand": "Northstar",
      "mpn": "CAB-10",
      "category": "Electronics > Cables"
    },
    {
      "id": "MASTER-LAMP-A",
      "title": "Lumo Adjustable Desk Lamp",
      "brand": "Lumo",
      "category": "Home > Lighting"
    },
    {
      "id": "MASTER-LAMP-B",
      "title": "Lumo Adjustable Desk Lamp",
      "brand": "Lumo",
      "category": "Home > Lighting"
    }
  ],
  "catalogColumns": {},
  "referenceColumns": {},
  "csvDelimiter": ",",
  "taxonomy": [
    {
      "category": "Beverages > Tea",
      "keywords": [
        "tea",
        "chamomile"
      ]
    },
    {
      "category": "Kitchen > Cookware",
      "keywords": [
        "mixing bowl",
        "saucepan"
      ]
    },
    {
      "category": "Garden > Accessories",
      "keywords": [
        "gloves",
        "garden"
      ]
    },
    {
      "category": "Office > Stationery",
      "keywords": [
        "notebook",
        "מחברת"
      ]
    }
  ]
}
```

# Actor output Schema

## `catalog` (type: `string`):

One dataset item: summary, rows, candidate evidence, categories and duplicate links. Primary deliverable, available even if convenience exports are interrupted.

## `json` (type: `string`):

Full structured report.

## `csv` (type: `string`):

One row per supplier product.

## `review` (type: `string`):

Input issues, ambiguous matches and potential duplicates.

## `html` (type: `string`):

Download and open the standalone HTML file if the console serves it as an attachment.

# 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 = {
    "catalog": [
        {
            "id": "SUP-001",
            "title": "ACME Sparkling Water 0.5 L",
            "brand": "ACME",
            "gtin": "4006381333931",
            "packSize": "1"
        },
        {
            "id": "SUP-002",
            "title": "Sparkling Water ACME 500ml",
            "brand": "ACME",
            "gtin": "4006381333931",
            "packSize": "1"
        },
        {
            "id": "SUP-003",
            "title": "ACME Sparkling Water 6 x 500 ml",
            "brand": "ACME",
            "packSize": "6"
        },
        {
            "id": "SUP-004",
            "title": "ACME Sparkling Water 1 litre",
            "brand": "ACME",
            "packSize": "1"
        },
        {
            "id": "SUP-005",
            "title": "Northstar USB-C Cable 1m black",
            "brand": "Northstar",
            "mpn": "CAB-10"
        },
        {
            "id": "SUP-006",
            "title": "Northstar USB-C Cable 1m blue",
            "brand": "Northstar",
            "mpn": "CAB-10"
        },
        {
            "id": "SUP-007",
            "title": "Lumo Adjustable Desk Lamp",
            "brand": "Lumo"
        },
        {
            "id": "SUP-008",
            "title": "Leaf Organic Chamomile Tea 20 pack",
            "brand": "Leaf"
        },
        {
            "id": "SUP-009",
            "title": "Stainless steel mixing bowl",
            "brand": "Kitchen Works"
        },
        {
            "id": "SUP-010",
            "title": "",
            "brand": "Unknown"
        },
        {
            "id": "SUP-011",
            "title": "Garden cotton gloves",
            "gtin": "12345"
        },
        {
            "id": "SUP-012",
            "title": "מחברת כחולה",
            "brand": "Paper House"
        }
    ],
    "reference": [
        {
            "id": "MASTER-WATER",
            "title": "ACME Sparkling Water 500 ml",
            "brand": "ACME",
            "gtin": "4006381333931",
            "packSize": "1",
            "category": "Beverages > Water"
        },
        {
            "id": "MASTER-CABLE",
            "title": "Northstar USB C Cable 100cm Black",
            "brand": "Northstar",
            "mpn": "CAB-10",
            "category": "Electronics > Cables"
        },
        {
            "id": "MASTER-LAMP-A",
            "title": "Lumo Adjustable Desk Lamp",
            "brand": "Lumo",
            "category": "Home > Lighting"
        },
        {
            "id": "MASTER-LAMP-B",
            "title": "Lumo Adjustable Desk Lamp",
            "brand": "Lumo",
            "category": "Home > Lighting"
        }
    ],
    "taxonomy": [
        {
            "category": "Beverages > Tea",
            "keywords": [
                "tea",
                "chamomile"
            ]
        },
        {
            "category": "Kitchen > Cookware",
            "keywords": [
                "mixing bowl",
                "saucepan"
            ]
        },
        {
            "category": "Garden > Accessories",
            "keywords": [
                "gloves",
                "garden"
            ]
        },
        {
            "category": "Office > Stationery",
            "keywords": [
                "notebook",
                "מחברת"
            ]
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("h_murdock/supplier-catalog-cleanup").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 = {
    "catalog": [
        {
            "id": "SUP-001",
            "title": "ACME Sparkling Water 0.5 L",
            "brand": "ACME",
            "gtin": "4006381333931",
            "packSize": "1",
        },
        {
            "id": "SUP-002",
            "title": "Sparkling Water ACME 500ml",
            "brand": "ACME",
            "gtin": "4006381333931",
            "packSize": "1",
        },
        {
            "id": "SUP-003",
            "title": "ACME Sparkling Water 6 x 500 ml",
            "brand": "ACME",
            "packSize": "6",
        },
        {
            "id": "SUP-004",
            "title": "ACME Sparkling Water 1 litre",
            "brand": "ACME",
            "packSize": "1",
        },
        {
            "id": "SUP-005",
            "title": "Northstar USB-C Cable 1m black",
            "brand": "Northstar",
            "mpn": "CAB-10",
        },
        {
            "id": "SUP-006",
            "title": "Northstar USB-C Cable 1m blue",
            "brand": "Northstar",
            "mpn": "CAB-10",
        },
        {
            "id": "SUP-007",
            "title": "Lumo Adjustable Desk Lamp",
            "brand": "Lumo",
        },
        {
            "id": "SUP-008",
            "title": "Leaf Organic Chamomile Tea 20 pack",
            "brand": "Leaf",
        },
        {
            "id": "SUP-009",
            "title": "Stainless steel mixing bowl",
            "brand": "Kitchen Works",
        },
        {
            "id": "SUP-010",
            "title": "",
            "brand": "Unknown",
        },
        {
            "id": "SUP-011",
            "title": "Garden cotton gloves",
            "gtin": "12345",
        },
        {
            "id": "SUP-012",
            "title": "מחברת כחולה",
            "brand": "Paper House",
        },
    ],
    "reference": [
        {
            "id": "MASTER-WATER",
            "title": "ACME Sparkling Water 500 ml",
            "brand": "ACME",
            "gtin": "4006381333931",
            "packSize": "1",
            "category": "Beverages > Water",
        },
        {
            "id": "MASTER-CABLE",
            "title": "Northstar USB C Cable 100cm Black",
            "brand": "Northstar",
            "mpn": "CAB-10",
            "category": "Electronics > Cables",
        },
        {
            "id": "MASTER-LAMP-A",
            "title": "Lumo Adjustable Desk Lamp",
            "brand": "Lumo",
            "category": "Home > Lighting",
        },
        {
            "id": "MASTER-LAMP-B",
            "title": "Lumo Adjustable Desk Lamp",
            "brand": "Lumo",
            "category": "Home > Lighting",
        },
    ],
    "taxonomy": [
        {
            "category": "Beverages > Tea",
            "keywords": [
                "tea",
                "chamomile",
            ],
        },
        {
            "category": "Kitchen > Cookware",
            "keywords": [
                "mixing bowl",
                "saucepan",
            ],
        },
        {
            "category": "Garden > Accessories",
            "keywords": [
                "gloves",
                "garden",
            ],
        },
        {
            "category": "Office > Stationery",
            "keywords": [
                "notebook",
                "מחברת",
            ],
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("h_murdock/supplier-catalog-cleanup").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 '{
  "catalog": [
    {
      "id": "SUP-001",
      "title": "ACME Sparkling Water 0.5 L",
      "brand": "ACME",
      "gtin": "4006381333931",
      "packSize": "1"
    },
    {
      "id": "SUP-002",
      "title": "Sparkling Water ACME 500ml",
      "brand": "ACME",
      "gtin": "4006381333931",
      "packSize": "1"
    },
    {
      "id": "SUP-003",
      "title": "ACME Sparkling Water 6 x 500 ml",
      "brand": "ACME",
      "packSize": "6"
    },
    {
      "id": "SUP-004",
      "title": "ACME Sparkling Water 1 litre",
      "brand": "ACME",
      "packSize": "1"
    },
    {
      "id": "SUP-005",
      "title": "Northstar USB-C Cable 1m black",
      "brand": "Northstar",
      "mpn": "CAB-10"
    },
    {
      "id": "SUP-006",
      "title": "Northstar USB-C Cable 1m blue",
      "brand": "Northstar",
      "mpn": "CAB-10"
    },
    {
      "id": "SUP-007",
      "title": "Lumo Adjustable Desk Lamp",
      "brand": "Lumo"
    },
    {
      "id": "SUP-008",
      "title": "Leaf Organic Chamomile Tea 20 pack",
      "brand": "Leaf"
    },
    {
      "id": "SUP-009",
      "title": "Stainless steel mixing bowl",
      "brand": "Kitchen Works"
    },
    {
      "id": "SUP-010",
      "title": "",
      "brand": "Unknown"
    },
    {
      "id": "SUP-011",
      "title": "Garden cotton gloves",
      "gtin": "12345"
    },
    {
      "id": "SUP-012",
      "title": "מחברת כחולה",
      "brand": "Paper House"
    }
  ],
  "reference": [
    {
      "id": "MASTER-WATER",
      "title": "ACME Sparkling Water 500 ml",
      "brand": "ACME",
      "gtin": "4006381333931",
      "packSize": "1",
      "category": "Beverages > Water"
    },
    {
      "id": "MASTER-CABLE",
      "title": "Northstar USB C Cable 100cm Black",
      "brand": "Northstar",
      "mpn": "CAB-10",
      "category": "Electronics > Cables"
    },
    {
      "id": "MASTER-LAMP-A",
      "title": "Lumo Adjustable Desk Lamp",
      "brand": "Lumo",
      "category": "Home > Lighting"
    },
    {
      "id": "MASTER-LAMP-B",
      "title": "Lumo Adjustable Desk Lamp",
      "brand": "Lumo",
      "category": "Home > Lighting"
    }
  ],
  "taxonomy": [
    {
      "category": "Beverages > Tea",
      "keywords": [
        "tea",
        "chamomile"
      ]
    },
    {
      "category": "Kitchen > Cookware",
      "keywords": [
        "mixing bowl",
        "saucepan"
      ]
    },
    {
      "category": "Garden > Accessories",
      "keywords": [
        "gloves",
        "garden"
      ]
    },
    {
      "category": "Office > Stationery",
      "keywords": [
        "notebook",
        "מחברת"
      ]
    }
  ]
}' |
apify call h_murdock/supplier-catalog-cleanup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,h_murdock/supplier-catalog-cleanup"
        }
    }
}
```

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/F2KcN5QOL8uDxffdF/builds/euOsdcM0XhefGBr2v/openapi.json
