# Google Merchant Feed Validation (`first_watch/google-merchant-feed-validation`) Actor

Ecommerce operators and agencies get products disapproved in Google Merchant Center because of feed errors (missing required attributes, invalid GTINs, bad price/availability formats, over-length titles). They find out only after upload. They need every problem listed per item, with a fix, and a...

- **URL**: https://apify.com/first\_watch/google-merchant-feed-validation.md
- **Developed by:** [Jordan Nabbe](https://apify.com/first_watch) (community)
- **Categories:** E-commerce, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $50.00 / 1,000 feed validateds

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

## Google Merchant Feed Validation

Google Merchant Feed Validation is built for ecommerce store owners, feed managers and PPC/Shopping agencies who need to catch product feed errors before Google Merchant Center disapproves their products. Give it a CSV, TSV, Google XML (RSS/Atom) or XLSX feed and it returns one import-ready row per issue, with a suggested fix, a health score per feed, an auto-corrected CSV and an optional new/recurring/fixed comparison with your previous run.

### What it does

- Parses real feed files: CSV, TSV, Google Shopping XML (RSS 2.0 and Atom, `g:` prefixes stripped) and `.xlsx` workbooks (first sheet, header in row 1). Input can be inline text, a base64 string or a public https URL.
- Runs a deterministic rule table: missing required attributes (id, title, description, link, image\_link, price, availability), duplicate ids, price format (`19.90 USD`), sale price not lower than price, availability and condition values, title over 150 characters, description over 5000 characters, invalid links (http is accepted by Google and only flagged as info), GTIN length and mod-10 check digit (ISBN-10 accepted), missing GTIN/MPN, brand required for new products except media (placeholder brands such as N/A or Generic are rejected), and missing apparel attributes when the product category contains "Apparel".
- Applies only safe automatic fixes to a corrected CSV (price format — both `1.234,56 EUR` and `1,234.56 USD` become `1234.56 …`; an ambiguous value such as `1.234 EUR` is flagged `PRICE_AMBIGUOUS` and never changed, availability and condition values, truncation of over-long title/description). GTIN problems and missing values are flagged, never guessed.
- Scores every feed (0-100) and reports per-feed summary rows, so agencies can compare clients.
- Optional run-over-run diff (`compareToPrevious`): each issue is marked `new`, `recurring` or `fixed` against the previous run's baseline. Baselines are kept in a named key-value store in your own account (`baselineStoreName`, default `merchant-feed-baselines`), one per feed `baselineId` (default: the feed name), so they survive between runs and never mix feeds or clients.
- Every failed feed produces a `feed_error` row; other feeds still complete.

### How to use it

1. Paste your feed(s) into the `feeds` input, or use the prefilled example and click Start.
2. Read the dataset (one row per issue, plus one `feed_summary` row per feed) or the `OUTPUT` record.
3. Download the corrected feed from the key-value store record named in `correctedFeedKey`.

```json
{
  "feeds": [
    {
      "name": "demo-shop",
      "format": "csv",
      "content": "id,title,description,link,image_link,price,availability,condition,gtin,brand\nSKU1,Blue Shirt,Soft cotton shirt,https://shop.example/p/1,https://shop.example/i/1.jpg,19.9 USD,in stock,new,4006381333931,Acme"
    }
  ],
  "targetCountry": "US",
  "generateCorrectedFeed": true
}
```

For XLSX use `"contentBase64": "<base64 of the .xlsx file>"`. For a hosted feed use `"url": "https://example.com/feed.xml"`.

#### Output example

| recordType | feed | itemId | severity | ruleCode | attribute | currentValue | suggestedFix | autoFixed |
|---|---|---|---|---|---|---|---|---|
| issue | demo-shop | SKU1 | error | PRICE\_FORMAT | price | 19.9 USD | 19.90 USD | true |
| issue | demo-shop | SKU1 | error | AVAILABILITY\_ENUM | availability | in stock | in\_stock | true |
| feed\_summary | demo-shop | | | | | | | |

Every row has the same 19 columns (unused ones are `null`), so it imports cleanly into spreadsheets and databases. Summary rows carry `itemsTotal`, `itemsWithErrors`, `errorCount`, `warningCount`, `healthScore` and `correctedFeedKey`.

### Pricing

Pay per event. The main charge is per validated feed; everything else listed below is free.

| Event | Price (USD) | Charged when |
|---|---|---|
| Run start (`apify-actor-start`) | $0.005 | once per run; the default 512 MB memory is one event |
| Feed validated (`feed-validated`) | $0.05 | per feed that was parsed and validated |

Not charged:

- every issue row and feed summary in the dataset
- the corrected CSV feed
- feeds that could not be read

Examples (default 512 MB memory, one start event):

- One daily feed check: $0.005 start + 1 × $0.05 = **$0.055**
- Five country feeds every day for 30 days: $0.005 start + 150 × $0.05 = **$7.505**

Spending limit: when you set a maximum cost per run, the Actor stops before it would exceed it, keeps every validated feed it already delivered and reports `limitReached` in the `OUTPUT` record. It never delivers results beyond the limit.

The Store's Pricing tab shows the prices in force. If this section and the Pricing tab ever differ, the Pricing tab applies.

### Use cases

- Pre-upload check of a product feed export before sending it to Merchant Center.
- Agency batch check of up to 50 client feeds in one run with per-feed health scores.
- Scheduled monitoring: enable `compareToPrevious` and see which issues are new, recurring or fixed.
- Producing a cleaned CSV with the mechanical errors already repaired.

### Limits

- Up to 50 feeds per run and 200,000 items per feed (default 50,000); extra items are ignored and reported with an info issue.
- Remote feeds: https only, default port, public hosts only (private and loopback addresses and redirects to them are blocked), 3 redirects, 60 s timeout, 100 MB cap. Base64 content is also capped at 100 MB and the whole input at 25 MB.
- Image checks (`checkImageUrls`) apply only to feeds supplied by URL, use HEAD requests with 10 in parallel, a 5 s timeout and at most 5,000 checks per run.
- The rules cover a practical subset of the Google product data specification; they do not replace Merchant Center's own review, and category-specific or country-specific rules beyond those listed are not checked.
- The `OUTPUT` record holds the first 1,000 rows; the dataset holds all rows.
- The Actor uses no Merchant Center credentials and does not scrape third-party sites.

### FAQ

**Does it connect to my Merchant Center account?** No. It only reads the feed you supply.

**Will it change my original feed?** No. Fixes are written to a separate corrected CSV.

**Why is a GTIN not auto-fixed?** A wrong check digit usually means a wrong number. The suggestion shows the digit that would make it valid, but you should verify it.

**How does the diff work?** Issues are matched by item id, rule and attribute against the baseline saved for the same feed name in the default key-value store of the run's storage, so use the same feed name and a persistent store for scheduled runs.

**What happens if one feed is broken?** It gets a `feed_error` row and the rest continue. The run fails only if every feed fails.

# Actor input Schema

## `feeds` (type: `array`):

Feeds to validate (1-50). Each feed needs a unique 'name' and exactly one of 'content' (inline CSV/TSV/XML text), 'contentBase64' (e.g. an .xlsx file) or 'url' (public https feed URL). Optional 'format': auto, csv, tsv, xml or xlsx.

## `targetCountry` (type: `string`):

Two-letter ISO country code, used to pick a default currency when a price has none.

## `generateCorrectedFeed` (type: `boolean`):

Store a CSV with safe automatic fixes applied in the key-value store.

## `compareToPrevious` (type: `boolean`):

Mark issues as new, recurring or fixed compared with the last run that used the same feed name.

## `checkImageUrls` (type: `boolean`):

HEAD-check image links for feeds supplied by url. Needs network. Max 5,000 checks per run.

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

Items beyond this limit are ignored and reported with an info issue.

## `baselineStoreName` (type: `string`):

Named key-value store in your account that keeps each feed's issue baseline between runs (used when compareToPrevious is on). Each feed uses its baselineId (default: the feed name) as key.

## Actor input object example

```json
{
  "feeds": [
    {
      "name": "demo-shop",
      "format": "csv",
      "content": "id,title,description,link,image_link,price,availability,condition,gtin,brand\nSKU1,Blue Shirt,Soft cotton shirt,https://shop.example/p/1,https://shop.example/i/1.jpg,19.9 USD,in stock,new,4006381333931,Acme\nSKU2,Red Hat,Warm hat,https://shop.example/p/2,https://shop.example/i/2.jpg,15.00 USD,available,new,,Acme\nSKU3,Steel Kettle,1.5 litre kettle,https://shop.example/p/3,https://shop.example/i/3.jpg,\"1.234,56 EUR\",in_stock,new,4006381333931,N/A"
    }
  ],
  "targetCountry": "US",
  "generateCorrectedFeed": true,
  "compareToPrevious": false,
  "checkImageUrls": false,
  "maxItemsPerFeed": 50000,
  "baselineStoreName": "merchant-feed-baselines"
}
```

# Actor output Schema

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

Every result row stored in the default dataset.

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

Summary written to the OUTPUT record.

# 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 = {
    "feeds": [
        {
            "name": "demo-shop",
            "format": "csv",
            "content": "id,title,description,link,image_link,price,availability,condition,gtin,brand\nSKU1,Blue Shirt,Soft cotton shirt,https://shop.example/p/1,https://shop.example/i/1.jpg,19.9 USD,in stock,new,4006381333931,Acme\nSKU2,Red Hat,Warm hat,https://shop.example/p/2,https://shop.example/i/2.jpg,15.00 USD,available,new,,Acme\nSKU3,Steel Kettle,1.5 litre kettle,https://shop.example/p/3,https://shop.example/i/3.jpg,\"1.234,56 EUR\",in_stock,new,4006381333931,N/A"
        }
    ],
    "targetCountry": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("first_watch/google-merchant-feed-validation").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 = {
    "feeds": [{
            "name": "demo-shop",
            "format": "csv",
            "content": """id,title,description,link,image_link,price,availability,condition,gtin,brand
SKU1,Blue Shirt,Soft cotton shirt,https://shop.example/p/1,https://shop.example/i/1.jpg,19.9 USD,in stock,new,4006381333931,Acme
SKU2,Red Hat,Warm hat,https://shop.example/p/2,https://shop.example/i/2.jpg,15.00 USD,available,new,,Acme
SKU3,Steel Kettle,1.5 litre kettle,https://shop.example/p/3,https://shop.example/i/3.jpg,\"1.234,56 EUR\",in_stock,new,4006381333931,N/A""",
        }],
    "targetCountry": "US",
}

# Run the Actor and wait for it to finish
run = client.actor("first_watch/google-merchant-feed-validation").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 '{
  "feeds": [
    {
      "name": "demo-shop",
      "format": "csv",
      "content": "id,title,description,link,image_link,price,availability,condition,gtin,brand\\nSKU1,Blue Shirt,Soft cotton shirt,https://shop.example/p/1,https://shop.example/i/1.jpg,19.9 USD,in stock,new,4006381333931,Acme\\nSKU2,Red Hat,Warm hat,https://shop.example/p/2,https://shop.example/i/2.jpg,15.00 USD,available,new,,Acme\\nSKU3,Steel Kettle,1.5 litre kettle,https://shop.example/p/3,https://shop.example/i/3.jpg,\\"1.234,56 EUR\\",in_stock,new,4006381333931,N/A"
    }
  ],
  "targetCountry": "US"
}' |
apify call first_watch/google-merchant-feed-validation --silent --output-dataset

```

## MCP server setup

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

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/8CmxRXuM34H2PJgWk/builds/Qf3qTBC7rF8HgccMG/openapi.json
