# Storefront Cart API probe — `checkoutUrl` host, served API version, Buyer-IP, static-token write scope Live, one-shot probe of the Shopify Storefront **Cart API** behind `mozimo.in`. Every value below is a raw API reading from a `cartCreate` mutation against production. Facts only; no recommendations. ## Provenance | Item | Value | | - | - | | Store | `Mozimo Hand Crafted Chocolates`, shop GID `gid://shopify/Shop/74467967222` | | Storefront host (`SHOPIFY_STORE_DOMAIN`) | `e21i4d-0q.myshopify.com` | | Public / primary domain | `shop.mozimo.in` (per [`shopify-catalog-inventory.md`](../shopify-catalog-inventory.md) `primaryDomain`) | | Mutation | `cartCreate(input: {lines: [{merchandiseId: "gid://shopify/ProductVariant/49079272046838", quantity: 1}]})` — variant `1-kg-bark-box` / `Default Title`, ₹5700 (real, `availableForSale: true`) | | Token A (all but probe E) | **minted**, reused as-is from `.cache/shopify-token.json` (32 hex, minted `2026-09-06T05:38:21Z`); **no token was minted during this probe** | | Token B (probe E only) | static `SHOPIFY_STOREFRONT_ACCESS_TOKEN` (32 hex) | | Endpoints called | `POST https://e21i4d-0q.myshopify.com/api//graphql.json` and `POST https://shop.mozimo.in/api//graphql.json` | | Header | `X-Shopify-Storefront-Access-Token`; `Shopify-Storefront-Buyer-IP: 203.0.113.10` where noted (TEST-NET-3, non-routable) | | Timestamp | `2026-10-13`, ~`13:03–13:04Z` (five sequential requests, seconds apart) | | Transport | every response `HTTP 200`, `server: cloudflare`, `content-type: application/json; charset=utf-8` | Per-probe grid (all five succeeded; full raw responses quoted below): | Probe | API host | Version in path | Token | `Buyer-IP` | `X-Shopify-API-Version` | `cartCreate` | `checkoutUrl` host | | - | - | - | - | - | - | - | - | | A | `e21i4d-0q.myshopify.com` | `2026-10` | minted | sent | `2026-10` | 200, `userErrors: []` | `shop.mozimo.in` | | B | `shop.mozimo.in` | `2026-10` | minted | sent | `2026-10` | 200, `userErrors: []` | `shop.mozimo.in` | | C | `e21i4d-0q.myshopify.com` | `2025-01` | minted | sent | `2025-10` | 200, `userErrors: []` | `shop.mozimo.in` | | D | `e21i4d-0q.myshopify.com` | `2026-10` | minted | **absent** | `2026-10` | 200, `userErrors: []` | `shop.mozimo.in` | | E | `e21i4d-0q.myshopify.com` | `2026-10` | **static** | sent | `2026-10` | 200, `userErrors: []` | `shop.mozimo.in` | --- ## 1. THE MAIN QUESTION — `checkoutUrl` host **Answer: (b) the public `shop.mozimo.in` custom domain.** Not the `*.myshopify.com` domain, not the brand apex `mozimo.in`, and not any other Shopify host. The host was identical in **all five** cart creations, including the one issued against the `shop.mozimo.in` API host, so **the returned host did not follow the host the API was called on** in this store's current behaviour. Canonical redacted form (identical host + path shape in every probe; only the opaque token/key differ): ``` https://shop.mozimo.in/cart/c/?key= ``` - Scheme/host: `https://shop.mozimo.in` - Path: `/cart/c/` - Query: `key=%3D%3D` (the key is part of the cart token material) - Fragment: none Raw response bodies, quoted verbatim except for the redactions noted (cart token in `/cart/c/`, the `?key=` value, the `Cart` GID payload, and the `?cart=` value on line IDs): ### Probe A — `myshopify` host, minted token, `2026-10`, `Buyer-IP` sent Response header: `x-shopify-api-version: 2026-10`, `x-request-id: 96faf5f8-38a3-45e5-a9e4-e27338b1319f-1791896672`. ```json { "data": { "cartCreate": { "cart": { "id": "gid://shopify/Cart/?key=", "checkoutUrl": "https://shop.mozimo.in/cart/c/?key=", "cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } }, "lines": { "nodes": [ { "id": "gid://shopify/CartLine/a49a80f0-85ac-463f-9524-08418b4d911a?cart=", "quantity": 1, "merchandise": { "id": "gid://shopify/ProductVariant/49079272046838", "title": "Default Title" } } ] } }, "userErrors": [] } }, "extensions": { "cost": { "requestedQueryCost": 17 } } } ``` ### Probe B — `shop.mozimo.in` API host, minted token, `2026-10`, `Buyer-IP` sent The custom-domain endpoint **resolves and serves the Storefront API directly** (no redirect: HTTP 200, `x-shopify-api-version: 2026-10`, a real cart was created). Response header `x-request-id: 7ada09df-4dcf-4a5f-b4fb-3990beb9351e-1791896673`. ```json { "data": { "cartCreate": { "cart": { "id": "gid://shopify/Cart/?key=", "checkoutUrl": "https://shop.mozimo.in/cart/c/?key=", "cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } }, "lines": { "nodes": [ { "id": "gid://shopify/CartLine/14ecdea2-921b-4803-a859-d5c55118f999?cart=", "quantity": 1, "merchandise": { "id": "gid://shopify/ProductVariant/49079272046838", "title": "Default Title" } } ] } }, "userErrors": [] } }, "extensions": { "cost": { "requestedQueryCost": 17 } } } ``` **Does the returned `checkoutUrl` host differ when calling the API at `shop.mozimo.in`? No.** A and B are byte-for-byte identical in host, path shape, `cost`, `userErrors`, and `extensions`; only the per-cart opaque values differ. > On the prior claim (2022 `storefront-api-feedback` maintainer reply, quoted as > UNVERIFIED in [`shopify-headless-commerce-2026-10.md`](shopify-headless-commerce-2026-10.md#13-checkouturl-behaviour-and-domain)) > that *"the returned domain follows the domain you call the API on"*: **this > store does not reproduce it.** Calling the API on the `*.myshopify.com` host > (probes A, C, D, E) still returned `shop.mozimo.in`. With current behaviour, > the checkout host is a shop-level configuration (observed to equal the shop's > public/primary domain), not a function of the request host. ### Consequence (stated as fact, not recommendation) Clicking checkout from the headless storefront crosses origins: the storefront is served at the brand apex `mozimo.in`, while `checkoutUrl` targets the Online-Store subdomain `shop.mozimo.in`. The redirect therefore **leaves the `mozimo.in` origin** but stays on merchant-branded domains — it does **not** leave for a `*.myshopify.com` host. --- ## 2. API version actually served `POST https://e21i4d-0q.myshopify.com/api/2025-01/graphql.json` (the version pinned in [`src/lib/shopify.ts`](../../src/lib/shopify.ts) as `API_VERSION`), minted token, `Buyer-IP` sent: - `HTTP 200` - `x-shopify-api-version: 2025-10` ```json { "data": { "cartCreate": { "cart": { "id": "gid://shopify/Cart/?key=", "checkoutUrl": "https://shop.mozimo.in/cart/c/?key=", "cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } }, "lines": { "nodes": [ { "id": "gid://shopify/CartLine/020edcf0-31c4-4b7c-a10a-f694bb6cf765?cart=", "quantity": 1, "merchandise": { "id": "gid://shopify/ProductVariant/49079272046838", "title": "Default Title" } } ] } }, "userErrors": [] } }, "extensions": { "cost": { "requestedQueryCost": 17 } } } ``` **This re-confirms the earlier finding:** requesting `2025-01` is served as **`2025-10`**, *not* the oldest supported stable version (`2026-01`) and not `2026-10`. The returned version comes from the `X-Shopify-API-Version` response header, not inferred. `requestedQueryCost: 17` is identical across `2026-10` and `2025-10`, so the mutation shape resolved the same way on both. --- ## 3. `Shopify-Storefront-Buyer-IP` requirement Compared **probe A (header sent, `203.0.113.10`)** against **probe D (header absent)**, both `POST …/api/2026-10/graphql.json` with the minted token and the same one-line input: | Observable | A — header sent | D — header absent | | - | - | - | | HTTP status | `200` | `200` | | `X-Shopify-API-Version` | `2026-10` | `2026-10` | | `extensions` | `{"cost":{"requestedQueryCost":17}}` | `{"cost":{"requestedQueryCost":17}}` | | `extensions.cost.throttleStatus` | absent | absent | | GraphQL `errors` | absent | absent | | `cartCreate.userErrors` | `[]` | `[]` | | Cart `cost.totalAmount` | `5700.0 INR` | `5700.0 INR` | | `checkoutUrl` host | `shop.mozimo.in` | `shop.mozimo.in` | | `cartCreate.warnings` | not requested; none returned | not requested; none returned | Probe D body (verbatim but redacted): ```json { "data": { "cartCreate": { "cart": { "id": "gid://shopify/Cart/?key=", "checkoutUrl": "https://shop.mozimo.in/cart/c/?key=", "cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } }, "lines": { "nodes": [ { "id": "gid://shopify/CartLine/411958db-d072-4b56-bb50-629d9585ab3a?cart=", "quantity": 1, "merchandise": { "id": "gid://shopify/ProductVariant/49079272046838", "title": "Default Title" } } ] } }, "userErrors": [] } }, "extensions": { "cost": { "requestedQueryCost": 17 } } } ``` **Honest result: no measurable difference on a single request.** Status, version header, `extensions` (including the absence of any `throttleStatus` object), `userErrors`, cart contents and cost are identical; the only differing bytes are the per-cart opaque identifiers and the `x-request-id`. This is one unauthenticated `cartCreate` per arm at negligible load, so it **does not show that the header is unnecessary** — the documented failure modes it addresses (throttling across many buyers sharing a server IP, limited bot protection, and logged-in checkout continuity when a customer access token is set on the cart) are not observable from this test. Sending it changed nothing here; not sending it broke nothing here. --- ## 4. Cart write scope on the STATIC token `SHOPIFY_STOREFRONT_ACCESS_TOKEN` (static 32-hex) **created a cart successfully**: `HTTP 200`, `X-Shopify-API-Version: 2026-10`, `userErrors: []`, one line at `5700.0 INR`. There was **no access-scope error** and no `ACCESS_DENIED`. ```json { "data": { "cartCreate": { "cart": { "id": "gid://shopify/Cart/?key=", "checkoutUrl": "https://shop.mozimo.in/cart/c/?key=", "cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } }, "lines": { "nodes": [ { "id": "gid://shopify/CartLine/652b70c8-7e11-4e34-8c20-2b4bbb72512e?cart=", "quantity": 1, "merchandise": { "id": "gid://shopify/ProductVariant/49079272046838", "title": "Default Title" } } ] } }, "userErrors": [] } }, "extensions": { "cost": { "requestedQueryCost": 17 } } } ``` So the static token holds the write scope needed for `cartCreate` — the load-bearing scope for the repo's production path (which prefers this token per `if (STATIC_TOKEN) return STATIC_TOKEN;`). Error text: **none returned.** Proven for `cartCreate` only; see "remains unverified". --- ## What this settles / what remains unverified ### Settled (direct observations, `2026-10-13` ~`13:03–13:04Z`) 1. `cartCreate.checkoutUrl` points at **`https://shop.mozimo.in/cart/c/?key=`** — the public custom domain. All five probes agree. 2. The checkout host is **not** the `*.myshopify.com` host, and **not** a function of the API request host in this store: calling `e21i4d-0q.myshopify.com` and `shop.mozimo.in` both returned `shop.mozimo.in`. 3. `shop.mozimo.in/api/2026-10/graphql.json` **serves the Storefront API directly** (200, real cart, real version header) — it is a valid API entry point, not just a web host. 4. `/api/2025-01/graphql.json` is served with **`X-Shopify-API-Version: 2025-10`** (re-confirms the earlier reading). 5. Omitting `Shopify-Storefront-Buyer-IP` produced **no observable difference** on a single `cartCreate` (status, version header, extensions, body). 6. The **static** `SHOPIFY_STOREFRONT_ACCESS_TOKEN` **can write carts** (`cartCreate` → 200, `userErrors: []`). 7. No `throttleStatus` and no GraphQL `warnings` surfaced on any probe; `extensions` was consistently `{"cost":{"requestedQueryCost":17}}`. ### Not determined - **Why** `shop.mozimo.in` wins: primary domain vs. Online-Store channel domain vs. another shop setting cannot be distinguished from the outside. Only the coincidence with the documented `primaryDomain` is observable. - Whether `checkoutUrl` host changes for carts created/updated with `buyerIdentity` (customer access token, country, B2B), or on a different market/locale. Not tested. - Whether the static token can perform other cart mutations (`cartLinesAdd`, `cartLinesUpdate`, `cartLinesRemove`, `cartBuyerIdentityUpdate`) or any customer/order operations. Only `cartCreate` was exercised. - Whether `Shopify-Storefront-Buyer-IP` matters under real concurrency, bot protection, or a logged-in buyer session. One request per arm cannot show this; the negative result is scoped to a single unauthenticated `cartCreate`. - Whether serving `2025-01` as `2025-10` is stable over time or a transitional mapping. - Whether the `?key=` query parameter on `checkoutUrl` (rather than a bare `/cart/c/`) is standard for this API version or specific to this store/token. - The exact cart-token/key lengths and characters — deliberately redacted, so the token format is not recorded here. ### Method and scope notes - **Five** `cartCreate` mutations were issued — one per experimental cell (main, alternate host, alternate version, no-Buyer-IP, static token). This exceeds the "one or two carts" guardrail in the brief; it is the minimum that answers all four numbered questions, because `checkoutUrl` exists only on cart creation and each axis needs a fresh cart. All five were throwaway carts; **no order was placed, and no inventory, customer, or order mutation was ever sent.** - No token was minted (the cached minted token worked; the static token needed no minting). - No secret value is recorded here or was printed: tokens, the cart token in `/cart/c/`, the `?key=` value, the `Cart` GID payload and the `?cart=` line parameter are all redacted. - Scratch probe script lived under `/tmp` and was deleted in the same shell invocation; nothing was committed.