# Merchant Feed Checker and Fixer for Google Merchant Center (`madrasco/merchant-feed-checker`) Actor

Checks a Google Merchant Center product feed (TSV, CSV, RSS 2.0 or Atom XML) against Google's published product data specification. One row per item with its problems, plus a corrected feed with only safe mechanical fixes and a change log. Never invents product data.

- **URL**: https://apify.com/madrasco/merchant-feed-checker.md
- **Developed by:** [Madrasco](https://apify.com/madrasco) (community)
- **Categories:** E-commerce, SEO tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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

## Merchant Feed Checker and Fixer for Google Merchant Center

Check a Google Merchant Center product feed before you upload it. Give the actor a feed link, upload the file, or paste it; it returns **one row per item** listing each problem found against Google's published product data specification, plus a **corrected copy of your feed** with only safe, mechanical fixes applied and a **change log** of every value it changed.

It never invents product data: titles, GTINs, brands, prices, currencies and categories are never made up or guessed. Anything that needs your own product information is reported, not changed.

### Supported feeds

- Tab-delimited text (`.txt` / `.tsv`) and comma-delimited CSV with a header row of attribute names (`id`, `title`, `image_link` or `image link`, with or without `g:`). Header names wrapped in single quotes (`'id'`) are rejected with an explanation, because Google would not recognise them.
- XML: RSS 2.0 or Atom 1.0 with the Google namespace `xmlns:g="http://base.google.com/ns/1.0"`. An Atom feed that leaves out the Atom namespace is still checked, with a warning. XML that is not well-formed is rejected with the line and column of the error (for a bare `&`, which must be written `&amp;`, the message says so); it is not repaired.
- Values the checker does not fix are written back exactly as they were read, including Windows line endings inside descriptions.
- Plain, `.gz`, or a `.zip` holding one feed file. The corrected feed is written in the same format (UTF-8).

### What it checks

| Rule (dataset `rule`) | What | Fixed automatically? |
|---|---|---|
| `missing-required` | id, title, description, link, image\_link, availability, price present and non-empty | No |
| `too-long` | id > 50, title > 150, description > 5,000, brand > 70, mpn > 70, additional\_image\_link > 2,000 characters | No (never truncated) |
| `duplicate-id` | the same id on more than one item | No |
| `availability-value` | must be in\_stock, out\_of\_stock, preorder or backorder | Yes for spelling variants ("In Stock", "out of stock", schema.org `InStock`); others reported |
| `condition-value` | must be new, refurbished or used | Yes for case/schema.org variants; others reported |
| `identifier_exists-value` | yes or no | Yes for TRUE/FALSE, Y/N |
| `price-format` | "15.00 USD" form: number, space, ISO 4217 code | Yes: currency moved after the amount, capitals, decimal comma "12,99" to "12.99", unambiguous thousands separators ("1,299.50", "1.299,00") |
| `price-currency` | currency code present and a real ISO 4217 code | No: a symbol such as $ or a missing code is reported, never guessed |
| `price-zero`, `price-value` | price of 0 (allowed only for some contract/subscription goods) or negative | No |
| `price-ambiguous` | warning for an amount with three digits after the point, like "1.299 EUR": Google keeps 2 decimal places and rounds the rest (1.299 becomes 1.30), and the point may be meant as a thousands separator | No |
| `sale-price-currency`, `sale-price-not-lower` | sale\_price in the same currency as price and lower than it | No |
| `missing-availability-date` | availability\_date required for preorder, asked for backorder | No |
| `date-format`, `date-order` | ISO 8601 dates (seconds and fractions of a second allowed); sale\_price\_effective\_date start before end | No |
| `gtin-format` | digits only, no spaces or dashes; spreadsheet scientific notation (4.00638E+12) | Spaces/dashes removed only when the result is a valid 12-, 13- or 14-digit GTIN (length and check digit); anything else, such as an SKU or a web address in the GTIN column, is reported unchanged; scientific notation reported |
| `gtin-length`, `gtin-check-digit` | 8, 12, 13 or 14 digits; GS1 check digit correct | No (a wrong GTIN is never "repaired") |
| `gtin-restricted` | restricted-circulation (02, 04, 2x) and coupon (98, 99) prefixes Google rejects | No |
| `gtin-isbn10` | ISBN-10 given as GTIN | Converted to ISBN-13 only when the item's google\_product\_category is Media > Books (784) or below and the ISBN-10 check digit is valid; any other 10-digit value is reported as `gtin-length`, not changed |
| `missing-identifiers` | no GTIN and not both brand and MPN (warning: depends on category) | No |
| `identifier-exists-conflict` | identifier\_exists is no but a GTIN is given | No |
| `brand-placeholder` | brand values such as "N/A", "Generic", "No brand" | No |
| `url-scheme` | link / image\_link start with http:// or https:// | No |
| `url-encoding` | spaces and other characters that must be percent-encoded | Yes (percent-encoded) |
| `image-format` | image URL ending in a non-accepted format (.svg, .avif, .heic, .pdf, ...); a URL ending in .html or .htm is a warning (some image services serve images from such addresses) | No |
| `category-unknown`, `category-format` | google\_product\_category is a taxonomy ID or exact path (English, or one of Google's translated taxonomies); not both | Yes for "ID - path" (ID kept) and spacing/capitals of an exact path |
| `missing-namespace-prefix` | XML attribute element without the g: prefix (Google ignores it) | Yes (moved into the g: namespace) |
| `whitespace` | leading/trailing spaces, repeated spaces (not in description), control characters | Yes |
| `unknown-attribute`, `extra-columns`, `encoding`, `missing-namespace` | whole-file problems | Encoding written as UTF-8; others reported |

### What it does not check

- It does **not** guarantee approval. Google also reviews landing pages, images (size, watermarks, placeholders), policies, shipping and tax settings and your account; none of that is checked here.
- It does not visit your links or images, compare the feed with your website, or check category-specific rules (apparel size/colour/gender, energy labels, alcohol, and so on).
- It does not check shipping, tax, installment or other group attributes (left unchanged in the corrected feed).
- Google Sheets and Content API sources are not read; export them as a file first.

### Input

- **Feed URL or file**: a direct link to the feed, or upload the file in the input form.
- **Or paste the feed**: for small feeds.
- **Include items with no problems** (default off), **Maximum file size** (default 100 MB) and **Maximum dataset rows** (default 1,000; 0 = no limit).

### Large feeds

The whole feed is read into memory. Our measurements on generated test feeds with defects seeded at known positions (every seeded problem reported, exact counts per rule, nothing else; run on our own machine, 2026-09-25):

| Feed | Items | File size | Run time | Peak memory |
|---|---|---|---|---|
| Tab-delimited text | 50,000 | 38 MB | 21 s | 405 MB |
| RSS 2.0 XML | 50,000 | 53 MB | 20 s | 490 MB |
| Tab-delimited text | 100,000 | 76 MB | 37 s | 739 MB |

Apify bills dataset writes per row (US$0.005 per 1,000 rows on its published pricing), which dominates the cost of a big feed with many problems: in our private test run on Apify of the 50,000-item text feed with every problem row written to the dataset (27,498 rows) the platform usage was US$0.14, of which US$0.137 was dataset writes and US$0.001 compute. That is why the dataset is capped at 1,000 rows by default; the complete lists are in `ISSUES.csv` and `CHANGES.csv`. The same feed with the default cap: 20 s, US$0.0086 platform usage (private run, 2026-09-25).

As a rule of thumb give the run about 10 to 12 times the file size in memory. The default run memory (1 GB) suits feeds up to about 80 MB; raise the run memory for bigger files. Run time on Apify can differ from these figures.

### Output

**Dataset**, one row per item (items with no problem are left out unless you turn them on; at most **Maximum dataset rows** rows, default 1,000):

```json
{"itemNumber": 3, "id": "SKU-003", "title": "Cast Iron Skillet 26 cm", "status": "error", "errors": 1, "warnings": 0, "fixed": 3,
 "problems": "FIXED availability: Normalised 'out of stock' to 'out_of_stock'.\nFIXED price: Reformatted price to '1299.00 EUR' (converted decimal comma).\nERROR gtin: GTIN '4006381333932' has a wrong check digit. ...",
 "issues": [{"attribute": "gtin", "severity": "error", "rule": "gtin-check-digit", "message": "...", "value": "4006381333932", "fixed": false, "source": "..."}]}
```

`status` is `error` (open errors left), `warning`, `fixed` (only safe fixes, now clean) or `ok`. Whole-file problems appear as item 0.

**Key-value store**

- `CORRECTED_FEED.txt` / `.csv` / `.xml`: your feed with only the fixes marked FIXED applied. Review `CHANGES.csv` before uploading it.
- `CHANGES.csv`: item number, id, attribute, rule, old value, new value for every change.
- `ISSUES.csv`: every open problem (item number, id, attribute, severity, rule, message, value), never capped.
- `REPORT.md`: summary, counts by rule and the items with problems.
- `OUTPUT`: JSON totals.

### Sources (accessed 2026-09-25)

- Google Merchant Center Help, *Product data specification*: https://support.google.com/merchants/answer/7052112 (required attributes, lengths, supported values, price/ISO 4217 format, GTIN rules, brand placeholders, image formats, google\_product\_category ID-or-path rule).
- Google Merchant Center Help, *RSS 2.0 specification*: https://support.google.com/merchants/answer/160589 (g: namespace; un-prefixed attributes are ignored).
- Google product taxonomy with IDs (version 2021-09-21 at the time of access): https://www.google.com/basepages/producttype/taxonomy-with-ids.en-US.txt (downloaded when the actor is built, not bundled).
- ISO 4217 currency codes, list one published 2026-09-17: https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/lists/list-one.xml
- GS1, *How to calculate a check digit manually*: https://www.gs1.org/services/how-calculate-check-digit-manually

Google can change its specification at any time; each dataset issue names its source so you can check it.

### Privacy

Your feed is read inside the run and the results are written to your own Apify storage. Nothing is sent anywhere else, and no links or images in the feed are visited.

### Support

Open an issue on the actor's Issues tab. This actor is built and maintained with AI assistance by Madrasco; a human owner can be reached on request through the Issues tab.

### Disclaimer

Independent tool, not affiliated with or endorsed by Google. Google, Google Merchant Center and Google Shopping are trademarks of Google LLC, named here only to describe the feeds this tool checks. Passing these checks does not guarantee that Google will approve your products or account.

# Actor input Schema

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

Link to your product feed, or upload the file. Tab-delimited text (.txt/.tsv), CSV, RSS 2.0 or Atom XML; may be .gz or a .zip holding one feed file.

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

Paste a small feed here instead (header row plus items for text feeds, or the whole XML). Ignored if a feed URL or file is given. The form starts with a three-item example feed (made-up products) so a first run shows what the report looks like; replace it with your own.

## `includeCleanItems` (type: `boolean`):

By default the dataset lists only items with a problem or a fix. Turn on to get one row for every item.

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

Larger files are refused. Peak memory measured about 9-11x the file size; allow 10-12x (a 38 MB, 50,000-item text feed peaked at 405 MB; a 53 MB XML feed at 490 MB). The default 1 GB run memory suits feeds up to about 80 MB.

## `maxDatasetItems` (type: `integer`):

The dataset lists at most this many items (0 = no limit). Apify bills dataset writes per row (US$0.005 per 1,000 rows on its published pricing), so a 50,000-item feed with problems in half its items would cost about US$0.13 in dataset writes alone. The full list of open problems is always in ISSUES.csv and every change in CHANGES.csv.

## Actor input object example

```json
{
  "feedText": "id\ttitle\tdescription\tlink\timage_link\tavailability\tprice\tbrand\tgtin\tcondition\nDEMO-1\tMen's  Pique Polo Shirt \tClassic cotton pique polo.\thttps://shop.example.com/p/demo-1\thttps://cdn.example.com/img/demo-1.jpg\tIn Stock\t$25.00\tExample Apparel\t4006381333931\tNew\nDEMO-2\tCast Iron Skillet 26 cm\tPre-seasoned skillet.\thttps://shop.example.com/p/demo-2\thttps://cdn.example.com/img/demo-2.jpg\tout of stock\t1.299,00 EUR\tExample Kitchen\t4006381333932\tnew\nDEMO-3\tEnamel Mug\tCamping mug.\twww.example.com/p/demo-3\t\tavailable\t15.00\t\t\trefurb\n",
  "includeCleanItems": false,
  "maxFileSizeMb": 100,
  "maxDatasetItems": 1000
}
```

# Actor output Schema

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

One dataset row per feed item with its problems.

## `files` (type: `string`):

The corrected feed (safe mechanical fixes only), CHANGES.csv, ISSUES.csv and REPORT.md.

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

Feed format and totals of items, problems and fixes.

# 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	brand	gtin	condition
DEMO-1	Men's  Pique Polo Shirt 	Classic cotton pique polo.	https://shop.example.com/p/demo-1	https://cdn.example.com/img/demo-1.jpg	In Stock	$25.00	Example Apparel	4006381333931	New
DEMO-2	Cast Iron Skillet 26 cm	Pre-seasoned skillet.	https://shop.example.com/p/demo-2	https://cdn.example.com/img/demo-2.jpg	out of stock	1.299,00 EUR	Example Kitchen	4006381333932	new
DEMO-3	Enamel Mug	Camping mug.	www.example.com/p/demo-3		available	15.00			refurb`
};

// Run the Actor and wait for it to finish
const run = await client.actor("madrasco/merchant-feed-checker").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\ttitle\tdescription\tlink\timage_link\tavailability\tprice\tbrand\tgtin\tcondition
DEMO-1\tMen's  Pique Polo Shirt \tClassic cotton pique polo.\thttps://shop.example.com/p/demo-1\thttps://cdn.example.com/img/demo-1.jpg\tIn Stock\t$25.00\tExample Apparel\t4006381333931\tNew
DEMO-2\tCast Iron Skillet 26 cm\tPre-seasoned skillet.\thttps://shop.example.com/p/demo-2\thttps://cdn.example.com/img/demo-2.jpg\tout of stock\t1.299,00 EUR\tExample Kitchen\t4006381333932\tnew
DEMO-3\tEnamel Mug\tCamping mug.\twww.example.com/p/demo-3\t\tavailable\t15.00\t\t\trefurb
""" }

# Run the Actor and wait for it to finish
run = client.actor("madrasco/merchant-feed-checker").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\\ttitle\\tdescription\\tlink\\timage_link\\tavailability\\tprice\\tbrand\\tgtin\\tcondition\\nDEMO-1\\tMen'\''s  Pique Polo Shirt \\tClassic cotton pique polo.\\thttps://shop.example.com/p/demo-1\\thttps://cdn.example.com/img/demo-1.jpg\\tIn Stock\\t$25.00\\tExample Apparel\\t4006381333931\\tNew\\nDEMO-2\\tCast Iron Skillet 26 cm\\tPre-seasoned skillet.\\thttps://shop.example.com/p/demo-2\\thttps://cdn.example.com/img/demo-2.jpg\\tout of stock\\t1.299,00 EUR\\tExample Kitchen\\t4006381333932\\tnew\\nDEMO-3\\tEnamel Mug\\tCamping mug.\\twww.example.com/p/demo-3\\t\\tavailable\\t15.00\\t\\t\\trefurb\\n"
}' |
apify call madrasco/merchant-feed-checker --silent --output-dataset

```

## MCP server setup

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

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/zcbrGqOEatnZkuNyf/builds/W0jmO7ctXqA6WJykL/openapi.json
