# Data-import repair: supplier catalog to Shopify product CSV (`nunabody/shopify-product-csv-import-repair`) Actor

Maps, normalizes and validates a supplier catalog (CSV/XLSX, up to 5,000 rows) into Shopify's product-import CSV, with a per-row validation report. Charged only on delivery.

- **URL**: https://apify.com/nunabody/shopify-product-csv-import-repair.md
- **Developed by:** [Alex Sinquefield](https://apify.com/nunabody) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5,000.00 / 1,000 job accepteds

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

## Data-import repair — Shopify product CSV

This README is the promise, verbatim. No marketing copy.

Written before the code. Every line is either **sourced** (to a snapshotted first-party page, by hash) or marked **v0 choice**.

**Destination.** Shopify's product import CSV as defined by Shopify's own page *Using CSV files to import and export products* (Shopify Help Center), snapshotted as browser-rendered text. The destination version is that page's content at sha256 `7fc9dd43a916a4b9689aab9120ac0ba8cef630b529130436191cda795f33ef9a`, pinned in this Actor's metadata. If the extract disagrees with the snapshot, the tests fail. *(sourced)*

**Inputs.** One CSV or XLSX file, UTF-8 (the page: "save your CSV file in UTF-8 format using LF-style linefeeds"; *sourced*), at most 5,000 rows *(v0 choice)*, exactly one header row, any column order. **Scope note:** the reviewer's example was one *specific supplier's* catalog format. No supplier exists yet, so v0's alias table covers (a) Shopify's own older column names, which the page says it "maintains backward compatibility with" *(sourced)*, and (b) a hand-written list of generic non-Shopify product headers *(v0 choice, marked in `aliases.py`)*. The specific supplier format arrives with the first customer and narrows the alias table, not the other way round.

**Permitted transformations.** Each is a named, deterministic, pure function in `repair.py`, and each is listed with its rule source in `spec_extract.json` under `permitted_rules`:

- `map_header` — header mapping by the explicit alias table only. *(alias table: sourced for legacy names, v0 choice for generic names)*
- `derive_handle_from_title` — only when `URL handle` is absent for a product with no variants: lowercase, spaces to dashes, letters/digits/dashes only. *(sourced: "based on the value in the Title column ... black-sunglasses by default"; "letters, dashes, and numbers, but it can't contain any spaces")*
- `derive_option1_default` — `Option1 name` = `Default Title` only when a product has one row and the column is blank. *(sourced: "If a product has only one option, then this value should be Default Title")*
- `derive_fulfillment_manual` — `Fulfillment service` = `manual` only when the column is present and blank. *(sourced: "manual (default)"; and the common-import-issues page: "If you're not using a fulfillment service, then this should be set to manual")*
- `normalize_money` — strip a leading currency symbol and thousands separators from `Price`, `Compare-at price`, `Cost per item`. *(sourced: "Only include the monetary value without a currency symbol. For example, 9.99")*
- `normalize_integer` — strip thousands separators and surrounding whitespace from integer columns. *(sourced: "specify only the numerical value")*
- `normalize_bool` — `yes/no`, `1/0`, `TRUE/FALSE` to `true`/`false` in boolean columns. *(v0 choice; valid values sourced)*
- `normalize_enum` — case-fold enum columns to their documented valid values. *(v0 choice; valid values sourced)*
- `straighten_quotes` — curly quotes to straight quotes. *(sourced: common-import-issues page, "change “ “ into quotes that look like this: " "")*
- `variant_row_layout` — on the second and later rows of a handle, blank `Title`, `Description`, `Vendor`, `Tags` when they repeat the first row's values. *(sourced: "In the following rows, enter the URL handle. Then skip the Title, Description, Vendor, and Tags columns")*

**Preserved.** `URL handle`, `SKU`, `Barcode`, and every source value that maps to a destination column — byte-for-byte unless a permitted transformation above names it. `URL handle`, `SKU` and `Barcode` are named by no transformation except `derive_handle_from_title`, which only fills an *absent* handle and never changes a present one. *(v0 choice, enforced by the checker)*

**Never done.** Inventing a missing value: a blank `Price`, `Inventory quantity`, `Weight value (grams)` stays blank even though the page states Shopify's own default for it; this pipeline never fills a default. Guessing an ambiguous header. Dropping a row silently. *(v0 choice)*

**Rejection conditions.** A required destination column that cannot be derived by a stated rule (`Title`; `URL handle` when a product has variants). A header matching two aliases, or two input headers mapping to one destination column. A header matching no alias (the page: "You can't add any other columns to your CSV file", `Collection` excepted). More than 5,000 rows. Input not decodable as UTF-8, or not parseable as CSV. A row that fails validation after all permitted transformations. Rejection returns the row, the reason, and the rule that failed. *(sourced where quoted; v0 choice otherwise)*

**Output.** The repaired CSV in the destination's column names, plus a JSON validation report listing the header mapping, every transformation applied per row and column, every rejection with reason and rule, and every warning.

**Destination.** Shopify's product import CSV as defined by Shopify's own page *Using CSV files to import and export products* (Shopify Help Center), snapshotted as browser-rendered text. The destination version is that page's content at sha256 `7fc9dd43a916a4b9689aab9120ac0ba8cef630b529130436191cda795f33ef9a`, pinned in this Actor's metadata. If the extract disagrees with the snapshot, the tests fail. *(sourced)*

### Price and refund rule

- **Price:** $5 per job (event `job_accepted`), charged only when a repaired file is delivered. A rejected file costs nothing.
- **Refund rule:** rejected jobs are never charged; delivered jobs are not refunded. The deliverable is the repaired CSV and the validation report.
- Output is validated against Shopify's documented format and not tested against a live store.

# Actor input Schema

## `file` (type: `string`):

The supplier catalog as text: CSV (UTF-8) or a base64-encoded XLSX. One header row, any column order, at most 5,000 rows (v0 limit).

## `filename` (type: `string`):

Used only to detect .xlsx; defaults to input.csv

## `base64` (type: `boolean`):

Set for XLSX uploads

## Actor input object example

```json
{
  "filename": "input.csv",
  "base64": false
}
```

# Actor output Schema

## `repairedCsv` (type: `string`):

The repaired product-import CSV in Shopify's documented header order (key OUTPUT.csv in the run's key-value store). Absent when the job was rejected.

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

Per-row transformations applied, rejections with the rule that failed, findings, and the header mapping (key REPORT.json).

## `summary` (type: `string`):

One dataset item per job: idempotency key, valid, accepted-row count, rejected-row count, charged.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("nunabody/shopify-product-csv-import-repair").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("nunabody/shopify-product-csv-import-repair").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 '{}' |
apify call nunabody/shopify-product-csv-import-repair --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nunabody/shopify-product-csv-import-repair"
        }
    }
}

```

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/8bbfzSSoMQ2m0HSAx/builds/OebHgUSEitcqQrDUG/openapi.json
