What it is
Product master data — one row per variant (per size/version, not per model). Combines shop-synced facts (SKU, name, price, stock, active flag) with procurement master data you maintain in the app: supplier assignment, purchase price, MOQ, lead times, safety/target stock days, seasonality profile, and lifecycle status. Variants of the same model are grouped byproduct_family (source product gid) with a human-readable product_family_label derived from the title. The procurement views — Product Demand, Procurement Queue, Family Trend — are all computed on top of this table.
How it gets data
Ingest-enabled — shop syncs push rows through the Ingest API; the procurement columns are edited in the app (or via MCPdata_row_update) and survive re-syncs:
- One JSON record per request; unknown fields are dropped.
- Upsert identity is the variant, not the SKU: products upsert on
(tenant_id, identity_key)whereidentity_key = COALESCE(external_id, sku). Send the source variant id asexternal_id(Shopify ProductVariant gid) — several sizes of one model can share a SKU, and withoutexternal_idthey would overwrite each other. Integrations that only have a SKU may omitexternal_id; then the SKU is the identity. tenant_id/workspace_idcome from the API key context, never from the body.- A ready-made Make.com flow template is downloadable in Settings → Data Sources.
Fields
Identity & shop facts
Procurement master data (maintained in the app)
“ingest” = populated through the ingest endpoint; read-only in the app. ✓ = editable in the app and settable on create; edits survive shop re-syncs. “API only” = real database column not registered as a visible app column. “trigger” = computed by the database on write.
Like every workspace table, products also carry the system columns
id, tenant_id, workspace_id, created_at, and updated_at — set automatically, never sent by integrations.