# Merchant Sale Transition Audit (`wwhynot/merchant-sale-transition-audit`) Actor

Audit feed vs static JSON-LD prices at sale boundaries. Compare up to 25 SKUs, classify new/existing/resolved differences, and export evidence, CSV and a reusable baseline.

- **URL**: https://apify.com/wwhynot/merchant-sale-transition-audit.md
- **Developed by:** [Elena Lyalina](https://apify.com/wwhynot) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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

## Merchant Sale Transition Audit

Catch declared feed prices that disagree with static product JSON-LD when a promotion starts or ends. Compare up to **25 SKUs per run**, retain exact evidence, and distinguish a new difference from an existing or resolved one using an earlier baseline.

Use this Actor for a small promotion-release check in a catalog or merchant-feed workflow. It returns a structured review queue, CSV and a reusable baseline. It does not change your store or feed.

### Pricing and the demonstration

The live **Pricing** tab is authoritative: author pay-per-event fees are not active until shown there. Under the proposed pay-per-event contract, **every delivered SKU audit record is billable**, including `clear`, `needs_review`/unknown and synthetic simulations. The prefilled demonstration is a simulation, not a free-only fixture and not proof of a real store fault.

The proposed price is **$0.005 per delivered record** plus **$0.00005 per run start** at the supported 256–512 MB memory. One record costs $0.00505; 25 records cost $0.12505 in event fees. Invalid input rejected before results may still incur the startup event. CSV, BASELINE and SUMMARY carry no extra author event fee. Proposed PPE includes platform usage rather than adding a separate usage fee; taxes and the current live Pricing tab remain authoritative.

**For the proposed PPE contract, $0.14 would be the minimum user-selected maximum spending cap, not a minimum billed fee.** It reserves enough room for the complete batch of up to 25 records and another event's headroom. Companion CSV/baseline/summary are expected on a successful complete run; an unrelated crash or manual interruption can still prevent their completion.

### Try the demonstration

The prefilled example is a **synthetic simulation**, with no network requests. It models an offer ending at `2026-10-06T07:00:00Z`: the feed returns to GBP100 while the supplied JSON-LD still contains GBP80. A compatible earlier baseline makes the result `new_issue`. This demonstration is not evidence of a fault in any real store.

Select **Start** with the prefilled input, then open the dataset. For your own audit, replace the example with your declared feed and authorized markup. A real observed run requires a timezone-aware capture timestamp for each supplied snapshot; omit `checkedAt` when fetching a fresh public page.

### Input

Supply exactly one `feed` array or `feedCsv` string. Use 1–25 unique string SKUs and declare `price`, three-letter `currency`, `price_basis` (`tax_inclusive` or `tax_exclusive`) and product `url`. Optional `sale_price`, `sale_start` and `sale_end` describe the promotion; both timestamps need an explicit timezone. Sale start is inclusive and end exclusive.

Supply static `html` on the feed row or a `snapshots` array keyed by SKU. For `mode: "observed"`, each supplied capture needs timezone-aware `captured_at`; comparisons use capture time, not when processing finishes. For a hypothetical boundary test, use `mode: "simulation"` with explicit `checkedAt`. Every simulation row is labelled hypothetical.

Optional `baseline` is the complete `BASELINE` array returned by a strictly earlier compatible run. Do not use a partial object or reevaluate one saved capture as two real observations. Changed product identity, market, variant, currency or declared feed terms can make a baseline incomparable.

CSV headers: `sku,price,currency,price_basis,url`; optional `sale_price,sale_start,sale_end,market,variant`. The currently supported variant contract is an exact match to `Product.size`.

### Output

One dataset row per supplied SKU contains classification, expected/observed prices, declared scope, reason, recommended action, evidence provenance, capture/evaluation times, exact JSON-LD script/pointer and markup SHA256. The key-value store also contains `SUMMARY`, reusable `BASELINE`, and spreadsheet-safe `audit.csv`.

| Classification | Meaning |
|---|---|
| `new_issue` | A new or changed difference against an earlier compatible known baseline |
| `existing_issue` | The same difference existed previously |
| `unbaselined_issue` | A difference exists, but its age cannot be established |
| `resolved` | An earlier difference now matches |
| `clear` | Supported declared static fields match in the labelled evidence mode |
| `needs_review` | Evidence is missing, ambiguous, malformed or incomparable |
| `baseline_incomparable` | Declared identity/feed scope changed, or the baseline is not strictly earlier |

Uncertainty never becomes a green price verdict. Multiple offers, missing SKU/currency, conflicting tax declarations, unsupported variants and invalid offer validity timestamps require review. Changes in decimal formatting or JSON-LD position alone do not create a new issue.

### Optional bounded public fetch

`fetchPublicUrls` is off by default. Enable it only for public pages you are authorized to access. It attempts at most **3 product pages / 6 HTTP requests total**, including robots checks. Each HTTP worker has a full 10-second deadline; page responses are limited to2MB and robots to100KB. Only standard HTTP(S) ports and validated public IP addresses are accepted. There are no redirects, cookies, proxies, browser rendering or anti-bot bypasses. Robots-unavailable, blocked and unsupported content remain uncertain.

For a larger catalog or a site requiring JavaScript, supply authorized snapshots or split a suitable workflow into bounded runs. This Actor does not automatically acquire a complete merchant feed.

### Limits and interpretation

- Static JSON-LD and declared feed/promotion terms only. Visible page price, shipping, checkout, stock, customer discounts and geography are outside the check.
- `clear` means supported static fields match; it does not mean Google has approved the product or that tax compliance is verified.
- Simulation is hypothetical. Real before/after evidence needs fresh captures from separate runs.
- Missing or ambiguous data produces review records, not guessed prices.
- Input validation can reject the run before any dataset rows are produced.

### Connect a workflow

Run from Apify Console, the Actor API, or your existing Apify integration. Retrieve the dataset and route `new_issue`/`resolved` rows into your review process. Save `BASELINE` for the next compatible observation. Use the CSV for a manual small-batch check.

### Support

Open an issue on the Actor's Issues tab with a minimal anonymized example and expected classification. Do not include customer data, credentials or private URLs. This is an initial release; customer demand and savings are not claimed.

# Actor input Schema

## `feed` (type: `array`):

1–25 exact-SKU feed rows. Use this or CSV, not both.

## `feedCsv` (type: `string`):

CSV headers: sku,price,currency,price_basis,url; optional sale_price,sale_start,sale_end,market,variant. Decimal prices; timezone required.

## `snapshots` (type: `array`):

Optional records keyed by unique SKU, overriding html/url/provenance on matching feed row.

## `baseline` (type: `array`):

Exact BASELINE array from earlier run, unchanged scope and strictly earlier evaluation time. Empty baseline never claims new issue.

## `checkedAt` (type: `string`):

In simulation, explicit hypothetical evaluation time. In observed mode, if supplied must equal snapshot captured_at; omit to use each capture time.

## `fetchPublicUrls` (type: `boolean`):

Default off. Up to3 pages/6 HTTPrequests total incl robots, no auth/redirects/browser/proxy, max2MB each. Live fetch cannot use checkedAt.

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

observed requires exact captured_at; default. simulation explicitly re-evaluates supplied markup at checkedAt and never represents a real before/after observation.

## Actor input object example

```json
{
  "feed": [
    {
      "sku": "A",
      "price": "100",
      "currency": "GBP",
      "sale_price": "80",
      "sale_start": "2026-10-06T06:00:00+00:00",
      "sale_end": "2026-10-06T07:00:00+00:00",
      "price_basis": "tax_inclusive",
      "url": "https://shop.example/product/a",
      "html": "<script type=\"application/ld+json\">{\"@type\": \"Product\", \"sku\": \"A\", \"offers\": {\"@type\": \"Offer\", \"price\": \"80\", \"priceCurrency\": \"GBP\"}}</script>",
      "evidence_label": "synthetic"
    }
  ],
  "baseline": [
    {
      "sku": "A",
      "url": "https://shop.example/product/a",
      "checked_at": "2026-10-06T06:59:59Z",
      "status": "match",
      "reason": "Declared price terms match",
      "pointer": "0:/offers",
      "expected": "80",
      "observed": "80",
      "currency": "GBP",
      "price_basis": "tax_inclusive",
      "scope_hash": "33a739a82f002a69e1981e15dc9b7c3db68f71019343412c0753c3ce4cf9e1b6",
      "evidence_label": "synthetic",
      "captured_at": null,
      "html_sha256": "2f629aa705be8fca4c85dba1f51fa9706e050367e5cc212a952d42999abd6d1b",
      "evaluation_mode": "simulation",
      "basis_evidence": "caller-declared unless explicit JSON-LD tax flag; not independently verified",
      "action": "Declared static fields agree; visible price, checkout and Merchant Center approval untested.",
      "classification": "clear"
    }
  ],
  "checkedAt": "2026-10-06T07:00:00Z",
  "fetchPublicUrls": false,
  "mode": "simulation"
}
```

# Actor output Schema

## `results` (type: `string`):

No description

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

No description

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

No description

## `baseline` (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 = {
    "feed": [
        {
            "sku": "A",
            "price": "100",
            "currency": "GBP",
            "sale_price": "80",
            "sale_start": "2026-10-06T06:00:00+00:00",
            "sale_end": "2026-10-06T07:00:00+00:00",
            "price_basis": "tax_inclusive",
            "url": "https://shop.example/product/a",
            "html": "<script type=\"application/ld+json\">{\"@type\": \"Product\", \"sku\": \"A\", \"offers\": {\"@type\": \"Offer\", \"price\": \"80\", \"priceCurrency\": \"GBP\"}}</script>",
            "evidence_label": "synthetic"
        }
    ],
    "baseline": [
        {
            "sku": "A",
            "url": "https://shop.example/product/a",
            "checked_at": "2026-10-06T06:59:59Z",
            "status": "match",
            "reason": "Declared price terms match",
            "pointer": "0:/offers",
            "expected": "80",
            "observed": "80",
            "currency": "GBP",
            "price_basis": "tax_inclusive",
            "scope_hash": "33a739a82f002a69e1981e15dc9b7c3db68f71019343412c0753c3ce4cf9e1b6",
            "evidence_label": "synthetic",
            "captured_at": null,
            "html_sha256": "2f629aa705be8fca4c85dba1f51fa9706e050367e5cc212a952d42999abd6d1b",
            "evaluation_mode": "simulation",
            "basis_evidence": "caller-declared unless explicit JSON-LD tax flag; not independently verified",
            "action": "Declared static fields agree; visible price, checkout and Merchant Center approval untested.",
            "classification": "clear"
        }
    ],
    "checkedAt": "2026-10-06T07:00:00Z",
    "fetchPublicUrls": false,
    "mode": "simulation"
};

// Run the Actor and wait for it to finish
const run = await client.actor("wwhynot/merchant-sale-transition-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 = {
    "feed": [{
            "sku": "A",
            "price": "100",
            "currency": "GBP",
            "sale_price": "80",
            "sale_start": "2026-10-06T06:00:00+00:00",
            "sale_end": "2026-10-06T07:00:00+00:00",
            "price_basis": "tax_inclusive",
            "url": "https://shop.example/product/a",
            "html": "<script type=\"application/ld+json\">{\"@type\": \"Product\", \"sku\": \"A\", \"offers\": {\"@type\": \"Offer\", \"price\": \"80\", \"priceCurrency\": \"GBP\"}}</script>",
            "evidence_label": "synthetic",
        }],
    "baseline": [{
            "sku": "A",
            "url": "https://shop.example/product/a",
            "checked_at": "2026-10-06T06:59:59Z",
            "status": "match",
            "reason": "Declared price terms match",
            "pointer": "0:/offers",
            "expected": "80",
            "observed": "80",
            "currency": "GBP",
            "price_basis": "tax_inclusive",
            "scope_hash": "33a739a82f002a69e1981e15dc9b7c3db68f71019343412c0753c3ce4cf9e1b6",
            "evidence_label": "synthetic",
            "captured_at": None,
            "html_sha256": "2f629aa705be8fca4c85dba1f51fa9706e050367e5cc212a952d42999abd6d1b",
            "evaluation_mode": "simulation",
            "basis_evidence": "caller-declared unless explicit JSON-LD tax flag; not independently verified",
            "action": "Declared static fields agree; visible price, checkout and Merchant Center approval untested.",
            "classification": "clear",
        }],
    "checkedAt": "2026-10-06T07:00:00Z",
    "fetchPublicUrls": False,
    "mode": "simulation",
}

# Run the Actor and wait for it to finish
run = client.actor("wwhynot/merchant-sale-transition-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 '{
  "feed": [
    {
      "sku": "A",
      "price": "100",
      "currency": "GBP",
      "sale_price": "80",
      "sale_start": "2026-10-06T06:00:00+00:00",
      "sale_end": "2026-10-06T07:00:00+00:00",
      "price_basis": "tax_inclusive",
      "url": "https://shop.example/product/a",
      "html": "<script type=\\"application/ld+json\\">{\\"@type\\": \\"Product\\", \\"sku\\": \\"A\\", \\"offers\\": {\\"@type\\": \\"Offer\\", \\"price\\": \\"80\\", \\"priceCurrency\\": \\"GBP\\"}}</script>",
      "evidence_label": "synthetic"
    }
  ],
  "baseline": [
    {
      "sku": "A",
      "url": "https://shop.example/product/a",
      "checked_at": "2026-10-06T06:59:59Z",
      "status": "match",
      "reason": "Declared price terms match",
      "pointer": "0:/offers",
      "expected": "80",
      "observed": "80",
      "currency": "GBP",
      "price_basis": "tax_inclusive",
      "scope_hash": "33a739a82f002a69e1981e15dc9b7c3db68f71019343412c0753c3ce4cf9e1b6",
      "evidence_label": "synthetic",
      "captured_at": null,
      "html_sha256": "2f629aa705be8fca4c85dba1f51fa9706e050367e5cc212a952d42999abd6d1b",
      "evaluation_mode": "simulation",
      "basis_evidence": "caller-declared unless explicit JSON-LD tax flag; not independently verified",
      "action": "Declared static fields agree; visible price, checkout and Merchant Center approval untested.",
      "classification": "clear"
    }
  ],
  "checkedAt": "2026-10-06T07:00:00Z",
  "fetchPublicUrls": false,
  "mode": "simulation"
}' |
apify call wwhynot/merchant-sale-transition-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,wwhynot/merchant-sale-transition-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/Z3FVPhRVCX6xxm0Iw/builds/6GIg1BBHa8ryFFPZa/openapi.json
