Conformance checklist
Each item below is one obligation of the contract, with the request that proves it and the observation that ticks it. Run them against your own storefront and the engine it talks to; the base is https://api.shop.example/api/v1 in every example, so replace the host with yours. Every response quoted here was read from a local engine serving the demo store (locales en and fr, EUR). Where an item needs a signed-in customer or a filled cart, the flow page linked from it shows how to get there.
The checklist
Section titled “The checklist”-
1. The store config is read first and rendered from. Send
GET /v1/store/configand see a body with the five keysidentity,brand,localization,moneyanddomains; on the demo storelocalizationis{ "supportedLocales": ["en", "fr"], "defaultLocale": "en", "rtlLocales": [], "timezone": "Europe/Paris" }andmoneyis{ "currencyCode": "EUR", "symbol": "€", "symbolPosition": "after", "decimalSeparator": ",", "thousandsSeparator": " ", "displayPrecision": 2, "taxDisplay": "TTC", "vatRate": 0.2 }. Then change the store’s default locale or currency in the admin and reload the storefront: the language of/, thedirattribute and every price follow the answer with no code change. 01 Config, locale and money. -
2.
Accept-Languageis sent on every call. SendGET /v1/products?pageSize=1withAccept-Language: en, then withAccept-Language: fr, and seedata[0].namechange whiledata[0].slugdoes not. SendGET /v1/store/configwithAccept-Language: frand seeidentity.storeNamecome back as"Boutique Démo". In your storefront, open the browser’s network panel on a French page and check that every request to the API carriesAccept-Language: fr(andX-Locale: frif you send it for Safari). 03 Catalog and search. -
3. The session cookie and the cart cookie travel. Add an item to an anonymous cart (
POST /v1/cart/items, see 04 Cart) and read the response headers: aSet-CookieforsessionIdwithMax-Age=2592000(thirty days),Path=/,HttpOnlyandSameSite=Strict(andSecureon a production install). A bareGET /v1/cartwith no cookie answers200with an empty cart ("id": "","items": []) and sets nothing. Then in the storefront, add an item, reload the page, and see the item still there: on a server-rendered page that proves the incomingCookieheader was forwarded and theSet-Cookierelayed. Sign in (02 Auth and session) and see a cookie whose name starts withbetter-authon the storefront origin; the account page renders on a full reload. -
4.
304is success; no authenticated response is cached. SendGET /v1/store/configwithIf-None-Match: "abc"and see200, not304, withCache-Control: public, max-age=60andVary: X-Locale, Accept-Language, X-Resolve-Locale; sendGET /v1/products?pageSize=1with the same header and see200withCache-Control: no-storeand noETagon either. The API never answers304because it emits no ETag, so your storefront’s304handling is exercised only by a cache in front of it or by your framework’s fetch layer; read Conventions for why. To tick this item, stage a304yourself: point the client at a stub that answers304for one call and see the page render with the value it already held rather than an error. Then confirm that no call carrying aCookieheader runs through a shared cache (cache: 'no-store'or the equivalent; see Framework notes). -
5. The session is bootstrapped atomically. Send
GET /v1/auth/get-sessionwithout a cookie and see200with the bodynull; send it with the session cookie and see the user and session object. The item has two shapes, and a storefront ticks the one it is, or both when it renders on the server and hydrates in the browser. In a browser: oneget-sessionat boot, committed together with the user state or not at all. Sign in, then make that call fail transiently (stop the API, or hit the rate limit) and reload: the header still shows the customer, nothing was cleared, and once the API is back the next reload resolves the session again; a page that waits on the boot (the account page) shows neither a flash of the logged-out shell nor a spinner that never ends. On a server: oneget-sessionper request, with the incomingCookieforwarded, whose failure renders the anonymous page and clears no cookie. Sign in, then makeget-sessionanswer a500(or stop the API) and request a page withcurland the session cookie: the HTML is the anonymous page, the status is200, and the response carries noSet-Cookiethat expires the session; with the API back, the same request renders the customer. 02 Auth and session. -
6.
X-Idempotency-Keyon order creation, repeated on a retry. SendPOST /v1/orderswith a JSON body and no key and see400with{ "error": { "code": "IDEMPOTENCY_KEY_REQUIRED", "message": "X-Idempotency-Key header is required for this endpoint." } }; send it withX-Idempotency-Key: not-a-uuidand see400withIDEMPOTENCY_KEY_MALFORMED. With a filled cart and an address, send the same order twice with the same UUID v4 key and see the samedata.idand the samedata.orderNumberboth times, and one order in the account’s list; send the same key with a changed body and see409 IDEMPOTENCY_KEY_CONFLICT. In the storefront, double-click the place-order button and see one order. 05 Checkout and orders. -
7. Every error code is surfaced in every locale. Send
GET /v1/products/does-not-existwithAccept-Language: frand see404with{ "error": { "code": "PRODUCT_NOT_FOUND", "message": "Product not found" } }: the message is English underfr, so the storefront must translate the code itself. Open the same slug on your French storefront and see a French not-found page; trigger a checkout error (an empty cart, an out-of-stock item) under each locale of the store and see a sentence in that locale, never the API’smessageand never an English sentence on a French page. The codes each route can answer are under “Error codes” on every flow page and in Error codes. -
8. The SEO and GEO files are proxied from the API. Send
GET /v1/sitemap_index.xmland see200,Content-Type: application/xml; charset=utf-8,Cache-Control: public, max-age=3600;GET /v1/robots.txtanswerstext/plain; charset=utf-8with aSitemap:line that names the storefront host;GET /v1/llms.txtwithAccept-Language: franswersVary: Accept-Languageand opens with# Boutique Démo. Then request/sitemap_index.xml,/sitemap.xml(a301to the index),/robots.txt,/llms.txtand/llms-full.txton your storefront’s root and see the same bodies with the same content types, and the Frenchllms.txtwhen you sendAccept-Language: fr. 08 SEO and GEO. -
9. JSON-LD, canonical and
hreflangare emitted. Request a product page from your storefront withcurl(no JavaScript) and see in the HTML one<link rel="canonical">, one<link rel="alternate" hreflang="...">per supported locale plus one withhreflang="x-default"pointing at the default locale’s URL, and a<script type="application/ld+json">whose@typeisProductwith the name, description and image fromGET /v1/products/{slug}; the home page carriesOrganizationandWebSitewith the values fromidentityanddomainsin the store config. Paste each block into a structured-data validator and see no error. 08 SEO and GEO names which field feeds which tag. -
10. The revalidation route is exposed. Send
POSTto your route (the shipped one is/api/_internal/revalidate) with noX-Revalidate-Secretand see401; with the right secret and the body{ "paths": ["/en/catalog/product/some-slug"], "reason": "product.write", "eventId": "<uuid>" }and see200with{ "data": { "invalidated": 1, "eventId": "<uuid>", "deduped": false } }; send the identical body again and see"invalidated": 0, "deduped": true. Then setSTOREFRONT_REVALIDATE_URLandSTOREFRONT_REVALIDATE_SECRETon the engine, rename a product in the admin, and see the storefront page carry the new name on the next request without a redeploy. 10 Revalidation.
Every request, ready to run
Section titled “Every request, ready to run”Three generated files hold every storefront request so you do not retype them. They are written by npm run docs:generate into docs/site/reference/_generated/ and regenerated whenever the API changes.
The REST Client file, storefront-api.http in that directory, lists every storefront operation in the order of the reference sidebar, one request per block, with a sample body built from the schema and variables at the top for the base URL, the locale and every path parameter the requests use (id, slug, itemId and the rest, each preset to its own name until you fill it). Open it in VS Code with the REST Client extension, or in an IntelliJ IDE, set baseUrl to your engine (http://localhost:53000/api on a local install) and locale to one of the store’s locales, run the sign-in request first so the tool’s cookie jar holds a session (the file declares no cookie variable; the client keeps cookies between requests), then click “Send Request” above any block; the response opens beside it.
The Postman collection, postman-collection.json in the same directory, holds the same requests for both audiences (storefront and admin), one folder per module, with the sample bodies and no token variables: the collection relies on Postman’s cookie jar, which keeps the Better Auth cookie and the cart cookie between calls the way a browser does. Import it, import postman-environment.json beside it as the environment, set baseUrl and locale there, run the storefront sign-in request once so the jar holds the session, and every request in the storefront folders runs as that customer; a request under an admin folder needs a staff sign-in instead.