# Google Shopping Feed Validator & Change Monitor (`muhzuhaib/google-shopping-feed-validator`) Actor

Checks every item in a Google Merchant Center product feed against the product data specification, then watches the feed on a schedule and reports what newly broke and what was fixed.

- **URL**: https://apify.com/muhzuhaib/google-shopping-feed-validator.md
- **Developed by:** [Muhammad Zuhaib Zahid](https://apify.com/muhzuhaib) (community)
- **Categories:** E-commerce, SEO tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / daily monitoring of one feed

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

## Google Shopping Feed Validator & Change Monitor

Point it at the product feed you already submit to Google Merchant Center. It checks every item
against the product data specification, tells you exactly what is wrong and how to fix it, and then
watches the feed on a schedule so you find out the day something breaks instead of the day sales
drop.

**The first audit of any feed is free and complete.** Nothing is charged until you have seen the
whole report.

![Issues found in a feed](https://api.apify.com/v2/key-value-stores/fzPqfSUYe9NdjD4vQ/records/feedvalidator-shot-1.png)

### Why this exists

Merchant Center tells you an item was disapproved after it has already stopped serving, and it tells
you in its own vocabulary. A feed export that starts writing `9,99 EUR` instead of `9.99 EUR`, or a
GTIN with one digit mistyped, can take out a whole category quietly.

This Actor reads the same feed Google reads, applies the same rules before Google does, and keeps
yesterday's result so it can tell you what is NEW today.

### What it checks

Every rule comes from the Merchant Center product data specification, read on **10 September 2026**,
and every finding names the attribute, the item, the value you submitted and the fix.

**Identity and content.** id present, unique across the feed and within 50 characters. Title present
and within 150. Description present and within 5000. Promotional wording, block capitals and
placeholder text in titles. HTML in descriptions.

**Links and images.** link and image\_link present, absolute and correctly encoded. Unsupported image
formats. Placeholder images. Additional images over the limit of ten.
**Image resolution measured from the real file**: Google's minimum becomes 500 by 500 pixels for
every category, and enforcement begins on 31 January 2027. This Actor reports undersized images as a
warning now and switches them to errors on that date by itself.

**Price.** The exact grammar Google wants: a number, a space, an ISO 4217 code. The decimal comma
that European exports produce. Currency symbols used instead of codes. Zero and negative prices. A
sale price that is not lower than the price, or is in a different currency. Sale windows that are
malformed or have already closed.

**Availability.** The four accepted values, a preorder without an availability date, an expiration
date in the past or more than 30 days out.

**Identifiers.** GTIN length AND its **GS1 check digit**, so a mistyped barcode is caught before
Google rejects it. MPN and brand limits. identifier\_exists that contradicts the identifiers actually
present. A missing brand is reported as an error only when nothing else identifies the product.

**Categories, variants and apparel.** google\_product\_category as a numeric id or a full path.
product\_type and item\_group\_id limits. age\_group, gender, size, size\_type, size\_system, colour and
material values and lengths.

**Measures and structured values.** unit\_pricing\_measure and unit\_pricing\_base\_measure units. Product
length, width, height and weight ranges. installment, subscription\_cost, certification, and product
highlight counts.

**The 2026 additions.** video\_link, validated by Google since 14 April 2026 and serving since
30 June 2026, plus handling\_cutoff\_time and minimum\_order\_value.

**The feed itself.** Duplicate ids. A header row that repeats a column. Rows whose column count does
not match the header, which is what an unescaped quote or delimiter does to a text feed. Mixed
currencies. An empty feed. A URL that answers with something that is not a product feed at all.

### What you get on every run

![The feed report and health score](https://api.apify.com/v2/key-value-stores/fzPqfSUYe9NdjD4vQ/records/feedvalidator-shot-2.png)

Three kinds of row, each with its own view in the dataset:

| View | One row per | Use it for |
|---|---|---|
| Issues | problem found | the fix list, sorted with errors first |
| Changes since the last run | thing that changed | what broke today, what got fixed, what was added or removed |
| Feed report | feed per run | the health score, the counts and the billing line |

The **health score** is one number you can put on a dashboard: start at 100, remove the share of
items carrying an error, remove a fifth of the share carrying a warning, and cap it at 40 while any
feed-level error stands.

### Monitoring, not just checking

![Changes since the previous run](https://api.apify.com/v2/key-value-stores/fzPqfSUYe9NdjD4vQ/records/feedvalidator-shot-3.png)

Put it on a daily schedule and from the second run onwards it reports the delta:

- items that **newly broke**, with the rules that broke;
- items that were **fixed**, so you can see a change land;
- items **added** and **removed**;
- **price** and **availability** moves;
- the feed **shrinking** against the previous run, which is what a broken export looks like before
  anyone notices;
- the health score **falling**.

Send those to Discord, Slack, or any endpoint that takes a JSON POST. Alerts carry regressions only
by default, and a feed's first audit never alerts, so switching it on does not bury you.

### Input

| Field | What it is |
|---|---|
| Product feed URLs | One per line. RSS 2.0 XML, Atom XML, and tab or comma separated text all work. |
| Extra request headers | Only if your feed sits behind Basic or bearer auth. Stored encrypted. |
| What to report | Errors only, errors and warnings (the default), or everything including notes. |
| Images to measure | How many product images to measure per feed. 25 by default, 0 to skip. |
| Webhook URL and format | Discord, Slack or generic JSON. |
| Limits | Items per feed, feed size, issue and change row caps, timeout. |

The default input points at a sample feed with deliberate mistakes in it, so you can press Start and
see exactly what the output looks like before you point it at your own.

### Pricing

| Event | Price | When |
|---|---|---|
| Daily monitoring of one feed | $1.50 | once per feed per day, and never on a feed's first audit |
| Actor start | platform rate | per run |

- **The first audit of a feed is free and complete.** Every issue, every fix, no charge.
- After that it is **$1.50 per feed per day**, charged once however many times you run it that day.
  A daily schedule is $45 a month per feed, which is less than the feed tools that do the same job,
  and running it hourly during a migration costs exactly the same.
- Each feed URL is charged separately, so an agency pays per client feed.
- **Nothing is charged** when a feed cannot be read, or when the URL turns out not to be a Merchant
  Center feed. If the charge cannot be made because of the maximum charge set on the run, you still
  get the full audit and the run says so.

### Limits and honesty

- It checks what is IN the feed. Rules that depend on your Merchant Center account settings, your
  target country, or Google's own crawl of your landing pages are reported as warnings or notes
  rather than errors, because this Actor cannot see those and a false error is worse than no check.
- It is not affiliated with Google. It reads the public product data specification and applies it.
- Feeds are read, nothing is written anywhere except your own dataset and your own webhook.
- A feed larger than the size limit is truncated rather than crashing the run, and the report says so.
- Images are measured from the first bytes of each file, on a bounded sample per run, so image
  checking never becomes the expensive part of the run.

### A good schedule

Daily, an hour after your feed export runs. That way the audit sees the feed Google will fetch, and
a regression reaches your webhook the same morning it appears.

# Actor input Schema

## `feedUrls` (type: `array`):

One feed URL per line. RSS 2.0 XML, Atom XML and tab or comma separated text all work. The default is a sample feed with deliberate mistakes in it, so a first run shows the checker working.

## `feedHeaders` (type: `object`):

Only needed if your feed sits behind Basic or bearer auth, for example {"authorization": "Basic ..."}. Stored encrypted.

## `reportLevel` (type: `string`):

Errors are the problems that get an item disapproved. Warnings degrade how the item serves or are a deadline coming up. Notes are informational.

## `imageSampleSize` (type: `integer`):

How many product images to measure per feed, to catch images below Google's 500 by 500 minimum. Only the first bytes of each file are read. Set to 0 to skip image checks.

## `reportUnknownAttributes` (type: `boolean`):

Adds a note for attributes this checker does not know. Custom attributes are allowed by Google, so this is off by default.

## `trackPrices` (type: `boolean`):

Include a row whenever an item's price changes between runs.

## `trackAvailability` (type: `boolean`):

Include a row whenever an item goes in or out of stock between runs.

## `shrinkPercent` (type: `integer`):

Report a feed-level regression when the item count drops by at least this percentage against the previous run. A feed that suddenly halves is usually a broken export.

## `webhookUrl` (type: `string`):

Optional. A Discord or Slack incoming webhook, or any endpoint that accepts a JSON POST. Alerts are sent only when something gets worse, and never on a feed's first audit.

## `webhookFormat` (type: `string`):

Discord and Slack post a readable message. Generic posts a full JSON envelope for n8n, Make, Zapier or your own server.

## `alertOn` (type: `string`):

Regressions covers newly broken items, removed items, a shrinking feed and a falling health score.

## `maxItemsPerFeed` (type: `integer`):

Items beyond this are not checked, and the report says so. The default is sized for a run with 2 GB of memory; raise the run memory as well if you raise this a long way.

## `maxFeedSizeMb` (type: `integer`):

The download stops at this size, so a runaway feed can never exhaust the run's memory.

## `maxIssuesPerFeed` (type: `integer`):

Errors are written first, so lowering this keeps the most serious findings.

## `maxChangesPerFeed` (type: `integer`):

How many change rows one feed may write in a run. A feed that has just been rebuilt can produce thousands.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for a feed to answer before giving up on it for this run.

## `maxConcurrency` (type: `integer`):

How many image probes run at once. Feeds themselves are always checked one at a time, so the billing ledger cannot race with itself.

## `stateStoreName` (type: `string`):

The named key-value store holding the feed history and the monitoring passes between runs. Change it only to keep separate client lists apart.

## Actor input object example

```json
{
  "feedUrls": [
    "https://api.apify.com/v2/key-value-stores/fzPqfSUYe9NdjD4vQ/records/sample-google-shopping-feed.xml"
  ],
  "reportLevel": "errors_and_warnings",
  "imageSampleSize": 25,
  "reportUnknownAttributes": false,
  "trackPrices": true,
  "trackAvailability": true,
  "shrinkPercent": 20,
  "webhookFormat": "generic",
  "alertOn": "regressions",
  "maxItemsPerFeed": 50000,
  "maxFeedSizeMb": 64,
  "maxIssuesPerFeed": 5000,
  "maxChangesPerFeed": 2000,
  "requestTimeoutSecs": 60,
  "maxConcurrency": 3,
  "stateStoreName": "feed-validator-state"
}
```

# Actor output Schema

## `issues` (type: `string`):

One row per problem: the rule, the severity, the attribute, the item it belongs to, the value that was submitted and how to fix it.

## `changes` (type: `string`):

One row per newly broken item, newly fixed item, added or removed item, price move, availability flip, or feed-level regression.

## `runSummary` (type: `string`):

Totals for the whole run: feeds checked, items checked, errors and warnings found, changes reported, and monitoring passes charged.

# 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 = {
    "feedUrls": [
        "https://api.apify.com/v2/key-value-stores/fzPqfSUYe9NdjD4vQ/records/sample-google-shopping-feed.xml"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("muhzuhaib/google-shopping-feed-validator").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 = { "feedUrls": ["https://api.apify.com/v2/key-value-stores/fzPqfSUYe9NdjD4vQ/records/sample-google-shopping-feed.xml"] }

# Run the Actor and wait for it to finish
run = client.actor("muhzuhaib/google-shopping-feed-validator").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 '{
  "feedUrls": [
    "https://api.apify.com/v2/key-value-stores/fzPqfSUYe9NdjD4vQ/records/sample-google-shopping-feed.xml"
  ]
}' |
apify call muhzuhaib/google-shopping-feed-validator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,muhzuhaib/google-shopping-feed-validator"
        }
    }
}
```

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/iv1MAKeeASQUItVDv/builds/5fLDEyv5ik0T7y2C0/openapi.json
