# Google Merchant Feed Doctor (`privatex/google-merchant-feed-doctor`) Actor

Check Google Merchant Center product feeds (RSS/Atom XML, CSV, TSV) against Google's product data specification: errors, warnings and tips with the fix for each, duplicate ids, GTIN checksums, plus a normalized feed and change log.

- **URL**: https://apify.com/privatex/google-merchant-feed-doctor.md
- **Developed by:** [PRIVATEX](https://apify.com/privatex) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

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 Doctor

Check your Google Merchant Center product feed against Google's **product data specification**. For every product you get its **errors, warnings and tips**, each with the offending value and how to fix it. You also get a **normalized feed** with only safe, mechanical fixes applied, and a **BEFORE/AFTER change log**.

> Passing these checks **does not guarantee** Merchant Center approval. Google also checks landing pages, images, policies and account settings, which this Actor cannot see.

### Who is it for

- **Merchants** who upload a feed to Google Merchant Center and want to find disapproval risks before uploading.
- **Agencies and feed managers** who check many clients' feeds and need a repeatable, rule-by-rule report.
- **Developers** who generate feeds and want an automated check in their pipeline (API, schedules).

### What it checks

Every rule cites Google Merchant Center Help, "Product data specification" (https://support.google.com/merchants/answer/7052112, read 2026-10-30). The rule source is included in every finding.

**ERROR — required attributes and fixed formats**

- **Required attributes:** `id` (≤ 50 chars), `title` (≤ 150), `description` (≤ 5000), `link`, `image_link`, `availability`, `price`.
- **Fixed formats:**
  - `price` and `sale_price` must be `15.00 USD` style: period as decimal separator plus an ISO 4217 currency.
  - `availability` must be `in_stock`, `out_of_stock`, `preorder` or `backorder`; preorder needs `availability_date`.
  - `condition`, `gender`, `age_group`, `adult` and `identifier_exists` must use their enumerated values.
- **GTIN:**
  - check digit;
  - no restricted (02, 04, 2) or coupon (98–99) prefixes.
- **Sale price:**
  - `sale_price` currency must match `price`;
  - `sale_price_effective_date` must be in the right format and order.
- **Duplicate ids:** **every copy is kept and flagged**. The message says whether the copies are identical or different.
- **Broken XML:** reported with the exact line and column. This includes an unescaped `&`.

**WARNING — conditional rules**

- **Identifiers:** a missing GTIN/MPN without `identifier_exists=no`, or `identifier_exists=no` while a GTIN is present.
- **Brand:** missing for new products, or a placeholder brand such as "N/A" or "Generic".
- **Sale price:** a `sale_price` that isn't lower than `price`.
- **Price 0.**
- **Apparel (by target country):** missing `color`/`size`/`gender`/`age_group`. These are required in BR, FR, DE, JP, UK and US.
- **Text and images:** HTML in `description`, unsupported image formats, more than 10 additional images, colour values that are numbers or hex codes.
- **Dates:** `availability_date` more than a year ahead, `expiration_date` more than 30 days ahead.

**TIP — non-blocking**

- All-caps titles.
- Promotional text in titles.
- Missing `google_product_category`.

### What it fixes, and what it never does

**Fixes** (only lossless, deterministic ones; each is logged in the change log)

- whitespace trimming;
- repeated spaces in titles;
- `In Stock` → `in_stock` (and other unambiguous availability spellings);
- `New` → `new`, `TRUE` → `yes` for yes/no attributes;
- GTIN spaces and dashes removed (Google ignores them anyway);
- `8,50 EUR` → `8.50 EUR`;
- `USD 12.00` → `12.00 USD`.

**Never done**

- No invented ids, brands, GTINs, prices or stock.
- No guessed values for ambiguous input. `1,299.00 USD` and `sold out` are reported, not changed.
- No currency conversion.
- No uploads to Merchant Center.

### Start in 60 seconds

1. Press **Start** without a feed. The Actor validates a small **synthetic demo feed** (fictional products on `example.invalid`) and shows every kind of finding. No item or report events are charged for demo runs.
2. Enter your feed's **https URL**, or pick a key-value store record. Set your **target country** and run.

#### Example input

```json
{
    "feedUrl": "https://www.example.com/feeds/google-merchant.xml",
    "targetCountry": "US",
    "onlyItemsWithIssues": false
}
```

#### Example output row (SYNTHETIC demo data)

```json
{
    "id": "NV-101",
    "title": "Nordvik Kupa Şöğüç 350 ml",
    "status": "HAS_ERRORS",
    "errors": 1, "warnings": 0, "tips": 1,
    "issues": [
        { "severity": "ERROR", "code": "GTIN_INVALID", "field": "gtin", "value": "4006381333932",
          "message": "GTIN is not valid (CHECK_DIGIT).",
          "fix": "Use the manufacturer-assigned UPC/EAN/JAN/ISBN-13/ITF-14 with a correct check digit, or leave gtin empty.",
          "spec": "https://support.google.com/merchants/answer/7052112" }
    ],
    "fixesApplied": [
        { "field": "availability", "before": "In Stock", "after": "in_stock", "rule": "AVAILABILITY_CANONICAL_VALUE" },
        { "field": "price", "before": "8,50 EUR", "after": "8.50 EUR", "rule": "PRICE_DECIMAL_POINT" }
    ],
    "dataOrigin": "SYNTHETIC_DEMO"
}
```

### Input formats

| Format | Notes |
|---|---|
| RSS 2.0 and Atom XML | `g:` namespace; CDATA; nested `shipping` and `tax`. |
| CSV and TSV | Header names like `image link`, `image_link` and `g:image_link` are all accepted. |
| `.gz` files | Read directly. Decompression is capped, so a "zip bomb" is refused. |

Text encodings: UTF-8 with or without BOM, the HTTP charset, or the XML `encoding="…"` declaration (for example ISO-8859-9 for Turkish).

### Output

| Where | What |
|---|---|
| Dataset | One row per product: `status` (`OK` / `TIPS_ONLY` / `HAS_WARNINGS` / `HAS_ERRORS`), `issues` (severity, code, field, offending value, message, fix, spec source), `fixesApplied`, `dataOrigin`. |
| `ISSUES_CSV` | One line per finding. UTF-8 with BOM. Text formulas are neutralised; numbers stay intact. |
| `CHANGELOG_CSV` | Every fix as BEFORE/AFTER, with its rule. |
| `NORMALIZED_FEED_TSV` | Your feed as TSV with only the logged fixes applied. TSV cannot hold tabs or line breaks inside values; those become spaces, and the report counts them. |
| `REPORT` | Counts by status, severity and rule, with the spec version. |
| `OUTPUT` | Run status: `SUCCEEDED`, `PARTIAL_RESULT` or `EMPTY_FEED`, or `FAILED` with an error code. Also counts. |

Error codes on `FAILED`:

- `INPUT_INVALID`
- `ACCESS_DENIED`
- `NOT_FOUND`
- `RATE_LIMITED`
- `UPSTREAM_UNAVAILABLE`
- `INPUT_TOO_LARGE`
- `PARSE_ERROR`
- `UNSUPPORTED_FORMAT`

### Limits and performance

These figures were measured on synthetic feeds on a VPS:

| Feed | Time | Peak memory |
|---|---|---|
| 10,000 products (6.6 MB) | 0.5 s | about 300 MB |
| 100,000 products (66.7 MB) | about 7 s | about 1.3 GB |

- Default memory is 1024 MB, which covers the default limit of 10,000 products. For about 100,000 products, set 2048 MB.
- `maxItems` defaults to 10,000 and can go up to 200,000.
- `maxFileSizeMb` defaults to 50 and can go up to 200.

### Pricing

The Actor is not yet published, and **prices are not set yet**. The code is prepared for pay-per-event: one event per validated product (`feed-item-validated`) and one per run report (`feed-report`). Failed and empty feeds and demo runs charge nothing, and the spending limit is respected. These rules are tested with a simulated platform.

### Security and privacy

- Feed URLs must be HTTPS. Private, internal, loopback and cloud-metadata addresses are refused, also after redirects and at DNS resolution. URLs with embedded credentials or non-standard ports are refused too.
- Download size and decompressed size are capped.
- Offending values in the output are cut to 200 characters. Secrets in URL query strings (`token=`, `key=`, `sig=` …) are masked.
- Limited permissions: the Actor reads only the key-value store you pick.

### Known limitations and what is not verified

- **Not verified on a real merchant feed.** Tests used synthetic feeds and one publicly shared community example. That example is not well-formed as published; after two mechanical repairs, 6 of its 6 items passed without false findings.
- **Not compared with Google's official example file.** The file could not be downloaded (HTTP 403).
- **Not verified that this matches Merchant Center's own diagnostics.** The Actor cannot see landing pages, images, policies or account settings.
- Rules cover the attributes listed above. Shipping, tax, loyalty, subscription, energy-label and certification attributes are read but not deeply validated.
- Product category requirements (for example mobile phones or gift cards) are not checked against the full Google taxonomy.

### Support

Use the **Issues** tab on this Actor's page.

# Actor input Schema

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

Direct https:// link to your RSS 2.0 / Atom XML, CSV or TSV feed (.gz also works). Private and internal addresses are refused; the query string is never logged.

## `keyValueStoreId` (type: `string`):

Pick a store that holds the feed file, then enter the record key below.

## `recordKey` (type: `string`):

Key of the feed record in the selected key-value store.

## `demoMode` (type: `boolean`):

Validates a small fictional feed so you can see every kind of finding. No item or report events are charged. Ignored when a feed is given. Also runs automatically when no feed is given.

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

2-letter country you sell to (e.g. US, DE, GB). Used for country-dependent rules such as apparel color/size/gender/age_group (required in BR, FR, DE, JP, UK, US).

## `onlyItemsWithIssues` (type: `boolean`):

Hide products without any finding from the dataset (they are still checked and counted).

## `normalizedFeed` (type: `boolean`):

A TSV copy of the feed with only the safe, logged fixes applied.

## `maxItems` (type: `integer`):

Products checked per run. For about 100,000 products use 2048 MB memory.

## `maxFileSizeMb` (type: `integer`):

Downloads (and decompressed .gz) larger than this are refused.

## Actor input object example

```json
{
  "demoMode": false,
  "targetCountry": "US",
  "onlyItemsWithIssues": false,
  "normalizedFeed": true,
  "maxItems": 10000,
  "maxFileSizeMb": 50
}
```

# Actor output Schema

## `items` (type: `string`):

No description

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

No description

## `changelog` (type: `string`):

No description

## `feed` (type: `string`):

No description

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

No description

## `summary` (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 = {
    "targetCountry": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("privatex/google-merchant-feed-doctor").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 = { "targetCountry": "US" }

# Run the Actor and wait for it to finish
run = client.actor("privatex/google-merchant-feed-doctor").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 '{
  "targetCountry": "US"
}' |
apify call privatex/google-merchant-feed-doctor --silent --output-dataset

```

## MCP server setup

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

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/EZM6xlEeGbVLirmEr/builds/x2UcL20tedgYMLr9T/openapi.json
