Knit Bundles — Headless Integration Guide
Knit Bundles exposes public endpoints for headless storefronts, mobile apps, and custom integrations that don’t run on the standard Shopify Liquid theme. There are two ways to integrate, depending on how much control you want:
Both modes are served from the Knit app origin, not from your
myshopify.com
storefront. This is deliberate — see Why the app origin?
below.
Base URL
/headless/*. Every request identifies the
target store with a shop query parameter:
Prerequisites
- Install the Knit Bundles app on your Shopify store.
- Configure at least one active bundle.
- Open the app once in the Shopify admin — this provisions the Storefront
access token the headless endpoints need. (Until this happens, the endpoints
return
APP_NOT_INSTALLED.)
Endpoint 1 — Bundle Data
Query parameters
Example
Response
Caching
Successful responses are sent with:Endpoint 2 — Embed (iframe)
<iframe> and you’re done rendering — you only need to handle
add-to-cart (see postMessage bridge).
Query parameters
Usage
Cache-Control: no-store and without
X-Frame-Options / frame-ancestors, so it is framable from any origin.
postMessage bridge
The embed iframe communicates with your host page overwindow.postMessage.
It renders the widget and manages selection state itself, but it cannot add
to your cart — that’s your job. You must listen for the messages below.
Every message includes source, v (version), and type. Ignore any message
whose source/v you don’t recognize.
Messages from iframe → your page
source: "nameless-bundle", v: 1
Messages from your page → iframe
source: "nameless-bundle-host", v: 1
lineItems shape
Critical: the __FB_ATC_UID attribute must reach Shopify as a line-item
property. The Knit Discount Function uses it to group bundle lines and apply
the tier discount. If your cart layer strips custom line-item properties, the
discount will not apply.
Minimal host handler
addToCart within ~10 seconds, the widget resolves the
action as failed (CART_TIMEOUT) and shows an error to the shopper.
Response schema reference
The/headless/bundles endpoint returns { ok: true, bundles: Bundle[] }.
Each Bundle has this shape (TypeScript):
How pricing works
Bundle discounts are expressed as conditions → rewards, not as precomputed prices:- A
conditionSetfires when itsconditionsare met for the referenced selector(s) — e.g. aquantitycondition withminimum: 2, maximum: 2fires when the shopper picks quantity 2. - The matching
rewardSet.rewardsthen apply — e.g.percentageDiscountof 19% off, or afixedDiscountamount.
conditionSet to its exact
quantity (minimum === maximum) and read the reward for that selector. See the
example renderer for a complete implementation pattern.
Error responses
Errors return{ ok: false, code, message } with an HTTP status:
The
/headless/embed endpoint returns plain-text 400 / 404 bodies for the
same missing-param / not-enabled conditions.
Why the app origin?
Headless clients can’t frame{shop}.myshopify.com directly: Shopify sends
Content-Security-Policy: frame-ancestors 'none' on every storefront response,
and it isn’t merchant-configurable. So the embed page is served from the Knit
app origin, which sets no such header and is therefore framable anywhere.
Because the page lives on the app origin (not the storefront), the widget fetches
its data from /headless/bundles (same-origin, no CORS) rather than the storefront
App Proxy.
Note on the App Proxy endpoints
You may see references tohttps://{shop}.myshopify.com/apps/proxy/bundles and
/apps/proxy/embed. Those are HMAC-signed App Proxy endpoints intended for hosts
that can frame the storefront origin. They return the same normalized
Bundle[] schema documented above. For headless apps and mobile/WebView hosts,
use the /headless/* endpoints in this guide instead.
Support
Include yourshop domain, the endpoint URL, and the full JSON error response
(or the [nameless] console logs from the embed iframe) when contacting support.