# Collection Page Merchandising Auditor (`junipr/collection-page-merchandising-auditor`) Actor

Audit collection/category pages for product count, sort/filter signals, merchandising blocks, out-of-stock products, duplicate tiles, price ranges,

- **URL**: https://apify.com/junipr/collection-page-merchandising-auditor.md
- **Developed by:** [junipr](https://apify.com/junipr) (community)
- **Categories:** E-commerce, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $11.50 / 1,000 collection page analyzeds

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

## Collection Page Merchandising Auditor

Audit public ecommerce collection pages at product-card level. The Actor inventories cards and reports duplicate tiles, promoted out-of-stock products, missing links/prices/images, filters, sort options, price range, and pagination evidence.

It is designed for ecommerce merchandisers, SEO teams, category managers, and agencies. Runs are deterministic for supplied HTML, bounded for explicit URL inputs, and produce schema-valid dataset objects plus downloadable report artifacts.

### What you get

- Collection URL and name
- Product name, URL, card position, numeric price, and availability
- Total/visible product counts
- Filter count and sort options
- Out-of-stock count and duplicate group
- Numeric page price range
- Pagination status and merchandising issue labels

Every row includes source URL/type, page title, record type/name, issue code, severity, an optional bounded evidence snippet, a recommendation, canonical URL, HTTP status, and scan timestamp. Missing evidence remains `null` or an explicit diagnostic state; it is not replaced with invented values.

### How it works

1. Read supplied collection HTML or bounded explicit collection URLs.
2. Optionally follow same-domain `rel=next` links within `maxDepth` and `maxCollectionPages`.
3. Extract visible product cards, names, links, numeric prices, images, and availability.
4. Calculate page-level product count, visible count, filter count, sort options, out-of-stock count, and price range.
5. Emit one object per product card with concrete issue labels; empty collections receive an explicit diagnostic object.

The Actor prefers user-supplied HTML because it is deterministic and inexpensive. Live HTTP retrieval is bounded by page limits, timeouts, delays, and an optional domain allowlist. Loopback and private IPv4 targets are rejected. Broad link discovery is disabled.

### Inputs

| Field | Purpose |
| --- | --- |
| `collectionUrls` | Explicit public category/collection URLs. |
| `htmlInputs` | Preferred captured collection HTML. |
| `followPagination` | Enables only bounded same-domain `rel=next` traversal. |
| `maxCollectionPages / maxDepth` | Page and pagination bounds. |
| `maxProductCards / maxProductsPerCollection` | Card caps. |
| `extractFilters / detectDuplicateTiles` | Optional audit controls. |
| `maxRules` | Maximum issue labels evaluated per card. |
| `includeEvidence` | Set false to omit evidence snippets from dataset rows. |
| `includeRawSnapshots` | Opt in to bounded raw HTML records in the key-value store. |
| `timeoutMs` / `requestDelayMs` | Bound each public request and pacing. |
| `maxChargeUsd` | Stops before a paid event would exceed the run budget. |
| `includeReport` | Controls paid report generation and key-value-store artifacts. |

`startUrls` remains available for task compatibility. Actor-specific URL fields are clearer for new integrations. `sitemapUrls` is accepted as workflow metadata but does not start a broad sitemap crawl.

### Input example

```json
{"htmlInputs":[{"sourceUrl":"https://shop.example/collections/storage","html":"<html>...public collection HTML...</html>","httpStatus":200}],"followPagination":false,"extractFilters":true,"detectDuplicateTiles":true,"maxCollectionPages":25,"maxProductCards":250,"maxRules":10,"maxChargeUsd":35}
```

For repeatable automation, store baseline or captured HTML outside the Actor and send only the bounded records needed for the current comparison. Avoid embedding credentials, session cookies, private customer data, or authenticated page captures.

### Dataset contract

The dataset schema is defined in `.actor/dataset_schema.json`. Each paid output is pushed as an individual object. Arrays are never submitted as a single dataset item. This makes API pagination, CSV export, and downstream validation predictable.

Common diagnostic conventions:

- `*_ok`: observable fields passed the implemented checks.
- `*_required` or `*_no_*`: required source evidence was absent.
- `*_source_unavailable`: the supplied/fetched source was blocked or unavailable.
- Actor-specific issue codes identify the exact mismatch, change, merchandising issue, review condition, or variant defect.
- HTTP status `0` means the public request failed before a response; `null` means no request was attempted.

Evidence snippets are bounded and can be disabled. Raw snapshots are off by default and, when enabled, are capped before storage.

### Reports

When `includeReport` is true and its charge is accepted, the Actor writes:

- a Markdown executive summary;
- a CSV containing the schema-aligned records;
- a JSON issue list;
- bounded evidence excerpts;
- run metadata with counts, issue codes, pricing mode, and consumed input fields.

The billing-status record reports accepted spend, configured maximum, and the number of rows pushed. If a charge is rejected, partially accepted, or reaches an Apify event limit, the Actor stops before the corresponding paid output.

### Pay-per-event pricing

| Event | Price | When charged |
| --- | ---: | --- |
| `actor-start` | $0.03500 | Once after initialization. |
| `collection-page-analyzed` | $0.01150 | Once per distinct collection page. |
| `product-card-record-extracted` | $0.00650 | For each extracted product card. |
| `merchandising-issue-detected` | $0.01150 | For each distinct card issue result. |
| `executive-report-generated` | $0.40000 | When report output is requested. |

Apify platform-usage pass-through is off. The configured event prices reserve the applicable platform and discount margin. `maxChargeUsd` is enforced before every event, including actor start and report output. A value of zero therefore produces no paid dataset output.

Billing sequence:

1. Initialize the Actor.
2. Accept the `actor-start` event.
3. Accept the distinct source/record event.
4. Accept any actor-specific comparison, extraction, theme, issue, or change event.
5. Push one validated dataset object.
6. Accept report events before storing paid report artifacts.
7. Write billing status and exit.

The Actor exits in a `finally` path. Dataset push failures are recorded as `dataset-push-failed` before the error is rethrown, which keeps failures observable and prevents a hanging run.

### Limits and interpretation

- Duplicate detection uses normalized product URL, or product name when a URL is absent.
- An out-of-stock card is flagged as a merchandising observation, not as proof that it should be removed.
- Filter and sorting output reflects observable controls only; backend facet correctness is not asserted.
- The Actor does not add products to carts or trigger storefront actions.

Additional safeguards:

- Only `http:` and `https:` URLs are eligible for fetching.
- Localhost, loopback, link-local, and common private IPv4 ranges are rejected.
- `allowedDomains` can restrict retrieval to exact hosts and their subdomains.
- Response HTML is truncated before analysis.
- Pagination, where supported, stays on the same hostname and within configured depth/page limits.
- No credentials or private API access are requested.
- Results describe observable source evidence at scan time; they are not legal, financial, inventory, or compliance guarantees.

### Running on Apify

Use the input editor, API, task, webhook, schedule, or integration of your choice. Keep automated runs small at first, inspect the dataset and billing-status record, then increase caps deliberately.

A successful default run uses embedded public-page-shaped HTML, performs no network request, finishes quickly, and writes a non-empty dataset. For production monitoring, replace that input with your own public URLs or snapshots and preserve prior snapshots externally where comparisons require them.

### Running from source

```bash
npm install
npm test
npm run build
npm run smoke
```

The smoke command clears package-local storage, executes the real entrypoint with `examples/input.tiny.json`, writes one object per dataset file, and creates every configured report artifact. The test suite also checks schema types, Store defaults, caps, SSRF guards, pricing events, charge limits, real entrypoint behavior, and actor-specific extraction cases.

### Troubleshooting

#### The run returns a required-input diagnostic

Confirm that the actor-specific source field is populated and that the HTML record contains `sourceUrl`, `html` or `currentHtml`, and an appropriate HTTP status. Comparison Actors also need a usable baseline.

#### The source is unavailable

Check the URL, public accessibility, domain allowlist, timeout, and robots/site policies. Prefer a captured public HTML snapshot when stable automation matters.

#### The dataset stops before the configured cap

Inspect the billing-status record. `maxChargeUsd`, an Apify event limit, or a partial charge can stop output before a row is pushed.

#### Expected fields are null

The field was not observable in the supplied source or was disabled by input. Review the evidence snippet and source HTML. Do not treat null as a negative factual claim.

#### Interactive content is missing

Supply server-rendered HTML or an authorized captured snapshot. This package intentionally analyzes static, observable evidence and does not claim browser-only interactions it did not perform.

# Actor input Schema

## `startUrls` (type: `array`):

Explicit pages used only when no HTML snapshots are supplied.

## `sitemapUrls` (type: `array`):

Accepted for workflow compatibility; this actor does not perform broad sitemap crawling.

## `htmlInputs` (type: `array`):

Deterministic records with sourceUrl, html/currentHtml, and optional httpStatus.

## `allowedDomains` (type: `array`):

Optional allowlist enforced for live URL hydration.

## `maxPages` (type: `integer`):

Maximum pages to fetch in one run.

## `maxDepth` (type: `integer`):

Only the collection auditor can follow same-domain rel=next links.

## `includeEvidence` (type: `boolean`):

Include short source evidence snippets in output rows.

## `includeRawSnapshots` (type: `boolean`):

Opt in to raw HTML artifacts in key-value storage.

## `requestDelayMs` (type: `integer`):

Delay in milliseconds between outbound page requests.

## `timeoutMs` (type: `integer`):

Maximum time in milliseconds to wait for a page request.

## `userAgentMode` (type: `string`):

User agent profile to use for public page requests.

## `collectionUrls` (type: `array`):

Public collection or category page URLs to inspect.

## `followPagination` (type: `boolean`):

Follow bounded same-domain pagination when enabled.

## `maxProductsPerCollection` (type: `integer`):

Maximum product cards to inspect per collection.

## `extractFilters` (type: `boolean`):

Extract visible filters and facets when present.

## `detectDuplicateTiles` (type: `boolean`):

Detect duplicate product tiles within a collection page.

## `outOfStockRules` (type: `array`):

Rules used to identify promoted out-of-stock products.

## `maxCollectionPages` (type: `integer`):

Maximum collection pages to inspect.

## `maxProductCards` (type: `integer`):

Maximum product-card records to emit.

## `maxRules` (type: `integer`):

Maximum merchandising rules to evaluate.

## `maxChargeUsd` (type: `number`):

Maximum estimated PPE spend before the actor exits gracefully.

## `includeReport` (type: `boolean`):

Write the markdown summary report to the run key-value store.

## Actor input object example

```json
{
  "startUrls": [],
  "sitemapUrls": [],
  "htmlInputs": [
    {
      "sourceId": "healthy-collection",
      "sourceUrl": "https://shop.example/collections/storage",
      "title": "External Storage",
      "html": "<!doctype html><html><head><title>External Storage Collection</title><link rel=\"canonical\" href=\"https://shop.example/collections/storage\"></head><body><h1>External Storage</h1><div class=\"filters\"><label>Capacity <select name=\"filter-capacity\"><option>1 TB</option><option>2 TB</option></select></label></div><select name=\"sort\"><option>Featured</option><option>Price low to high</option></select><article class=\"product-card\"><a href=\"/products/northstar-drive\"><h2>Northstar Backup Drive 2TB</h2><img src=\"/images/northstar.jpg\" alt=\"Northstar\"><span class=\"price\">$129.00</span><span class=\"availability\">In stock</span></a></article></body></html>",
      "httpStatus": 200
    }
  ],
  "allowedDomains": [],
  "maxPages": 3,
  "maxDepth": 1,
  "includeEvidence": true,
  "includeRawSnapshots": false,
  "requestDelayMs": 0,
  "timeoutMs": 15000,
  "userAgentMode": "standard",
  "collectionUrls": [],
  "followPagination": false,
  "maxProductsPerCollection": 100,
  "extractFilters": true,
  "detectDuplicateTiles": true,
  "outOfStockRules": [
    "flag promoted out-of-stock cards"
  ],
  "maxCollectionPages": 25,
  "maxProductCards": 250,
  "maxRules": 10,
  "maxChargeUsd": 35,
  "includeReport": true
}
```

# Actor output Schema

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

No description

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

// Run the Actor and wait for it to finish
const run = await client.actor("junipr/collection-page-merchandising-auditor").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("junipr/collection-page-merchandising-auditor").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 junipr/collection-page-merchandising-auditor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,junipr/collection-page-merchandising-auditor"
        }
    }
}

```

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/24yMqhabl49TQSnL8/builds/xyFwSxadKlcJE9WXn/openapi.json
