# Supplier Catalog Converter Pro (`privatex/supplier-catalog-converter-pro`) Actor

Turn supplier price lists (CSV, TSV, XLSX, text PDF) into a validated product dataset plus WooCommerce and Shopify import CSVs. GTIN checks, duplicate and variant handling, a review queue, nothing invented.

- **URL**: https://apify.com/privatex/supplier-catalog-converter-pro.md
- **Developed by:** [PRIVATEX](https://apify.com/privatex) (community)
- **Categories:** E-commerce, Integrations
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 product row converteds

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

## Supplier Catalog Converter Pro

Turn a supplier's price list (**CSV, TSV, XLSX or text-based PDF**) into clean product rows, plus **ready-to-import WooCommerce and Shopify CSV files**, a **validation report** and a **review queue**. Missing data stays empty: no SKU, GTIN, brand, price, image or stock level is ever invented.

### Who needs it

- **Online shops** that receive supplier catalogs in Excel or PDF and re-type them into WooCommerce or Shopify.
- **Agencies and marketplace operators** onboarding many suppliers' files in different layouts and languages.

### What it does

- **Reads the file:**
  - CSV and TSV (comma, semicolon or tab; quoted multi-line cells; UTF-8 with or without BOM; windows-1254 and other encodings).
  - XLSX: every sheet; the header row is found even below title rows; formulas use their stored results.
  - Text-based PDF tables, including catalogs with several "SKU | price" column pairs side by side.
- **Finds the columns:** SKU, name, description, brand, GTIN/EAN/UPC, price, sale price, currency, category, size/colour/material, parent/style, stock, weight and image.
  - Header names are matched in English, Turkish, German, Spanish and French.
  - Old/previous and change/percentage price columns are skipped (for example "Old List Price" vs "New List Price").
  - Every decision is listed in the validation report. You can override any of them with `columnMapping`.
- **Checks every row:**
  - GTIN check digits are verified.
  - Prices like `1.234,56 €` and `$1,234.56` are parsed. Text such as "Inquire" is never turned into a price.
  - Exact duplicate rows are merged.
  - The same SKU with different data goes to review. **Both rows are kept**; nothing is merged unless every product field (name, description, brand, GTIN, MPN, price, sale price, currency, category, parent, options, stock, weight, images) is identical. `duplicateConflictFields` lists what differs, and a stock difference is coded `DUPLICATE_SKU_STOCK_DIFFERS`.
  - A sale price that is filled in but is not a usable number (text or negative) is flagged `SALE_PRICE_NOT_NUMERIC` and goes to review, instead of being dropped silently.
  - GTINs in Excel number cells lose their leading zeros (UPC `012345678905` becomes `12345678905`). The Actor adds back at most one or two zeros, **only if the check digit then validates**, marks the row `GTIN_LEADING_ZEROS_RESTORED` and **sends it to the review queue**: a wrong number can pass the check digit by chance, so please confirm the restored GTIN. Otherwise the value is reported as `GTIN_INVALID`. GTINs stored as text are never changed.
  - A price read from ambiguous "1.250"-style text that sits 50× away from the catalog median goes to review.
- **Groups variants** (size, colour, material) by the supplier's parent/style code.
- **Writes the import files** following the platforms' documented headers (checked 2026-10-30).
  - **WooCommerce:**
    - Type, SKU, `GTIN, UPC, EAN, or ISBN`, Name, Published, … Regular price, Sale price, Categories, Images, In stock?, Stock, Parent, Attribute 1–3.
    - Products are **drafts** (`Published = -1`) unless you choose otherwise.
  - **Shopify:**
    - Uses the current template: Title, `URL handle`, Description, Vendor, … SKU, `Barcodes`, Option1–3, Price, Compare-at price, Inventory quantity, `Product image URL`.
    - Variants share a URL handle.
    - Products are **drafts** unless you choose `active`.
  - For every target column the report says **MAPPED**, **DEFAULTED**, **BLANK** or **UNSUPPORTED**. For example, the default WooCommerce importer has no brand column.
  - Cells starting with `=`, `+`, `-` or `@` (text, not real numbers) are neutralised against spreadsheet formula injection. The count is in the report.

### What it does NOT do

- **No OCR.** Scanned PDFs (pages with no text layer) are reported as `OCR_REQUIRED`.
- **No writes to your shop.** It never connects to WooCommerce or Shopify; you import the CSV yourself.
- **No made-up data.** No currency conversion, no generated SKUs or barcodes, no guessed brands, images or stock.
- **No legacy `.xls`.** Save Excel 97-2003 files as `.xlsx` or CSV first.

### Start in 60 seconds

1. Press **Start** without giving a file. It converts a small **synthetic** catalog for a fictional brand. The demo shows variants, duplicates, an invalid GTIN and the review queue. No conversion events are charged for demo runs.
2. Open **WooCommerce import CSV**, **Shopify import CSV** and **Review queue** in the Output tab.
3. Paste the HTTPS link to your supplier file, or pick a key-value store record. Set the catalog currency if the file doesn't state it, then run.

#### Example input

```json
{
    "fileUrl": "https://example.com/supplier/price-list-2026.xlsx",
    "defaultCurrency": "USD",
    "wooPublished": "draft",
    "shopifyStatus": "draft"
}
```

#### Example output row

This row is real: cloud run `rZbPV6ENMfPEHfUiy`, 2026-10-30, a public Broilmaster retail price list (XLSX). The `description` field is omitted here for brevity.

```json
{
    "sku": "P3PK5N",
    "name": "Package 1, Stainless Cart/Base, one Side Shelf with Stainless Bracket, Natural",
    "gtin": "720968971470",
    "price": 2109,
    "currency": "USD",
    "currencySource": "user-default",
    "brand": null,
    "stock": null,
    "weight": "116",
    "issues": [],
    "needsReview": false,
    "source": { "file": "2026-27-Broilmaster-Retail-Price-List-Effective-10-1-26-Excel.xlsx", "sheet": "BRM Price List (List Format)", "row": 3 }
}
```

Two real files were converted in the cloud:

- the Broilmaster XLSX: 477 rows, `New List Price` chosen over `Old List Price`, 0 rows needing review;
- a public 26-page Gilson PDF price list: 5,218 rows from 5 side-by-side column groups, 228 rows in the review queue ("Inquire" prices and SKUs listed twice with different prices).

### Output

| Where | What |
|---|---|
| Dataset | One row per product: `sku`, `name`, `description`, `brand`, `gtin`, `gtinRaw`, `price`, `salePrice`, `currency`, `currencySource`, `options`, `stock`, `issues`, `needsReview` and `source` (file, sheet, row, or PDF page/line). |
| `WOOCOMMERCE_CSV` | Import file for the WooCommerce Product CSV Importer. |
| `SHOPIFY_CSV` | Import file for Shopify (Products → Import). |
| `REVIEW_QUEUE_CSV` | Rows a person should check first, with the reasons. |
| `VALIDATION_REPORT` | Column-mapping decisions, per-column export status, issue counts, variant groups and notes. |
| `OUTPUT` | Run status: `SUCCEEDED`, `PARTIAL_RESULT`, `EMPTY_INPUT` or `FAILED`, with an error code such as `INPUT_INVALID`, `ACCESS_DENIED`, `UNSUPPORTED_FORMAT`, `PARSE_ERROR`, `OCR_REQUIRED` or `INPUT_TOO_LARGE`. Also counts and billing. |

All CSV files are UTF-8 with a BOM, so Excel shows non-English characters correctly.

### Pricing

Pay per event:

| Event | Price | When it is charged |
|---|---|---|
| `product-row-converted` | **$0.001** | Once per product row delivered to the dataset and the export files. |
| `catalog-converted` | **$0.03** | Once per run, when at least one row was delivered. |
| `apify-actor-start` | $0.00005 | Once per run, by Apify. |

Examples:

- 500 rows: $0.53.
- 5,000 rows: $5.03.

Platform usage is included.

You are **not charged** for:

- exact duplicate rows;
- empty rows;
- failed runs;
- demo conversion events.

The Actor respects your **maximum cost per run**: it converts only as many rows as the limit allows, keeps room for the catalog event and reports the rest as `skippedForChargeLimit`.

### Limits and security

| Setting | Default | Maximum |
|---|---|---|
| Rows (`maxRows`) | 5,000 | 50,000 |
| File size (`maxFileSizeMb`) | 20 MB | 100 MB |
| PDF pages (`maxPdfPages`) | 50 | 300 |

- File URLs must be HTTPS. Private, internal, loopback and cloud-metadata addresses are refused, also after redirects.
- Oversized and compressed-bomb downloads are cut off.
- The Actor runs with **limited permissions**. It can read only the key-value store you pick.
- Your file is processed in the run and not stored anywhere else.

### Known limitations

- PDF table reading relies on a recognisable header row ("SKU", "Item", "Model No.", "Price", "US$" …). It handles single tables and repeated column groups. Complex layouts (merged cells, prices on a different line from the SKU) may need a CSV/XLSX export from the supplier.
- When the file has no name/title column, the description column is used as the product name; the validation report says so.
- WooCommerce variations are linked only when the file has a parent/style code. Variants found by product name alone are exported as simple products, and the report explains why.
- Shopify and WooCommerce import prices in your store currency. Check that it matches the catalog currency.

### Support

Use the **Issues** tab on this Actor's page.

# Actor input Schema

## `fileUrl` (type: `string`):

Direct https:// link to a CSV, TSV, XLSX or text-based PDF. Private and internal addresses are refused. Pre-signed links are fine; the query string is never logged.

## `keyValueStoreId` (type: `string`):

Pick a store that holds the uploaded file, then enter the record key below.

## `recordKey` (type: `string`):

Key of the file record in the selected key-value store.

## `demoMode` (type: `boolean`):

Converts a small fictional supplier catalog that shows variants, duplicates, an invalid GTIN and the review queue. No conversion events are charged. Ignored when a file is given. Also runs automatically when no file is given.

## `sheetName` (type: `string`):

Only this sheet. Empty = all sheets.

## `defaultCurrency` (type: `string`):

3-letter ISO code such as EUR. Used only for rows where no currency is found in a column, the price text or the price header; such rows are marked currencySource = user-default.

## `columnMapping` (type: `object`):

Override automatic header detection, e.g. {"sku": "Art.-Nr.", "price": "VK netto"}. Fields: sku, name, description, brand, gtin, price, salePrice, currency, category, size, color, material, parent, stock, weight, image, mpn.

## `exportWooCommerce` (type: `boolean`):

Product CSV for the WooCommerce Product CSV Importer.

## `wooPublished` (type: `string`):

Draft is safest: review in WooCommerce, then publish.

## `exportShopify` (type: `boolean`):

Product CSV in Shopify's current import template.

## `shopifyStatus` (type: `string`):

Draft is safest.

## `maxRows` (type: `integer`):

Rows read from the file.

## `maxFileSizeMb` (type: `integer`):

Larger files are refused.

## `maxPdfPages` (type: `integer`):

Pages read from a PDF.

## Actor input object example

```json
{
  "demoMode": false,
  "columnMapping": {},
  "exportWooCommerce": true,
  "wooPublished": "draft",
  "exportShopify": true,
  "shopifyStatus": "draft",
  "maxRows": 5000,
  "maxFileSizeMb": 20,
  "maxPdfPages": 50
}
```

# Actor output Schema

## `products` (type: `string`):

No description

## `woocommerce` (type: `string`):

No description

## `shopify` (type: `string`):

No description

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

No description

## `report` (type: `string`):

No description

## `summary` (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 = {
    "columnMapping": {}
};

// Run the Actor and wait for it to finish
const run = await client.actor("privatex/supplier-catalog-converter-pro").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 = { "columnMapping": {} }

# Run the Actor and wait for it to finish
run = client.actor("privatex/supplier-catalog-converter-pro").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 '{
  "columnMapping": {}
}' |
apify call privatex/supplier-catalog-converter-pro --silent --output-dataset

```

## MCP server setup

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

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/CGNltvGh6lLazaXjq/builds/zMPSFgw8AU3tMy7L3/openapi.json
