# Storefront SEO Regression Checker | HTML Release Diff (`cygnix_labs/cygnix-storefront-release-proof`) Actor

Compare supplied before-and-after storefront HTML for canonical, noindex and Product/Offer JSON-LD changes. Get PASS, FAIL or HOLD plus JSON, CSV and HTML evidence. Up to 20 page pairs per report. $0.05 per completed report. No live crawling.

- **URL**: https://apify.com/cygnix_labs/cygnix-storefront-release-proof.md
- **Developed by:** [Umair Saleem Mohammad Saleem](https://apify.com/cygnix_labs) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$50.00 / 1,000 completed reports

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

## Cygnix Storefront Release Proof

Check storefront HTML before a release. Compare supplied baseline and release captures for up to 20 storefront product pages, within the byte and schema limits below. Find changes to canonical URLs, indexing directives and supported Product/Offer JSON-LD, then get a coverage-aware PASS, FAIL or HOLD result with JSON, CSV and HTML evidence for review.

Use this when your release pipeline or capture tool already produces HTML captures and HTTP status observations. You supply both capture sets. This Actor does not visit the URLs, render JavaScript, collect a baseline or change your storefront.

### What it checks

- Changes to canonical URLs, resolved against each supplied page URL
- `noindex` or robots `none` in robots/googlebot meta tags, including directives already present in the baseline
- Changes to supported Product/Offer identity, price, currency and availability data
- Missing page pairs, missing or unusable HTTP status, malformed JSON-LD and unsupported evidence

Product data must be present in JSON-LD in the supplied HTML. Each Product needs a unique `@id` or string SKU and inline Offer data. Multiple Offers need unique identities; one anonymous Offer is supported. Each page needs exactly one valid canonical link. Equivalent decimal trailing zeros and JSON-LD ordering do not create changes.

### Quick start

1. Open the Input tab. The prefilled values are a synthetic one-page demonstration using the fictitious `store.example` domain. You can also paste the complete JSON below.
2. Check the current pricing panel. Start a fresh run with 512 MB memory and a 120-second timeout explicitly selected to match the verified configuration; do not assume the current defaults match. Set a finite spending limit of at least $0.05.
3. Open the completed dataset item for the decision, coverage counts and report links.
4. Download the JSON, CSV or HTML evidence from the run’s key-value store. The `OUTPUT` record lists the artifact keys. Use the signed-in Console if a direct report link requires authorization.

In the verified build 0.0.9 test, this unchanged demonstration returned PASS, `complete=true`, one compared page and one completed-report dataset item. For your own comparison, replace all demo IDs, URLs, captures and the observation time.

### Input fields

- `baselineId`: your reference for the baseline capture set
- `releaseId`: your reference for the release capture set
- `observedAt`: supplied ISO 8601 observation time with a timezone
- `expectedUrls`: 1–20 unique HTTP(S) URLs to compare
- `before` and `after`: capture arrays with `url`, `html` and observed integer HTTP `status`
- `severity` (optional): set `canonical`, `noindex` or `products` to `error` or `warning`. The default is `error`. Overrides do not make incomplete evidence complete.

Use the same exact URL strings in the expected inventory and capture arrays. Duplicate or unexpected URLs are rejected. Missing captures or missing HTTP status produce HOLD. URLs must not include credentials or fragments.

#### Complete synthetic example

```json
{
  "baselineId": "baseline-1",
  "releaseId": "release-2",
  "observedAt": "2026-10-10T23:00:00Z",
  "expectedUrls": [
    "https://store.example/products/a"
  ],
  "before": [
    {
      "url": "https://store.example/products/a",
      "html": "<html><link rel=\"canonical\" href=\"https://store.example/products/a\"><meta name=\"robots\" content=\"index,follow\"><script type=\"application/ld+json\">{\"@type\": \"Product\", \"@id\": \"a\", \"sku\": \"SKU-a\", \"offers\": {\"@type\": \"Offer\", \"price\": \"19.90\", \"priceCurrency\": \"USD\", \"availability\": \"https://schema.org/InStock\"}}</script></html>",
      "status": 200
    }
  ],
  "after": [
    {
      "url": "https://store.example/products/a",
      "html": "<html><link rel=\"canonical\" href=\"https://store.example/products/a\"><meta name=\"robots\" content=\"index,follow\"><script type=\"application/ld+json\">{\"@type\": \"Product\", \"@id\": \"a\", \"sku\": \"SKU-a\", \"offers\": {\"@type\": \"Offer\", \"price\": \"19.90\", \"priceCurrency\": \"USD\", \"availability\": \"https://schema.org/InStock\"}}</script></html>",
      "status": 200
    }
  ]
}
```

### Understanding the result

- **PASS:** every expected page was compared, with no error-severity findings. Warning findings can still be present.
- **FAIL:** every expected page was compared, and at least one error-severity finding was found. The Actor run succeeds when this complete report is delivered.
- **HOLD:** the comparison is incomplete because evidence is missing, blocked, malformed or unsupported. No completed-report dataset item is appended, and the Actor run fails visibly.

Always check `decision`, `complete` and coverage. A successful Actor run can contain a complete FAIL result. Supplied non-2xx statuses, including redirects, 401/403/429, 404/410 and server errors, prevent a complete comparison.

A completed PASS or FAIL produces one compact dataset summary for the entire comparison, with links to full reports:

- **JSON:** structured coverage, page results, findings, before/after values and provenance
- **CSV:** page/finding rows for spreadsheet review
- **HTML:** a standalone readable report

Ordinary evidence-related HOLD results retain diagnostic report artifacts. Size-limit rejection produces a `LIMIT-ERROR` diagnostic instead. Invalid input can be rejected before any report exists. Evidence is never silently truncated to fit.

`OUTPUT` confirms that the report artifacts were persisted. Dataset delivery is tracked separately in `NATIVE-REPORT-ATTEMPT`; successful delivery records `DELIVERED`. If a run fails after writing artifacts, inspect its existing dataset and attempt record before starting another run.

Reports retain `mode=offline-fixture-proof` and `cloudValidated=false`. These fields describe the supplied-evidence report and do not certify a live storefront. The cloud-delivery tests described below were verified separately.

### Supported limits

- Up to 20 expected page pairs and 20 captures per side
- Up to 100,000 UTF-8 bytes per HTML capture
- Up to 2,000,000 combined before/after HTML bytes
- Up to 2,500,000 bytes when the Actor serializes the decoded input for its size check
- Up to 4,000,000 bytes for the rendered JSON report and 8,000,000 bytes per rendered artifact
- Up to 5,000 JSON-LD nodes and 40 nesting levels per page

The combined HTML limit applies even when every individual capture meets its own limit. Report expansion can also exceed output limits. Split large comparisons into smaller independent runs. The input size check happens after input download and decoding; it does not cap that initial allocation.

### Limitations and safe use

The report depends on your supplied HTML and HTTP observations. It does not authenticate captures or prove what a live site served. Keep and version your source captures separately; this Actor does not maintain or update a baseline.

Unsupported checks include checkout behavior, visual layout, JavaScript execution, link crawling, HTTP X-Robots-Tag headers, microdata/RDFa, search rankings and search-engine eligibility. Referenced Offers, AggregateOffer, ambiguous identities and unsupported structured-data forms produce HOLD rather than a partial PASS.

Use fresh run-scoped storage and one process. Shared/named storage overrides, resumed runs and automatic restart recovery are unsupported. A new run is a separate comparison. Delivery uses one append attempt; a lost response can leave an uncertain outcome. There is no exactly-once or cross-run deduplication guarantee. Review existing outputs before rerunning an interrupted comparison, and repair the evidence before retrying HOLD.

Only process captures you are entitled to use. Remove passwords, access tokens, customer/account details and payment information before uploading captures or posting support examples.

### Pricing and verified coverage

Pricing is $0.05 per completed release report, whether the result is PASS or FAIL. Each completed report creates one default-dataset item. HOLD, invalid-input and size-limit rejections create no report item and incur no report fee. There is no startup fee. Platform usage during the run is included in the completion price; post-run dataset interactions may incur standard Apify platform costs. Set a finite run spending limit of at least $0.05. Check the current pricing display before starting.

Build 0.0.9 was tested on Apify on 13 October 2026 at 512 MB memory, a 120-second timeout and a $0.05 spending cap. The synthetic PASS, complete FAIL, blocked HOLD, oversize rejection and 20-page near-limit FAIL cases produced the expected artifacts and dataset-item counts. These tests verify the supplied-evidence comparison and cloud delivery for those cases; they do not verify live-site correctness, actual buyer charges or revenue.

### Common questions

**Can I enter a store URL and get an audit?**
Supply captured HTML and observed HTTP status values for both versions. URLs identify the captures; this Actor does not retrieve them.

**Does it work with Shopify or WooCommerce?**
It evaluates supported HTML and JSON-LD rather than connecting to a commerce platform. Each expected page needs matching before/after evidence, exactly one valid canonical, and supported Product/Offer structure. Check the identity and Offer requirements above before processing a full batch.

**Why did a run succeed while the result says FAIL?**
Execution success means the completed report was delivered. FAIL is the report's finding about the supplied comparison. Always check decision, completeness and page coverage together.

### Support

Open an issue in this Actor’s Issues tab. Include the decision or error message, approximate input size and a minimal sanitized example that reproduces the problem. Do not post credentials or confidential captures.

# Actor input Schema

## `baselineId` (type: `string`):

Your reference for the baseline capture set. The prefilled baseline-1 is a demo ID; the Actor never updates your baseline.

## `releaseId` (type: `string`):

Your reference for the release capture set. The prefilled release-2 is a demo ID.

## `observedAt` (type: `string`):

Your supplied observation time in ISO 8601 format with a timezone. The prefilled time belongs to the synthetic example; this is not a live fetch timestamp.

## `expectedUrls` (type: `array`):

Supply 1–20 unique HTTP(S) URLs matching the capture arrays exactly. Prefilled store.example URLs are fictitious and will not be fetched.

## `before` (type: `array`):

Baseline captures containing url, html and observed HTTP status. Prefilled HTML is synthetic. Maximum 20 captures, 100,000 UTF-8 bytes each, and 2,000,000 HTML bytes across both sides.

## `after` (type: `array`):

Release captures containing url, html and observed HTTP status. Prefilled HTML is synthetic. Maximum 20 captures, 100,000 UTF-8 bytes each, and 2,000,000 HTML bytes across both sides.

## `severity` (type: `object`):

Optional canonical, noindex and products rule overrides, each error or warning. Incomplete evidence still yields HOLD.

## Actor input object example

```json
{
  "baselineId": "baseline-1",
  "releaseId": "release-2",
  "observedAt": "2026-10-10T23:00:00Z",
  "expectedUrls": [
    "https://store.example/products/a"
  ],
  "before": [
    {
      "url": "https://store.example/products/a",
      "html": "<html><link rel=\"canonical\" href=\"https://store.example/products/a\"><meta name=\"robots\" content=\"index,follow\"><script type=\"application/ld+json\">{\"@type\": \"Product\", \"@id\": \"a\", \"sku\": \"SKU-a\", \"offers\": {\"@type\": \"Offer\", \"price\": \"19.90\", \"priceCurrency\": \"USD\", \"availability\": \"https://schema.org/InStock\"}}</script></html>",
      "status": 200
    }
  ],
  "after": [
    {
      "url": "https://store.example/products/a",
      "html": "<html><link rel=\"canonical\" href=\"https://store.example/products/a\"><meta name=\"robots\" content=\"index,follow\"><script type=\"application/ld+json\">{\"@type\": \"Product\", \"@id\": \"a\", \"sku\": \"SKU-a\", \"offers\": {\"@type\": \"Offer\", \"price\": \"19.90\", \"priceCurrency\": \"USD\", \"availability\": \"https://schema.org/InStock\"}}</script></html>",
      "status": 200
    }
  ],
  "severity": {}
}
```

# Actor output Schema

## `manifest` (type: `string`):

Names the persisted JSON, CSV and HTML artifacts. Dataset delivery is recorded separately in the delivery attempt record.

## `files` (type: `string`):

No description

## `completedReports` (type: `string`):

One compact summary linking to full JSON, CSV and HTML evidence for a completed PASS or FAIL. HOLD, invalid input and size-limit rejection produce no completed-report item. See the Store pricing display for current charges; this output is not a billing receipt.

## `attempt` (type: `string`):

Tracks this run’s delivery attempt. Interrupted or uncertain attempts are not automatically resumed; inspect existing outputs before starting another run.

## `limitDiagnostic` (type: `string`):

Present when an operational input/output size limit is exceeded. No evidence is truncated and no completed-report item is appended.

# 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 = {
    "baselineId": "baseline-1",
    "releaseId": "release-2",
    "observedAt": "2026-10-10T23:00:00Z",
    "expectedUrls": [
        "https://store.example/products/a"
    ],
    "before": [
        {
            "url": "https://store.example/products/a",
            "html": "<html><link rel=\"canonical\" href=\"https://store.example/products/a\"><meta name=\"robots\" content=\"index,follow\"><script type=\"application/ld+json\">{\"@type\": \"Product\", \"@id\": \"a\", \"sku\": \"SKU-a\", \"offers\": {\"@type\": \"Offer\", \"price\": \"19.90\", \"priceCurrency\": \"USD\", \"availability\": \"https://schema.org/InStock\"}}</script></html>",
            "status": 200
        }
    ],
    "after": [
        {
            "url": "https://store.example/products/a",
            "html": "<html><link rel=\"canonical\" href=\"https://store.example/products/a\"><meta name=\"robots\" content=\"index,follow\"><script type=\"application/ld+json\">{\"@type\": \"Product\", \"@id\": \"a\", \"sku\": \"SKU-a\", \"offers\": {\"@type\": \"Offer\", \"price\": \"19.90\", \"priceCurrency\": \"USD\", \"availability\": \"https://schema.org/InStock\"}}</script></html>",
            "status": 200
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cygnix_labs/cygnix-storefront-release-proof").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 = {
    "baselineId": "baseline-1",
    "releaseId": "release-2",
    "observedAt": "2026-10-10T23:00:00Z",
    "expectedUrls": ["https://store.example/products/a"],
    "before": [{
            "url": "https://store.example/products/a",
            "html": "<html><link rel=\"canonical\" href=\"https://store.example/products/a\"><meta name=\"robots\" content=\"index,follow\"><script type=\"application/ld+json\">{\"@type\": \"Product\", \"@id\": \"a\", \"sku\": \"SKU-a\", \"offers\": {\"@type\": \"Offer\", \"price\": \"19.90\", \"priceCurrency\": \"USD\", \"availability\": \"https://schema.org/InStock\"}}</script></html>",
            "status": 200,
        }],
    "after": [{
            "url": "https://store.example/products/a",
            "html": "<html><link rel=\"canonical\" href=\"https://store.example/products/a\"><meta name=\"robots\" content=\"index,follow\"><script type=\"application/ld+json\">{\"@type\": \"Product\", \"@id\": \"a\", \"sku\": \"SKU-a\", \"offers\": {\"@type\": \"Offer\", \"price\": \"19.90\", \"priceCurrency\": \"USD\", \"availability\": \"https://schema.org/InStock\"}}</script></html>",
            "status": 200,
        }],
}

# Run the Actor and wait for it to finish
run = client.actor("cygnix_labs/cygnix-storefront-release-proof").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 '{
  "baselineId": "baseline-1",
  "releaseId": "release-2",
  "observedAt": "2026-10-10T23:00:00Z",
  "expectedUrls": [
    "https://store.example/products/a"
  ],
  "before": [
    {
      "url": "https://store.example/products/a",
      "html": "<html><link rel=\\"canonical\\" href=\\"https://store.example/products/a\\"><meta name=\\"robots\\" content=\\"index,follow\\"><script type=\\"application/ld+json\\">{\\"@type\\": \\"Product\\", \\"@id\\": \\"a\\", \\"sku\\": \\"SKU-a\\", \\"offers\\": {\\"@type\\": \\"Offer\\", \\"price\\": \\"19.90\\", \\"priceCurrency\\": \\"USD\\", \\"availability\\": \\"https://schema.org/InStock\\"}}</script></html>",
      "status": 200
    }
  ],
  "after": [
    {
      "url": "https://store.example/products/a",
      "html": "<html><link rel=\\"canonical\\" href=\\"https://store.example/products/a\\"><meta name=\\"robots\\" content=\\"index,follow\\"><script type=\\"application/ld+json\\">{\\"@type\\": \\"Product\\", \\"@id\\": \\"a\\", \\"sku\\": \\"SKU-a\\", \\"offers\\": {\\"@type\\": \\"Offer\\", \\"price\\": \\"19.90\\", \\"priceCurrency\\": \\"USD\\", \\"availability\\": \\"https://schema.org/InStock\\"}}</script></html>",
      "status": 200
    }
  ]
}' |
apify call cygnix_labs/cygnix-storefront-release-proof --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cygnix_labs/cygnix-storefront-release-proof"
        }
    }
}
```

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/mjTcYgdkijrQIepyE/builds/4BtwypFYgooetJPvh/openapi.json
