Files
mozimo.in/docs/research/shopify-cart-probe-2026-10.md
T
tanshu de0553d0d4 Wayfinder: chart the Shopify headless commerce map
The route from "mozimo.in displays Shopify products" to "mozimo.in sells them",
as a plan with a phase spine and gates, plus the open decision tickets that
still shape it and the evidence they rest on.

- .scratch/shopify-headless-commerce/: plan.md (phases and gates), map.md (the
  wayfinder map), 10 open decision tickets
- CONTEXT.md: Storefront / Theme store / Cart / Parity vocabulary
- docs/adr/0001-0003: Checkout stays Shopify-hosted, one Variant per product,
  the checkout domain stays shop.mozimo.in
- docs/research/ and docs/shopify-catalog-inventory.md: verified store facts,
  the Cart API probe, and the catalogue inventory

Raw Shopify doc scrapes under .scratch/shopify-research/ are ignored: they are
working material, not record.
2026-10-14 09:00:42 +05:30

322 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<ver>/graphql.json` and `POST https://shop.mozimo.in/api/<ver>/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/<REDACTED-CART-TOKEN>?key=<REDACTED-CART-KEY>
```
- Scheme/host: `https://shop.mozimo.in`
- Path: `/cart/c/<opaque>`
- Query: `key=<opaque>%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/<REDACTED-CART-GID>?key=<REDACTED-CART-KEY>",
"checkoutUrl": "https://shop.mozimo.in/cart/c/<REDACTED-CART-TOKEN>?key=<REDACTED-CART-KEY>",
"cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } },
"lines": {
"nodes": [
{
"id": "gid://shopify/CartLine/a49a80f0-85ac-463f-9524-08418b4d911a?cart=<REDACTED-CART-KEY>",
"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/<REDACTED-CART-GID>?key=<REDACTED-CART-KEY>",
"checkoutUrl": "https://shop.mozimo.in/cart/c/<REDACTED-CART-TOKEN>?key=<REDACTED-CART-KEY>",
"cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } },
"lines": {
"nodes": [
{
"id": "gid://shopify/CartLine/14ecdea2-921b-4803-a859-d5c55118f999?cart=<REDACTED-CART-KEY>",
"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/<REDACTED-CART-GID>?key=<REDACTED-CART-KEY>",
"checkoutUrl": "https://shop.mozimo.in/cart/c/<REDACTED-CART-TOKEN>?key=<REDACTED-CART-KEY>",
"cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } },
"lines": {
"nodes": [
{
"id": "gid://shopify/CartLine/020edcf0-31c4-4b7c-a10a-f694bb6cf765?cart=<REDACTED-CART-KEY>",
"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/<REDACTED-CART-GID>?key=<REDACTED-CART-KEY>",
"checkoutUrl": "https://shop.mozimo.in/cart/c/<REDACTED-CART-TOKEN>?key=<REDACTED-CART-KEY>",
"cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } },
"lines": {
"nodes": [
{
"id": "gid://shopify/CartLine/411958db-d072-4b56-bb50-629d9585ab3a?cart=<REDACTED-CART-KEY>",
"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/<REDACTED-CART-GID>?key=<REDACTED-CART-KEY>",
"checkoutUrl": "https://shop.mozimo.in/cart/c/<REDACTED-CART-TOKEN>?key=<REDACTED-CART-KEY>",
"cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } },
"lines": {
"nodes": [
{
"id": "gid://shopify/CartLine/652b70c8-7e11-4e34-8c20-2b4bbb72512e?cart=<REDACTED-CART-KEY>",
"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/<opaque>?key=<opaque>`** — 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/<token>`) 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.