# Shopify Promotion Rollback Review (`remarkable_whipscorpion/shopify-promotion-rollback-review`) Actor

Compare before, applied, and current Shopify CSV prices to identify variants requiring rollback review.

- **URL**: https://apify.com/remarkable_whipscorpion/shopify-promotion-rollback-review.md
- **Developed by:** [Kagerou AP](https://apify.com/remarkable_whipscorpion) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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 Promotion Rollback Review

Review the main variant price after a Shopify promotion using three CSV snapshots: **baseline** before the promotion, **applied** after the promotion, and **current** now. The report identifies exact variants whose current price still matches the applied promotion, and separates intervening changes, variants already at baseline, unchanged promotions, and unknown or ambiguous records.

This is for Shopify CSV bulk-price agencies that already retain all three snapshots. If baseline or applied files were not retained, do not reconstruct them from guessed discounts. This Actor compares supplied CSV text locally. It does not fetch files, call Shopify, change a store, produce an import-ready patch, or determine that a restoration is safe or approved.

### Try the original demo

The default input and API example are:

```json
{"mode":"demo"}
```

Demo uses the project's original synthetic fixture in USD and has **no custom report event**. Leave all CSV fields omitted; supplied CSV data is rejected in demo. The fixture contains four exact variants with these expected outcomes:

| Variant | Baseline | Applied | Current | Review result |
| --- | --- | --- | --- | --- |
| project-shirt / Size S | 20.00 | 15.00 | 15.0 | restore_candidate; review suggestion 20.00 |
| project-shirt / Size M | 20.00 | 15.00 | 17.00 | intervening_change |
| project-shirt / Size L | 20.00 | 15.00 | 20.00 | already_baseline |
| project-tote / Title Default Title | 9.00 | 9.00 | 9.00 | no_promotion_delta |

The automatic platform startup fee is disclosed below and also applies to demo.

### Review your own snapshots

Use `mode: "review"`, a single shop `currency` label, `rightsConfirmed: true`, and three nonempty CSV strings: `baselineCsv`, `appliedCsv`, and `currentCsv`. Review rejects missing or false rights confirmation. The currency label starts with a letter and contains at most 16 letters, digits, spaces, underscores, or hyphens. By confirming, you acknowledge authorization to upload and process all three snapshots in your Apify storage.

[sample-review-input.json](sample-review-input.json) uses the same original synthetic fixture in charged review mode. It is separate from [sample-input.json](sample-input.json), which is the default demo. The currency label is supplied by you; the Actor does not verify it or perform currency conversion. Data is processed in the run and persisted to your Apify storage.

#### Supported CSV fields

| Purpose | Frozen supported headers |
| --- | --- |
| Product handle | `Handle` or `URL handle` |
| Main variant price | `Variant Price` or `Price` |
| First option | `Option1 Name` / `Option1 name`, `Option1 Value` / `Option1 value` |
| Second option | `Option2 Name` / `Option2 name`, `Option2 Value` / `Option2 value` |
| Third option | `Option3 Name` / `Option3 name`, `Option3 Value` / `Option3 value` |

The spaced option headers cover the legacy and current Shopify forms. Option1 value is required, including `Default Title` for a default variant. Repeated required headers are rejected; conflicting values in duplicate alias columns are rejected. Parsing handles RFC 4180 quoting, preserves string identifiers and leading zeros, and does not infer an identity from SKU.

Identity is the exact handle plus the complete option name/value tuple. Handle continuation is propagated only within the same product group. Image-only continuation rows are explicitly ignored and counted. Duplicate identities in any snapshot are ambiguous. An identity missing from any snapshot is unknown with `missing_snapshot`; no price restoration is guessed.

Prices must be nonnegative decimal strings with at most 12 integer and 6 fractional digits. Exponents, thousands separators, negative values, blank prices, and invalid money do not become usable prices. Comparison uses scaled integer arithmetic, so `15.0` and `15.00` compare equally without binary floating point. Invalid money produces unknown coverage.

### Rules and human review

For an exact identity with valid prices in all three snapshots:

| Condition | Status |
| --- | --- |
| Baseline equals applied | `no_promotion_delta` |
| Baseline differs from applied; current equals baseline | `already_baseline` |
| Baseline differs from applied; current equals applied | `restore_candidate` |
| Current differs from both applied and baseline | `intervening_change` |

Only `restore_candidate` includes `reviewTargetPrice`, using the baseline's original price string. This is a **review suggestion**. Source rows and current conditions still require buyer review before any action. Missing, ambiguous, or invalid comparisons cannot produce a restore suggestion.

### Outputs

Every completed run publishes one nonempty default Dataset summary with `schemaVersion`, `rulesVersion`, `status`, `mode`, `currency`, `counts`, `coverage`, `limits`, `billing`, `reportKey`, and `reviewCsvKey`. Dataset rows do not contain variant details.

The default key-value store holds:

- **REPORT.json**: complete JSON report and every union identity within the limit, with exact `handle` and `options`, `sourceRows { baseline, applied, current }`, `prices { baseline, applied, current }`, `status`, `reasonCode`, and a review-only `reviewTargetPrice` for restore candidates.
- **REVIEW.csv**: the same complete review facts in CSV form. Exported cells are guarded against spreadsheet formula execution. This CSV is intended for review and is not an import-ready Shopify patch.

The Actor Output schema links both reports and the summary Dataset. Counts explicitly include useful comparisons, missing snapshots, ambiguous records, invalid prices, and ignored rows; coverage identifies partial reviews. Output is complete for accepted inputs, with an explicit total and detail limit. The Actor rejects input above the union identity cap rather than silently truncating a paid report.

`sourceRows` records the physical end line of each parsed CSV record, including the header in line numbering. A quoted multiline record therefore points to its final physical line. The single Dataset summary is published before the custom event, so its billing may remain `pending`. **REPORT.json is authoritative for the settled billing state**; its later `confirmed` or `ambiguous` state does not create a second Dataset summary.

### Limits and pricing

Each CSV has a hard limit of **5,000 data rows**, **5 MiB UTF-8**, and **200 columns**. The union of exact identities across the three snapshots is capped at **5,000**; the JSON and CSV reports contain all accepted identities. Only the main variant price is compared.

The custom event `rollback_review_completed` costs **$0.50 for one delivered useful custom report**, irrespective of variant count. A partial report with at least one useful exact comparison is one report event. Invalid input, all-unknown or all-ambiguous diagnostics, and the original demo incur **zero custom report events**.

The platform-managed `apify-actor-start` event is **$0.00005 per start**. There is no Dataset item event. The Actor does not manually charge the start event. Allow **$0.50005** for a useful custom report including startup; the run's pricing and platform billing remain visible in Apify.

Funded useful capacity is determined before useful Dataset, KVS, or checkpoint content is published. The custom event is issued only after the complete JSON report, complete CSV report, and summary have been saved. An insufficient budget yields diagnostics without a useful paid report. Failed output persistence does not produce a report event. Resume state prevents a duplicate fee; an uncertain earlier charge fails closed without blind retries. Inspect `billing` for the observed event state.

### Source and license

Runtime dependencies are pinned to **Apify SDK 3.7.2** and **csv-parse 6.1.0** in the package and lockfile. CSV parsing uses csv-parse under the MIT license; the license text is included in [evidence/csv-parse-license.txt](evidence/csv-parse-license.txt), copied from the project's inspected package license evidence.

The original synthetic demo fixture was created for this project and is made available under CC0 1.0. It contains no customer data. No WooCommerce PHP code or upstream Actor code is copied.

### Local development

Install from the committed lockfile with `npm ci`, then run `npm start`. Before importing the SDK for local testing, set `CRAWLEE_STORAGE_DIR` in the child process environment to a product-specific isolated directory and confirm the SDK uses that resolved directory. Never drop or purge shared default storage.

The local test command is `node --test --test-isolation=none test/core.test.js test/sdk.test.js` after setting that launch environment and clearing cloud credentials/flags. The suite asserts the actual SDK storage client base. The launch environment takes precedence over constructor `storageDir`, so the roundtrip tests use distinct store/dataset IDs within the same verified isolated base. Local non-PPE storage tests do not prove a hosted fee; budget and event behavior is tested with the real pinned charging manager and a stubbed native charge endpoint.

The Dockerfile installs only lockfile production dependencies and runs as the Node user. Native build, hosted run, pricing registration, and publication require the separate release gate and are outside local implementation.

# Actor input Schema

## `mode` (type: `string`):

Demo uses only the included original fixture, with no custom report fee. Review requires all three CSV strings, currency, and rightsConfirmed=true. Supplying CSV data in demo is rejected.

## `baselineCsv` (type: `string`):

Required in review: complete CSV text from before the promotion. Maximum 5 MiB UTF-8 and 5,000 data rows. Do not provide this field in demo.

## `appliedCsv` (type: `string`):

Required in review: complete CSV text showing the applied promotion prices. Maximum 5 MiB UTF-8 and 5,000 data rows. Do not provide this field in demo.

## `currentCsv` (type: `string`):

Required in review: complete CSV text showing current prices. Maximum 5 MiB UTF-8 and 5,000 data rows. Do not provide this field in demo.

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

Required in review, for example USD. This label is supplied by you; the Actor does not verify currency or perform foreign exchange. Demo uses USD.

## `rightsConfirmed` (type: `boolean`):

Review requires true. Confirm that you have permission to upload and process all three CSV snapshots in your Apify storage. The original demo is exempt.

## Actor input object example

```json
{
  "mode": "demo"
}
```

# Actor output Schema

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

REPORT.json contains the summary and every union identity up to the 5,000 identity limit, exact handle/options, all three source row lists and prices, status, reasonCode, and reviewTargetPrice only for restore_candidate. Inspect coverage and billing before using results. REPORT.json is authoritative for the settled billing state.

## `reviewCsv` (type: `string`):

REVIEW.csv contains the same complete review facts as REPORT.json, with cells guarded against spreadsheet formula execution. It is a review artifact and is not an import-ready Shopify patch.

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

Default Dataset contains one summary with counts, coverage, limits, billing, and report keys. It is published before the custom event, so its billing can remain pending; inspect REPORT.json for the settled billing state. Variant details are in the two KVS reports.

# 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 = {
    "mode": "demo"
};

// Run the Actor and wait for it to finish
const run = await client.actor("remarkable_whipscorpion/shopify-promotion-rollback-review").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 = { "mode": "demo" }

# Run the Actor and wait for it to finish
run = client.actor("remarkable_whipscorpion/shopify-promotion-rollback-review").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 '{
  "mode": "demo"
}' |
apify call remarkable_whipscorpion/shopify-promotion-rollback-review --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,remarkable_whipscorpion/shopify-promotion-rollback-review"
        }
    }
}
```

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/h8SMHcYApet3AVwsE/builds/ONeXFUBZjCGXn4oBC/openapi.json
