The
/sample, /test, and public-feed reads need no key at all. Only the live import
(ingestProducts) needs sk + HMAC. See
Key Usage by Endpoint.1. A sample feed to model yours after
A real, valid sample feed carries the exact field names and covers both a single-variant product and a multi-variant one, whose variants share aproduct_group. No key required.
Response
2. Dry-run validation, including a bad row
The validator parses every row and returns the samesummary and errors a real import would,
plus a normalized_preview of how Galactic Core interpreted each row — while writing nothing. It
needs no key, so the format can be settled before any request is signed.
A feed carrying one valid row and one invalid row (a negative price) shows the contract in full: the
validator accepts the good row, rejects the bad one, and status becomes partial:
Response
status—valid(no failures),partial(some rows rejected), orinvalid(all rejected).summary— the exact counts a real import would produce:rows_total/rows_created/rows_updated/rows_skipped/rows_failed.errors[]— one entry per rejected row, naming the 1-basedrow, the offendingsku, thefieldthat failed, and a readablereason, hereprice must be a positive number.normalized_preview[]— every row as Galactic Core interpreted it, withcategoryandbrandresolved and anything it could not infer leftnull, which is where the mapping is confirmed before signing.
status: valid, rows_failed: 0, and an empty errors array.
Flagged rows can be fixed and re-run against the validator as many times as needed, since the live
request is signed only once the counts are right.
3. Pushing the catalog (secret key + HMAC)
The real write is upsert-by-SKU: an existingsku is updated, a new one created. Rows are applied
independently, so one bad row never fails the batch, and the response carries created / updated /
skipped / failed counts plus an errors array for any rejections.
Three signing headers are required, on the same scheme as orders and payments: an Idempotency-Key,
where retrying with the same key returns the original result rather than re-importing; an
X-Timestamp in unix seconds, within 5 minutes of server time; and an X-Signature, the base64
HMAC-SHA256 of `${timestamp}.${body}` signed with the store’s signing secret from Integrations →
API Keys.
rows_created: 2, errors: []:
Response
rows_updated: 2, nothing
duplicated, which is what makes the import safe to run on a schedule. A batch mixing good and bad
rows returns status: partial with the per-row errors array, as the dry run did, and the good rows
are still written.
Four rules govern a first import:
- A category must already exist. Categories are a fixed platform-level set, so a row whose
categorydoes not match one of the store’s enabled categories is rejected, never auto-created. The name must match exactly. - A subcategory is auto-created. Subcategories are the merchant’s to define freely, and an
unknown one is created on the fly.
>nests them, as incategory: "Electronics",subcategory: "Audio > Headphones". - Variants group by
product_group. Rows sharing aproduct_groupbecome variants of one product, each still carrying its own uniquesku. - CSV and XML behave identically.
format: 'csv'(or'xml') with a string body follows the same upsert and partial-success rules. A CSV body is the header row plus data rows as one string:
Carrying your own columns
A feed is not limited to the fields above. A column that matches the label of a custom field defined on products for the store is recorded on the product; anything else is ignored, so extra columns are safe to leave in.4. Publishing the catalog back out
The inverse direction. Opting in under admin → Catalog Sync → “Publish my catalog” makes the catalog readable at a stable public URL with no key required, so any system can pull it — another store’s scheduled sync, a partner integration, a script. Only storefront-safe fields are exposed, never cost or margin. The SDK call below abstracts the host away; the dashboard shows the full URL to copy, and where the store belongs to an operated platform that URL carries the platform’s own host — see Publish your own catalog.Response
subcategory, brand, and sale_price are nullable: the first product has subcategory: null
and a sale_price of 129, being on sale, while the second has sale_price: null and is not. The
feed shape matches what the ingestion endpoints accept, which makes a store-to-store sync a direct
pull-and-ingest with no field mapping. server.ingestion.getStoreCatalogFeedXml({ store }) returns
the same data as an XML document. A feed protected with a token takes it as
{ store, token: 'YOUR_FEED_TOKEN' }; a wrong or missing token, or a store that has not opted in,
returns 404.What’s next
feed.sync.completedin Webhooks & automation fires the moment an inbound feed-pull finishes.- With products in, Search & discovery and the Storefront checkout come next.

