Skip to main content
One shopper’s discovery session, from the search box to a priced product card. A search matches, comes back empty, is filtered, or is paged; a recommendation rail is served directly or falls back; a price resolves differently in New York, London, and Berlin. Every shape below is captured from a live call against the canonical test store. The calls are read-only and run with the publishable client in the browser, except recommendations, which run server-side with the secret client (a publishable key returns 403). Workflow Examples has the client setup. The shopper is hunting for a Samsung Galaxy Watch 6 (0186665f-f0a1-4c79-976a-919b5b1c2daa, variant e98631a9-…, $299), which carries forward through every surface.

1. Searching the catalog

Two search surfaces answer different questions. Both return product ids and relevance scores rather than full product objects, so the cards are hydrated from the ids (step 5).

1a. Text search (keyword) — a match

searchProducts matches the literal query against product names and attributes. It is exact and fast, which suits a search box where the shopper types a brand or model. Publishable key.
Response

1b. Text search — an empty result

A mistyped query, or one for something the store doesn’t stock, still succeeds with 200: the same envelope comes back with an empty results array and totalResults: 0. There is no error to catch, so an empty state branches on results.length.
Response

1c. Semantic search (natural language)

semanticSearch matches the meaning of a conversational query against product meaning using AI embeddings, rather than literal keyword overlap, which fits a shopper who describes what they want (“wireless earbuds for the gym”) instead of naming it. Each hit carries a similarity score; minScore sets the floor (default 0.3) and limit caps the count.
Response
totalResults is the size of the candidate set considered, not the number returned — here 50 products were ranked and the top 8 above minScore came back. A higher minScore tightens relevance, a lower one widens the net. Semantic hits carry a score but no matchReason, which is a text-search field.

1d. In-catalog search with a category filter and paging

The two surfaces above search the index. A results grid usually reads the catalog directly instead: listProducts takes a search term, a categoryId filter, and cursor paging in one call, and returns full product rows rather than ids. This is the shape behind a “Headphones → Sony” filtered grid.
Each product row carries the default variant inline (variant_id, sku, price, stock) plus category_name/subcategory_name, so the grid needs no second call. Paging passes pagination.next_cursor back as cursor:
Response
Both search surfaces return ids and scores only. Cards hydrate by passing each id to storefront.products.getProduct({ id }), or by quoting prices for the whole result set at once with storefront.pricing.getProductPrices(...) (step 4). fields= trims the payload to the columns a grid renders.
Brand is a second axis alongside categories. listBrands returns the catalog’s distinct brands with a product count, most products first, which is the shape a featured-brands strip renders from; a click passes the brand to listProducts({ brand }).
brand combines with categoryId/search — e.g. listProducts({ brand: 'Samsung', categoryId }) for “Samsung in Audio”. On a marketplace operator key, listBrands aggregates brands across all merchants; pass storeId to scope the strip to one merchant’s shop page.

2. Recommendation rails — every type, and the fallback

Recommendations back the “you might also like” and “frequently bought together” rails. This is the one discovery call that is secret-key only, running on your server (a publishable key returns 403). One method, getRecommendations, serves every rail through the type field, and every response shares the same { type, recommendations[], fromCache, computedAt } envelope, each item carrying productId, score, and reason. A rail is never empty, because the engine cascades through fallbacks: a cold product with no co-purchase or similarity signal falls back bundle → also-bought → similar → trending → random. An also-bought rail on a product with no purchase history therefore comes back with reason strings reading “Similar product…” or “Trending #1…” — the requested type had nothing behind it and the engine served the next-best signal it had, under the same envelope. Each rail below is anchored on the Watch the shopper is viewing (0186665f-…).

2a. Similar products

Response

2b. Frequently bought together (also-bought)

Ranks products co-purchased with the anchor. Without co-purchase history the engine falls back to the similarity signal, so the reason strings are the similar rail’s and fromCache is false — this rail was computed fresh.
Response

2c. Bundle pairings (bundle) — complements, not substitutes

bundle surfaces complementary products that pair with the anchor — a case for the watch — rather than alternatives to it. The reason carries the co-purchase rate, here 0%, so these are ranked by complementarity alone, and the scores near 0.32 are what a pairing suggestion scores rather than a close substitute.
Response
trending and new take no productId, which makes them homepage rails. trending ranks by recent sales velocity, reported as units/day in the reason; new ranks by recency.
Response
Response

2e. Personalized (personalized) — for a signed-in shopper

Identify the shopper with a credential header (x-auth-token, x-external-auth, or x-idp-token); the recommendations are keyed to whoever that credential resolves to, not to a customer id in the body. Without a valid credential (or per-shopper signal) it falls back to trending, which is why the reason strings here are the trending ones. The same call returns personalized ranks once the customer’s history builds.
Response
Asking for a rail on a product that doesn’t exist returns 404. A publishable key on any recommendation call returns 403 — keep these on your server.

2f. Discovery shelves — “Popular now” and a product-page “hot” badge (publishable key)

Where recommendations are personalized and secret-key, Discovery is the non-personalized counterpart that renders straight from the browser: windowed rankings built from live shopper signals — views, cart-adds, view-to-purchase. It is publishable-key and available on every plan. Each call returns { metric, window_hours, products: [{ product_id, score }] }, with cards hydrated through products.getProduct. score is a raw sortable signal — a count, or a ratio for converting — already ordered highest-first.
Response
storefront here uses a publishable key, since Discovery is browser-safe. Allowed windowHours differ per metric — 1|24|168|720 for most-viewed, 24|168 for carts, 168|720 for converting — and an unlisted value returns 400. Discovery covers organic “what’s hot” shelves; Recommendations covers personalized and anchored rails.

3. Capturing browsing behaviour for the next rail

The next-item rail sharpens on what the shopper does as they browse. recordEvent logs one event per call against a variant_id (or product_id), grouped by the same session_id the anonymous cart uses. It is publishable-key and safe in the browser, and it is fire-and-forget: it returns 202 immediately without blocking the page. Three event_type values exist, one per shopper action.

3a. view — the shopper opens the Watch’s page

Response

3b. add_to_cart — they add it

3c. add_to_wishlist — or they save it for later

These events feed the session-sequence model. Once several are logged under one session_id, the next rail (secret key) answers what the shopper is likely to look at next, from what other shoppers viewed after the same sequence:
Response

4. Dynamic, location-aware prices

Search and recommendations return product ids; prices come next, in the shopper’s currency. getProductPrices resolves each product’s display price from the shopper’s location — coordinates (latitude/longitude) or a placeName — matching it to a region and converting from the store’s base currency. USD is the store default here, with GBP and EUR enabled. The same Watch priced from three locations covers every conversion shape.

4a. New York → USD (store base, rate 1)

No conversion happens: display_currency is USD, exchange_rate is 1, and price_breakdown.currency_conversion is null.
Response

4b. London → GBP (rate 0.79)

With placeName: 'London, UK' (or London coordinates) the same product comes back priced in GBP, and currency_conversion is populated with the from/to/rate/converted_amount.
Response

4c. Berlin → EUR (rate 0.92)

latitude: 52.52, longitude: 13.405 resolves to EUR — the same envelope, different rate and converted_amount.
Response

4d. No location → store default (USD)

Without coordinates or a placeName, pricing falls back to the store’s default currency, USD here — the same shape as New York, with no conversion.
For a single product, storefront.pricing.getProductPrice({ id, placeName: 'Berlin' }) returns a price_range ({ min, max }) on a multi-variant product plus a per-variant display_price. Berlin resolves the Nebula Ceramic Mug to EUR at rate 0.92, turning its $14.99$15.55 range into €13.79€14.31, each variant carrying its own converted price_breakdown.

5. Browsing by category

A navigation menu or category landing page starts from listCategories (publishable key), then filters the catalog or pricing by categoryId — the same filter the grid used in step 1d.
Response
Both listProducts and getProductPrices accept a categoryId to scope a catalog read to that category. The Watch lives in Wearables (3310356e-…), the same product this page followed through search, recommendations, behaviour, and pricing:
Categories are a fixed, store-wide set. Merchants nest subcategories under them to any depth for deeper navigation; storefront.taxonomy.listSubcategories({ tree: true }) fetches them as a tree, and { rootOnly: true } / { parentId } a single level.

What’s next

  • A shopper who has found a product continues into the storefront checkout — cart, sign-in, and payment.
  • Reviews & Messaging opens with the wishlist flow, moving a saved Watch into the cart before the post-purchase surfaces.
  • Returning customer covers order history, returns, and store credit for a shopper coming back.
  • Search, recommendations, and pricing all accept a marketplace operator key — see Marketplace.