# Google Merchant Feed Validator & Price Checker (`fluffy_ingot/merchant-feed-preflight`) Actor

Check Google Merchant-style CSV/TSV product feeds for missing fields, duplicate IDs, invalid GTIN check digits and price issues. Optionally compare up to 10 product pages with JSON-LD. Get row-level CSV findings, an HTML report and JSON. No Merchant Center approval guarantee.

- **URL**: https://apify.com/fluffy\_ingot/merchant-feed-preflight.md
- **Developed by:** [yipee Gameplay](https://apify.com/fluffy_ingot) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / completed feed report

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Google Merchant Feed Validator & Price Checker

Check a **Google Shopping / Merchant Center product feed before upload**. Find malformed prices, image URLs, duplicate IDs and other selected feed issues, with the row, product ID and correction guidance in one report. Optionally compare a small sample of product pages with their published Product/Offer JSON-LD.

**Up to 10,000 feed rows per report. Base price: $1.00 per completed report + $0.00005 per start; platform usage included.** HTML, CSV and JSON are included, with no separate fee for each product or finding. The live Pricing tab shows your current price and tier discounts.

### Preview the correction report

The prefilled synthetic feed has 2 products. The second product produces these 4 findings:

| Field and sample value | Report guidance |
| --- | --- |
| `image_link`: `/missing-image.jpg` | Use an absolute HTTP(S) image URL |
| `price`: `12.345 USD` | Review price precision above two decimal places |
| `sale_price`: `15.00 USD`, regular price `12.345 USD` | Sale price should be lower than regular price |
| `availability`: `preorder`, no availability date | Provide the expected availability date |

The report identifies product `P2` on line 3 and separates **2 errors from 2 warnings**. This is a static output preview; reading it does not start a run.

[Run the two-product sample](https://apify.com/fluffy_ingot/merchant-feed-preflight/examples/find-product-feed-price-and-image-errors) with the displayed Actor price, or replace it with your own feed. The sample keeps website checks off.

**File validation is the default.** Website comparison is optional and samples 1–10 rows; unsupported or inaccessible page markup returns `unknown`. This independent utility checks a stated subset of Google's requirements and does not guarantee Merchant Center approval.

### What the report checks

- Required core fields; title or structured\_title and description or structured\_description alternatives.
- Duplicate IDs, ID/title/description length and inconsistent row widths.
- Price/currency syntax, extra price decimals, sale-price comparisons and zero-price review.
- Link/image URL syntax, supported availability and condition values, and dates for preorder/backorder.
- Optional single-GTIN length/check digit, plus explicitly labeled identifier-review cases.

Checks distinguish **errors**, **warnings** and **review** findings. Conditional cases receive review findings when the feed alone cannot establish eligibility. Structured title/description values satisfy the presence check, but their subfields, content and AI provenance require separate validation. Missing GTIN, brand, MPN or condition is not automatically labeled invalid.

### Run it

1. Choose **Paste CSV / TSV text** or **Upload CSV / TSV file** under **Feed source**. Include lowercase English headers on the first line. Upload mode ignores the demo text, so you do not need to delete it.
2. Select **CSV (comma)** or **TSV (tab)**. The delimiter is explicit; it is not guessed.
3. For a private uploaded file, select the temporary key-value store created by the upload under **Uploaded file store (read access)**. Only that store receives READ permission. Pasted text and valid signed upload links do not need a store selection.
4. Leave page checks off for feed validation, or enable **Check product-page prices and availability**, confirm your permission to check the supplied pages, and choose a sample size of 1–10 rows.
5. Start the Actor and open **Review report**. Download **CSV findings** or use **JSON report** in an automation.

Example input:

```json
{
  "sourceMode": "text",
  "feedText": "id,title,description,link,image_link,availability,price\nAPRON-1,Cotton apron,Plain cotton apron.,https://example.com/apron,https://example.com/apron.jpg,in_stock,19.95 USD\n",
  "delimiter": "csv",
  "checkPages": false
}
```

For uploaded input, use `sourceMode: "upload"`, `feedFileUrl` and, for a private upload, `sourceStores: ["YOUR_UPLOAD_STORE_ID"]`. The file picker provides the Apify record URL. Only HTTPS `api.apify.com/v2/key-value-stores/.../records/...` URLs are accepted. Signed URLs must contain only their storage signature; API-token query parameters are rejected. Existing API clients that send `feedText` and omit `sourceMode` continue to work. When the source mode is omitted, supply exactly one nonblank text or upload source.

The prefilled example is synthetic, produces four feed findings, and makes no product-page requests. For a first test, run it unchanged. Replace the example.com product links with your own authorized product pages before enabling online checks.

#### Example: invalid GTIN digits and identifier conflicts

[Check invalid GTIN digits in a product feed CSV](https://apify.com/fluffy_ingot/merchant-feed-preflight/examples/check-invalid-gtin-digits-in-a-product-feed-csv) contains three synthetic products:

| Product ID | Selected check result |
| --- | --- |
| `DEMO-DIGIT` | `GTIN_CHECK_DIGIT`: final check digit does not match |
| `DEMO-CONFLICT` | `IDENTIFIER_CONFLICT_REVIEW`: GTIN is supplied while `identifier_exists` says `no` |
| `DEMO-VALID` | No findings in the selected rules |

The report has **1 error and 1 review finding**. The identifiers are checksum fixtures, not claims that those identifiers belong to the synthetic products. A valid check digit does not verify assignment or Google approval. Page checks stay off. Reading this preview does not start a run; running the example uses the displayed Actor price. At base pricing, one report plus startup totals $1.00005.

### Optional website comparison

The checker reads **server-rendered JSON-LD** from the first 1–10 valid-width product rows in feed order; the default sample is 5. It compares a selected Product/Offer's `price`, `priceCurrency` and `availability` with the feed. Product SKU or GTIN and exact product/offer URLs help select the product and variant. Queries remain part of internal variant matching but are omitted from report evidence.

Each field is **match**, **mismatch** or **unknown**. A mismatch means the feed and observed JSON-LD differ; it is a review warning, not a claim that the checkout is wrong or Google will disapprove the product. A page can have a definite availability difference while its price remains unknown.

- Multiple unresolved products/offers, aggregate price ranges, lost variant identity, missing JSON-LD and inaccessible pages return **unknown**.
- Sale prices, price specifications, member/quantity/region restrictions and tax-related price context receive review instead of a definite price mismatch. This first version does not choose an active sale or compute tax, shipping or currency conversion.
- JavaScript-only markup, RDFa, Microdata, rendered prices, cookies, login, browser challenges, remote JSON-LD contexts, checkout, images and website changes over time are outside scope. Pages with unsupported markup may be useful to a browser but unknown to this checker.
- Only public HTTP(S) URLs on standard ports are fetched. Private, loopback, link-local, reserved and multicast addresses, credential-bearing URLs and sensitive query keys are rejected. DNS answers are validated and connections are pinned to a public address. Redirects are capped at 3 and must stay on the same hostname; HTTPS cannot redirect down to HTTP.
- Each page is limited to **512 KB and 6 seconds**, with **2 concurrent requests and 32 seconds total** for the online phase. Compressed responses are not decoded. The checker requests uncompressed HTML; a server that ignores this request returns unknown. Raw HTML is discarded after extracting the selected fields. There are no proxies, retries, images, AI calls or browser fees.

The report states how many rows were not checked. A sample is not a full-site audit. Separate network failures remain in the report, so one blocked page does not discard useful results from other pages. Choose your sample order deliberately if you want to prioritize high-risk products.

### Limits and scope

Each run accepts up to **10,000 nonblank product rows, 2,000,000 characters and 40 columns**. Uploaded files must also be uncompressed UTF-8 and no larger than 2,000,000 bytes. Blank lines are ignored. Headers and cell edges are trimmed. Duplicate headers are rejected. CSV quoting supports embedded commas and newlines; a finding's line number is the record's ending physical line. XML feeds and arbitrary remote feed URLs are outside scope.

Reports contain complete finding counts and at most **500 detailed findings**. Truncation is explicit. Finding values are capped at 1,000 characters and IDs at 200 characters. HTTP(S) URLs in findings omit query strings; credential-bearing URLs are replaced with a safe placeholder. Fix reported issues and rerun to expose remaining findings. The JSON column inventory identifies fields outside the checked subset.

With page checks off, product URL checks are syntax-only. Neither mode verifies domains, image dimensions, checkout or complete price accuracy. Three uppercase currency letters pass the syntax check even if that currency is unsupported. Prices longer than 30 numeric characters receive review instead of numeric comparison. A valid GTIN checksum does not establish registration or correct product assignment. Multiple GTINs, category/country rules, shipping, tax, account policies and approval status remain outside scope. Date syntax is checked without deciding whether the date is correct or still in the future.

Google's requirements can change. A report with no findings means no problems were detected by this tool's listed rules.

### Outputs and automation

- **report.html:** self-contained, escaped HTML report with no scripts or remote assets.
- **REPORT:** JSON summary, column inventory, scope notes, findings and `pageChecks` evidence when enabled. Each page check contains its line/ID, query-free URL, status, match method and selected field values/reasons when available.
- **findings.csv:** summary row, findings and optional `PAGE_EVIDENCE` rows. Formula-like cells receive a leading apostrophe for spreadsheet safety; JSON preserves the parsed text within the stated preview limits.
- **Default dataset:** exactly one summary item per completed report. Product rows are not individual billable results.

Use the run's output links or key-value-store records after the run succeeds. A recurring workflow can submit its fresh export, wait for completion and route the summary or report to its existing review process. The Actor does not submit or alter a Merchant Center feed.

A practical recurring sequence is: create the fresh feed export → submit its text or upload through the Actor input → wait for a successful run → retrieve the default dataset's single summary → inspect `rowsWithErrors`, `rowsWithWarnings`, `rowsNeedingReview` and `pageCountsByStatus` → route `report.html` or `REPORT` to your own review step. Do not mark unchecked/unknown pages as valid or automatically change prices based on this report. Apify schedules repeat saved input; the upstream workflow must refresh that input or upload record when the feed changes.

### Pricing

USD **$1.00 per completed feed report**, plus **$0.00005 per Actor start**. A completed report totals **$1.00005**, with platform usage included. There is no separate per-product or per-finding fee. A report that finds problems is still a completed report. Set the maximum run charge to at least $1.00005; $1.01 covers one report and startup. Unsupported input fails without creating a report-result event; the startup fee may still apply. Data is stored in the run's Apify storage.

### Rule references

Feed rules reviewed September 5, 2026; upload and structured-data support reviewed September 7, 2026. These primary sources explain the corresponding attributes and platform features; the implementation intentionally checks a limited subset:

- [Google product data specification](https://support.google.com/merchants/answer/7052112?hl=en): required fields, alternative structured text and text lengths.
- [Price](https://support.google.com/merchants/answer/6324371?hl=en) and [sale price](https://support.google.com/merchants/answer/6324471?hl=en): amount/currency format, precision, sale comparisons and conditional zero-price cases.
- [Availability](https://support.google.com/merchants/answer/6324448?hl=en), [availability date](https://support.google.com/merchants/answer/6324470?hl=en), and [date formatting](https://support.google.com/merchants/answer/7055760?hl=en): supported values and conditional dates, including vehicle-only build\_to\_order.
- [GTIN](https://support.google.com/merchants/answer/6324461?hl=en) and [structured-data identifier guidance](https://support.google.com/merchants/answer/6386198?hl=en): supported identifier lengths and check digits.
- [Condition](https://support.google.com/merchants/answer/6324469?hl=en): supported condition values and conditional requirement.
- [Google Product/Offer merchant-listing structured data](https://developers.google.com/search/docs/appearance/structured-data/merchant-listing) and [Schema.org Offer](https://schema.org/Offer): selected page fields and price context. This utility does not implement the complete specifications.
- [Apify input schema and resource permissions](https://docs.apify.com/actors/development/actor-definition/input-schema/specification/v1) and [dataset schemas](https://docs.apify.com/storage/dataset-schema): native uploads, selected storage access and typed summary output.

### Local verification

Run `python -m unittest -q` from the source folder. The 56 validator, upload, network-transport, comparison, rendering and lifecycle tests use only the standard library, synthetic data and injected transports; the hosted lifecycle uses Apify SDK 4. They cover private-address/DNS/redirect blocking, private-store authorization, unknown/partial results, time and concurrency caps, spreadsheet/HTML safety and the one-summary billing contract. No customer data or live product requests are needed.

`python prepare_package.py` regenerates schemas and synthetic reports. `example-page-report.html` illustrates two observed differences using the local `example-product-page.html` fixture through an injected fetcher; it is **not evidence of a real live-page run**. The default `example-input.json` keeps page checks off.

### More previews and related tools

[Watch the 32-second synthetic portfolio demo](https://api.apify.com/v2/key-value-stores/pUK33haAXl69dRu1G/records/preflight-actors-demo.mp4?signature=1nIZqH2Dg5GUlcwpyKtnI) for a walkthrough of the four tools. The video uses sample data.

- [Shopify CSV Import Checker & Supplier Catalog Diff](https://apify.com/fluffy_ingot/supplier-catalog-preflight)
- [OpenAPI JSON Diff & Breaking Change Checker](https://apify.com/fluffy_ingot/openapi-change-preflight)
- [Greenhouse & Ashby Job Change Monitor](https://apify.com/fluffy_ingot/job-board-change-feed)

# Actor input Schema

## `sourceMode` (type: `string`):

Choose upload to use the file below; pasted demo text is ignored in upload mode. Existing API calls may omit this field when supplying one source.

## `feedText` (type: `string`):

Used in text mode. Paste lowercase headers and up to 2 million characters, 10,000 nonblank product rows and 40 columns. Product pages are only fetched when page checks are enabled below.

## `feedFileUrl` (type: `string`):

Used in upload mode. Upload uncompressed UTF-8 CSV/TSV, up to 2 MB. Apify storage URLs only; select private upload storage below.

## `sourceStores` (type: `array`):

Select the temporary key-value store created by the file upload. The Actor receives READ access only to this selected store. Not needed for pasted text or a valid signed upload link.

## `delimiter` (type: `string`):

Select the actual delimiter. This tool does not guess.

## `checkPages` (type: `boolean`):

Optional: compare a small sample of product links against server-rendered Product/Offer JSON-LD. No browser or JavaScript rendering. Unsupported or ambiguous pages are unknown, not passes.

## `pageAccessAuthorized` (type: `boolean`):

Required only when page checks are enabled. Use public product links; no login, secrets, paid APIs or customer URLs.

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

Check the first 1–10 valid-width product rows, in feed order. Up to 512 KB per page, 6 seconds each, 32 seconds total, 2 concurrent requests. Remaining rows are explicitly unchecked.

## Actor input object example

```json
{
  "sourceMode": "text",
  "feedText": "id,title,description,link,image_link,availability,price,sale_price\r\nP1,Cotton apron,Plain cotton apron.,https://example.com/p1,https://example.com/p1.jpg,in_stock,19.95 USD,\r\nP2,Cotton apron,Plain cotton apron.,https://example.com/p1,/missing-image.jpg,preorder,12.345 USD,15.00 USD\r\n",
  "delimiter": "csv",
  "checkPages": false,
  "pageAccessAuthorized": false,
  "maxPages": 5
}
```

# Actor output Schema

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

Readable report of errors, warnings and review findings.

## `json` (type: `string`):

Complete summary counts, up to 500 findings and sampled page evidence when enabled.

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

Spreadsheet-safe findings and optional page-evidence rows.

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

One summary per completed report.

# 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 = {
    "feedText": `id,title,description,link,image_link,availability,price,sale_price
P1,Cotton apron,Plain cotton apron.,https://example.com/p1,https://example.com/p1.jpg,in_stock,19.95 USD,
P2,Cotton apron,Plain cotton apron.,https://example.com/p1,/missing-image.jpg,preorder,12.345 USD,15.00 USD`
};

// Run the Actor and wait for it to finish
const run = await client.actor("fluffy_ingot/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 = { "feedText": """id,title,description,link,image_link,availability,price,sale_price\r
P1,Cotton apron,Plain cotton apron.,https://example.com/p1,https://example.com/p1.jpg,in_stock,19.95 USD,\r
P2,Cotton apron,Plain cotton apron.,https://example.com/p1,/missing-image.jpg,preorder,12.345 USD,15.00 USD\r
""" }

# Run the Actor and wait for it to finish
run = client.actor("fluffy_ingot/merchant-feed-preflight").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 '{
  "feedText": "id,title,description,link,image_link,availability,price,sale_price\\r\\nP1,Cotton apron,Plain cotton apron.,https://example.com/p1,https://example.com/p1.jpg,in_stock,19.95 USD,\\r\\nP2,Cotton apron,Plain cotton apron.,https://example.com/p1,/missing-image.jpg,preorder,12.345 USD,15.00 USD\\r\\n"
}' |
apify call fluffy_ingot/merchant-feed-preflight --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fluffy_ingot/merchant-feed-preflight"
        }
    }
}
```

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/Wnuyh9cOtoixcQph2/builds/PSh0mg7gZWleXwK72/openapi.json
