Commerce Data
The commerce layer reads your store through one consistent, scope-bound contract: normalized orders, a unified customer view, subscriptions, the live product catalog, and connected integrations. Every response is bound to your brand’s connected shop — see Tenant Isolation & Scope.
The beta gate
Section titled “The beta gate”The commerce, customers, insights, and integrations endpoints require the
beta:cdp-commerce permission on your brand and a connected Shopify
integration. Both checks fail as a structured 403 with error: "forbidden" and
a machine-readable code, never a 500:
| Condition | 403 body |
|---|---|
| Brand not enrolled in the commerce beta | { "error": "forbidden", "code": "beta_not_enabled" } |
| No connected Shopify integration / no resolvable shop | { "error": "forbidden", "code": "integration_not_connected" } |
Branch on the code, not on the error_description text. Once you clear both,
every read is scoped to the shop resolved from your key — you never pass a scope,
and you can never widen it.
How data is served
Section titled “How data is served”Reads fall into two serving modes, and it matters because it drives freshness:
| Data | Served from | Freshness |
|---|---|---|
| Orders, unified customers, subscriptions, insights | Data synced from your store | Reflects the most recent sync from your store |
| Product catalog | Read live from Shopify (proxied) | Live, behind a short read-through cache (~30s) |
- Synced reads (orders, unified customers, subscriptions, and all insights) come from a copy of your store data. No live Shopify call happens on these reads, so they stay fast and predictable; their freshness reflects the last sync.
- The product catalog (list and single product) is read live from Shopify through a proxy. A short read-through cache of about 30 seconds sits in front so bursts of requests don’t hammer Shopify.
Normalized, backend-agnostic contracts
Section titled “Normalized, backend-agnostic contracts”Responses are normalized into a stable, Voyage-owned shape. The raw
Shopify or provider payload, the integration’s settings, and any credentials
are never exposed. Because the contract is decoupled from the underlying
provider, it stays the same even if the serving backend changes later — your app
depends on the documented fields, not on Shopify’s raw API shape.
Money and completed orders
Section titled “Money and completed orders”- Money is a dollar-denominated decimal string in the store’s currency, such
as
"120.00"— never an integer count of cents. - Monetary fields count completed orders only (
state = "complete"). Orders in any other state — pending, draft, delivered, cancelled, or refunded — are excluded from spend totals, lifetime value, and every insight. Order-listing endpoints still return all states; the completed-only rule applies to the monetary math.
The products proxy
Section titled “The products proxy”The product catalog is the one live-proxied surface, so it has a few semantics of its own.
-
Cursor pagination. Product pages use an opaque
nextCursor(Shopify’s cursor), not offset paging. FollownextCursoruntil it isnull. See Pagination. -
Requires products read access. The connected Shopify token must be able to read products — the
read_productsscope, orwrite_products, which Shopify treats as granting read access. If it can’t, the proxy returns a clean403— never a500:{"error": "forbidden","code": "missing_shopify_scope","missing_shopify_scope": "read_products","error_description": "The connected Shopify token is missing read_products."} -
Throttling always surfaces as
429withRetry-After. The proxy has a dedicated per-brand throttle that protects your Shopify request budget, and it also passes through Shopify’s own throttling after bounded back-off. Either way you get a429and aRetry-Afterheader telling you how long to wait — never a500:Response Meaning 429 { "error": "rate_limited", "code": "products_proxy_throttled" }The per-brand proxy throttle tripped. Retry-After: 1.429 { "error": "shopify_throttled" }Shopify throttled the request. Honor the Retry-Afterheader. -
Unknown product id →
404 { "error": "not_found" }.
Where to go next
Section titled “Where to go next”- List Orders and Get Customer — the synced order and unified-customer contracts.
- List Products — the live product catalog.
- List Subscriptions — synced subscription contracts.
- List Integrations — connected integrations, without settings or credentials.
- Insights — how LTV, RFM, product, and churn metrics are computed.
- Errors and Tenant Isolation & Scope — the error envelope and scope guarantees.