Product feeds
Google Shopping XML, CSV and JSON feeds for Generic stores: supported fields, schedules, authentication, ETag caching and full-replace semantics.
ShopAssist pulls feeds from your server on a schedule. A store can have up to 10 feeds: a primary products feed, translation feeds, extra-currency feeds and local inventory feeds.
Formats
| Format | Use when | Notes |
| ------------------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| Google Shopping XML (RSS 2.0 or Atom) | You already have a Merchant Center feed | Elements in the g: namespace; extensions in xmlns:sa="https://shopassist.coec.dk/ns/feed/1" |
| CSV | Spreadsheet or ERP exports | Delimiter detected (, ; tab |), UTF-8 (with or without BOM), Windows-1252 or ISO-8859-1 |
| JSON / JSON Lines | Your own REST endpoint or export | Needs a field mapping (below) |
Feeds may be gzip- or deflate-compressed. ZIP archives are not supported.
Google Shopping fields
Each <item> is one variant; items with the same item_group_id form one product.
| Field | Used as | Rules |
|---|---|---|
id | Variant id (product id without item_group_id) | Required |
item_group_id | Product id | Groups variants |
title | Variant title; the product title is the common prefix of the group (or sa:product_title) | |
description | Product description | HTML is sanitized |
link | Product URL | HTTPS, on one of your allowed domains |
image_link, additional_image_link | Images | HTTPS; up to 10 additional |
availability | Stock status | in_stock, out_of_stock, backorder, preorder |
price, sale_price, sale_price_effective_date | Price, sale price and sale window | Currency must equal the feed currency |
brand, gtin, mpn | Brand, GTIN, attributes | |
color, size, material, pattern | Variant options when they differ in the group | |
product_type | Category path (A > B > C) | |
product_detail, product_highlight | Attributes the assistant can answer from | |
sa:sku, sa:quantity | SKU and stock quantity | Quantity is an integer ≥ 0 |
sa:compare_at_is_reference | true when the regular price is the lowest price of the last 30 days | See prices |
sa:add_to_cart_url, sa:tag, sa:rating, sa:rating_count, sa:is_published | Cart URL, tags, rating, visibility |
custom_label_*, shipping, tax and promotion fields are ignored.
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0" xmlns:sa="https://shopassist.coec.dk/ns/feed/1">
<channel>
<item>
<g:id>SHIRT-42-BLUE</g:id>
<g:item_group_id>SHIRT-42</g:item_group_id>
<g:title>Linen shirt – Blue</g:title>
<g:link>https://shop.example.com/products/linen-shirt</g:link>
<g:image_link>https://shop.example.com/img/linen-blue.jpg</g:image_link>
<g:availability>in_stock</g:availability>
<g:price>499.00 DKK</g:price>
<g:color>Blue</g:color>
<sa:quantity>12</sa:quantity>
</item>
</channel>
</rss>CSV
Use the Google field names as column headers (id, title, image link, g:price all work; ShopAssist extensions use the sa_ prefix, for example sa_quantity). Multi-value cells: additional_image_link, sa_tag and gtin are split on commas. If your headers differ, map them in the feed settings, for example {"id": "SKU", "price": "Pris", "availability": "Lager"}.
JSON
JSON feeds need a mapping that selects the records and maps fields with a small JSONPath subset ($, @, .name, [0], [*], and filters like [?(@.visible == true)]). Transforms such as trim, int, decimal, cents, money, url and map:<name> convert values. Pagination follows a next-page URL or a page parameter.
{
"version": 1,
"format": "json",
"records": "$.data.products[*]",
"record_kind": "product",
"fields": {
"external_id": "@.id",
"title": { "path": "@.name", "transforms": ["trim"] },
"url": { "path": "@.permalink", "transforms": ["url"] }
},
"variants": {
"records": "@.variants[*]",
"fields": {
"external_id": "@.id",
"sku": "@.sku",
"price": { "path": "@.price_cents", "transforms": ["cents"] },
"available_qty": { "path": "@.stock", "transforms": ["int"] }
}
}
}Use Test feed in the dashboard to fetch the feed and see up to 20 mapped samples with errors before you save.
Schedules
Each feed is fetched every schedule_minutes (at least 60). A forced refetch runs every night regardless of caching. You can start a run manually in Catalog → Sync.
Authentication
Feeds can be public, use HTTP Basic authentication or a custom header (for example Authorization: Bearer … or X-Api-Key). Credentials are stored encrypted and never shown again; changing the feed's host removes them. ShopAssist fetches feeds from its egress IPs, so you can allowlist them. Redirects are not followed: use the final URL.
ETag and caching
ShopAssist sends If-None-Match and If-Modified-Since. A 304 Not Modified answer completes the run without changes, which keeps scheduled runs cheap for large feeds.
Full feeds and deltas
- A full primary feed is authoritative: products missing from a successful run are removed. If more than half of your products would disappear at once, ShopAssist stops and asks you to confirm (
catalog_shrink_suspected). - A full primary feed also blocks product pushes through the Push API (
409 feed_authoritative), because the next feed run would undo them. - A delta feed only adds and updates. Remove products with the Push API
products:deleteor a full feed. - An empty feed that previously had products is treated as an error; nothing is removed.
Local inventory feeds
A local inventory feed carries stock per store, in Google's local inventory format (XML or CSV) or JSON:
| Field | Used as |
|---|---|
store_code | Location id (unknown codes create a location you complete in the dashboard) |
id | Variant id or SKU |
quantity | Stock in that store |
availability | in_stock, limited_availability, on_display_to_order, out_of_stock |
pickup_method, pickup_sla | Pickup options |
Local inventory feeds also work for WooCommerce and PrestaShop stores that have no stock per store in the platform.
Last updated on