# Merchant Feed Preflight (`checksmithcats/merchant-feed-preflight`) Actor

Before refreshing a product feed, sample public product pages and compare observable price, stock, image, and structured-data signals against feed rows.

- **URL**: https://apify.com/checksmithcats/merchant-feed-preflight.md
- **Developed by:** [Checksmith Cats](https://apify.com/checksmithcats) (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 $5.00 / report generated

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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

## Merchant Feed Preflight

Catches feed-vs-page price, stock, and image mismatches before you submit a product feed. Reads only public URLs; no account access.

Give it a public product feed URL. It fetches the feed and a sample of live product pages, compares them, and returns a per-finding report in JSON, HTML, and Markdown. For each sampled product it reports where the price differs, where stock status disagrees, where an image URL 404s, where product structured data is missing, and whether GTIN check digits and selected merchant structured-data types are present.

Policy and contact checks only confirm that a returns/shipping/privacy/contact link exists and resolves. They do not read or judge the policy text. Structured return/shipping checks detect `MerchantReturnPolicy` and `OfferShippingDetails` type presence only. GTIN checks validate the check digit only; they do not verify GS1 assignment, ownership, or product identity.

### Scope

MVP checks:

- Product feed fetch and parsing for CSV, TSV, RSS/XML, and Atom/XML
- Product page reachability
- Feed price vs static page/JSON-LD price comparison
- Feed availability vs static page/JSON-LD availability comparison
- Feed image URL reachability
- Product structured data detection
- Brand, GTIN, and MPN signal presence
- GTIN-8/12/13/14 check-digit validation when a GTIN is provided
- `MerchantReturnPolicy` and `OfferShippingDetails` JSON-LD type presence
- Store HTTPS check
- robots.txt status check
- Return/refund, shipping, privacy, terms, contact, and about link detection
- Public contact signal detection
- SSRF-safe URL fetching with redirect revalidation

Out of scope:

- Google Merchant Center account access
- Google approval, account recovery, or reinstatement work
- appeal text generation
- legal, tax, policy, or advertising advice
- Shopify, WooCommerce, or CMS repair work
- checkout testing
- login, CAPTCHA, proxy, or bot-block bypass
- default JavaScript rendering

Not affiliated with Google. This is a diagnostic report of detectable differences, not an approval, legal/policy review, or reinstatement service, and it does not reproduce Google's review decision.

### Local Development

Run tests:

```bash
PYTHONPATH=src python3 -m unittest discover -s tests -v
````

Generate a synthetic sample report:

```bash
PYTHONPATH=src python3 -m merchant_trust_preflight.cli \
  examples/sample-input.json \
  --fixture-map fixtures/sample-map.json \
  --json examples/sample-report.json \
  --html examples/sample-report.html \
  --md docs/sample-report.md
```

Run against a real public feed:

```bash
merchant-feed-preflight input.json --json report.json --html report.html
```

Example input:

```json
{
  "feedUrl": "https://example.com/products.xml",
  "storeUrl": "https://example.com/",
  "sampleLimit": 25,
  "country": "US",
  "currency": "USD",
  "recordRobots": true
}
```

Use only feeds and sites you own, manage, or have permission to scan.

### Apify Actor

Actor metadata lives in `.actor/`.

The intended first release route is:

1. Private Actor deployment
2. fixture and real-site smoke tests
3. pay-per-event pricing setup
4. public Store listing

Initial PPE events:

| Event | Suggested price | Meaning |
|---|---:|---|
| `report-generated` | `$5.00` | Charged once, only when at least one product page was fetched and compared |
| `product-checked` | `$0.25` | One product page that was actually fetched and compared (unreachable pages are reported but not charged) |

The Actor writes:

- dataset rows: one row per finding
- `REPORT.json`
- `REPORT.html`
- `REPORT.md`

### Security

The scanner fetches user-supplied URLs, so SSRF controls are part of the core product:

- http/https only
- username/password URLs rejected
- private, loopback, link-local, multicast, unspecified, CGNAT, and other non-global IPs blocked
- DNS resolution is checked before connecting
- redirects are revalidated
- body and header sizes are capped
- request timeouts are enforced
- query strings are redacted in report evidence

See `docs/security-and-license-review.md`.

### Commercial Use and Licenses

This source package is proprietary to Checksmith Cats.

Core scanner: Python standard library plus `defusedxml` (PSF-2.0) for safe XML feed parsing. Actor entrypoint: Apify SDK (Apache-2.0).

See `LICENSE` and `THIRD_PARTY_LICENSES.md`.

### References

- Google Merchant Center product data specification: https://support.google.com/merchants/answer/7052112?hl=en
- Google Merchant Center website requirements: https://support.google.com/merchants/answer/12756116?hl=en
- Google Merchant Center return policies: https://support.google.com/merchants/answer/14011730?hl=en
- Google Search Central merchant listing structured data: https://developers.google.com/search/docs/appearance/structured-data/merchant-listing
- Apify Actor input schema: https://docs.apify.com/platform/actors/development/actor-definition/input-schema/specification/v1
- Apify pay per event: https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event

# Actor input Schema

## `feedUrl` (type: `string`):

Public HTTPS URL of a Google-style product feed in CSV, TSV, RSS/XML, or Atom/XML format.

## `storeUrl` (type: `string`):

Optional public store homepage URL. If omitted, the Actor infers the store from product links in the feed.

## `sampleLimit` (type: `integer`):

Maximum number of products to sample from the feed. Keep this small for a low-impact preflight scan.

## `country` (type: `string`):

Optional country label shown in the report header. Does not change the checks.

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

Optional default ISO currency (e.g. USD) applied when a feed or page price omits one, so amounts compare correctly.

## `recordRobots` (type: `boolean`):

When enabled, the report records robots.txt availability. This MVP does not parse robots.txt rules or bypass blocks/login walls.

## Actor input object example

```json
{
  "feedUrl": "https://example.com/products.xml",
  "sampleLimit": 25,
  "country": "US",
  "currency": "USD",
  "recordRobots": 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 = {
    "feedUrl": "https://example.com/products.xml"
};

// Run the Actor and wait for it to finish
const run = await client.actor("checksmithcats/merchant-feed-preflight").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 = { "feedUrl": "https://example.com/products.xml" }

# Run the Actor and wait for it to finish
run = client.actor("checksmithcats/merchant-feed-preflight").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "feedUrl": "https://example.com/products.xml"
}' |
apify call checksmithcats/merchant-feed-preflight --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=checksmithcats/merchant-feed-preflight",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Merchant Feed Preflight",
        "description": "Before refreshing a product feed, sample public product pages and compare observable price, stock, image, and structured-data signals against feed rows.",
        "version": "0.1",
        "x-build-id": "ljNekjpMv6iJbg6Ng"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/checksmithcats~merchant-feed-preflight/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-checksmithcats-merchant-feed-preflight",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/checksmithcats~merchant-feed-preflight/runs": {
            "post": {
                "operationId": "runs-sync-checksmithcats-merchant-feed-preflight",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/checksmithcats~merchant-feed-preflight/run-sync": {
            "post": {
                "operationId": "run-sync-checksmithcats-merchant-feed-preflight",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "feedUrl"
                ],
                "properties": {
                    "feedUrl": {
                        "title": "Product feed URL",
                        "pattern": "^https?:\\/\\/.+",
                        "type": "string",
                        "description": "Public HTTPS URL of a Google-style product feed in CSV, TSV, RSS/XML, or Atom/XML format."
                    },
                    "storeUrl": {
                        "title": "Store URL",
                        "pattern": "^https?:\\/\\/.+",
                        "type": "string",
                        "description": "Optional public store homepage URL. If omitted, the Actor infers the store from product links in the feed."
                    },
                    "sampleLimit": {
                        "title": "Sample limit",
                        "minimum": 1,
                        "maximum": 200,
                        "type": "integer",
                        "description": "Maximum number of products to sample from the feed. Keep this small for a low-impact preflight scan.",
                        "default": 25
                    },
                    "country": {
                        "title": "Target country",
                        "type": "string",
                        "description": "Optional country label shown in the report header. Does not change the checks.",
                        "default": "US"
                    },
                    "currency": {
                        "title": "Expected currency",
                        "type": "string",
                        "description": "Optional default ISO currency (e.g. USD) applied when a feed or page price omits one, so amounts compare correctly.",
                        "default": "USD"
                    },
                    "recordRobots": {
                        "title": "Record robots.txt status",
                        "type": "boolean",
                        "description": "When enabled, the report records robots.txt availability. This MVP does not parse robots.txt rules or bypass blocks/login walls.",
                        "default": true
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
