diff --git a/README.md b/README.md
index f6fae93e..92d01d48 100644
--- a/README.md
+++ b/README.md
@@ -136,6 +136,29 @@ Preview URL: https://skill-deploy-abc123.vercel.app
Claim URL: https://vercel.com/claim-deployment?code=...
```
+### nosto
+
+Frontend integration guide for [Nosto](https://www.nosto.com/), the e-commerce personalization platform. Covers the `nostojs` global, the `@nosto/nosto-js` npm package, and `@nosto/nosto-react`.
+
+**Use when:**
+- Implementing or debugging Nosto on a storefront
+- Migrating from Page Tagging to the Session API (SPA / headless)
+- Sending Nosto events (`viewProduct`, `viewCategory`, `viewCart`, `viewSearch`, `addOrder`)
+- Rendering recommendation placements and wiring add-to-cart attribution
+- Integrating Nosto into a React / Next.js app with `@nosto/nosto-react`
+- Gating Nosto behind GDPR / cookie consent
+
+**Topics covered:**
+- Initialization (`init`, the legacy stub, custom script loaders, Shopify Markets)
+- Session API chain (`defaultSession().viewX(...).setPlacements([...]).load()`)
+- Order tracking and conversion attribution
+- Recommendation placements, dynamic filtering, `prerender` / `postrender` callbacks
+- Customer identification (with PII / `customer_reference` rules)
+- Nosto Search GraphQL
+- Common gotchas: mixing Page Tagging with Session API, double-tracking, empty placements, SSR limitations
+
+**Sources:** [docs.nosto.com/techdocs](https://docs.nosto.com/techdocs), [nosto-js typedoc](https://nosto.github.io/nosto-js/modules.html).
+
## Installation
```bash
diff --git a/skills/nosto/SKILL.md b/skills/nosto/SKILL.md
new file mode 100644
index 00000000..f82673dc
--- /dev/null
+++ b/skills/nosto/SKILL.md
@@ -0,0 +1,143 @@
+---
+name: nosto
+description: Nosto frontend JavaScript SDK and `@nosto/nosto-js` package — initialization, session events (viewProduct, viewCategory, viewCart, viewSearch, addOrder), recommendation placements, cart and customer tracking, search, and SPA/React integration. Use when implementing or debugging Nosto on a storefront, integrating product recommendations, sending Nosto events, wiring Nosto into a React/Next.js/headless app, or working with the `nostojs` global, the `@nosto/nosto-js` npm package, or `@nosto/nosto-react`. Triggers on mentions of Nosto, `nostojs`, `nosto-js`, Nosto recommendations, Nosto placements, or Nosto session API.
+license: MIT
+metadata:
+ author: community
+ version: "1.0.0"
+---
+
+# Nosto Frontend JS APIs
+
+Reference for integrating Nosto's frontend JavaScript SDK on a storefront. Covers the legacy `nostojs` global, the modern `@nosto/nosto-js` npm package, the `@nosto/nosto-react` provider, and the underlying session/recommendation APIs.
+
+## When to Apply
+
+- Adding Nosto to a Shopify, BigCommerce, Magento, or custom storefront
+- Migrating from Page Tagging to the Session API (SPA/headless)
+- Sending Nosto events (`viewProduct`, `viewCategory`, `viewCart`, `viewSearch`, `addOrder`)
+- Rendering recommendation placements and attributing add-to-cart events
+- Wiring Nosto into React/Next.js with `@nosto/nosto-react`
+- Implementing GDPR/cookie-consent gating around the Nosto loader
+- Debugging empty placements, double-tracking, or broken attribution
+
+## Two Integration Modes — Pick One Per Page
+
+| Mode | Use it when | Key calls |
+|---|---|---|
+| **Page Tagging** | Server-rendered storefront with `
` tags in the DOM | The script auto-reads tagging; `api.customer(...)`, `api.resendCartTagging()` |
+| **Session API** | SPA, headless, or any app where you build state imperatively per route | `api.defaultSession().viewX(...).setPlacements([...]).load()` |
+
+**Never mix both on the same page** — produces double-tracked sessions or empty placements. See `references/patterns-gotchas.md`.
+
+## Quick Reference
+
+### 1. Load the script
+
+Modern (recommended):
+
+```ts
+import { init, nostojs } from "@nosto/nosto-js";
+await init({ merchantId: "shopify-12345" });
+```
+
+Legacy embed (when modifying a theme directly):
+
+```html
+
+
+```
+
+Full loader patterns, custom script loaders, Shopify International, and consent-gated loading: `references/initialization.md`.
+
+### 2. Session API — every route change
+
+```js
+nostojs(api =>
+ api.defaultSession()
+ .viewProduct("product-id")
+ .setPlacements(["productpage-nosto-1", "productpage-nosto-2"])
+ .load()
+ .then(res => console.log(res.recommendations))
+);
+```
+
+Replace `viewProduct` with the page-type method:
+
+| Page | Method |
+|---|---|
+| Front page | `viewFrontPage()` |
+| Category | `viewCategory("/path")` |
+| Product | `viewProduct(productId)` |
+| Cart | `viewCart()` |
+| Search | `viewSearch(query)` |
+| 404 / content / account | `viewOther()` |
+
+Full session chain, `setCustomer`, `addOrder`, dynamic filtering, and recommendation callbacks: `references/session-events.md` and `references/recommendations.md`.
+
+### 3. Order tracking — once on the confirmation page
+
+```js
+nostojs(api =>
+ api.defaultSession()
+ .addOrder({
+ external_order_ref: "145000006",
+ info: { order_number: "195", email: "buyer@example.com",
+ first_name: "X", last_name: "Y", type: "order", newsletter: true },
+ items: [{ product_id: "406", sku_id: "243", name: "Item",
+ quantity: 1, unit_price: 49.5, price_currency_code: "USD" }]
+ })
+ .setPlacements(["order-related"])
+ .load()
+);
+```
+
+Idempotency relies on `external_order_ref`. Full schema in `references/session-events.md`.
+
+### 4. Add-to-cart attribution
+
+```js
+nostojs(api => api.recommendedProductAddedToCart(productId, "nosto-categorypage-1"));
+```
+
+Records *attribution only*. Cart contents must also be updated (Page Tagging: `api.resendCartTagging()`; Session API: re-call `viewCart()` or include the new cart in the next session).
+
+### 5. React / Next.js
+
+```tsx
+import { NostoProvider, NostoSession } from "@nosto/nosto-react";
+
+
+
+
+
+```
+
+Routing patterns and SPA pitfalls: `references/patterns-gotchas.md`.
+
+## References (load on demand)
+
+- `references/initialization.md` — loaders, `init()` options, consent gating, debug mode
+- `references/session-events.md` — full `defaultSession` chain, every `view*`, `addOrder`, `setCustomer`, cart updates
+- `references/recommendations.md` — `setPlacements`, `loadRecommendations`, `createRecommendationRequest`, dynamic filtering, prerender/postrender callbacks
+- `references/customer-search-experiments.md` — customer identification, GraphQL search, A/B testing surfaces
+- `references/patterns-gotchas.md` — SPA integration, multi-store, GDPR, common bugs (double-tracking, missing attribution, PII in `customer_reference`)
+
+## TypeScript Module Map
+
+The `@nosto/nosto-js` typedoc lives at
and exposes two top-level modules:
+
+- `core` — `init`, `nostojs`, `isNostoLoaded`, `getSettings`, `getNostoWindow`, `addSkuToCart`, types `InitProps`, `Settings`, `BackendEnvironment`, `ScriptLoadOptions`
+- `client` — the runtime API surface: `API`, `Session`, `Cart`, `Order`, `Customer`, `Product`, `JSONProduct`, `SearchQuery`, `SearchResult`, `ABTest`, `Experiment`, `RenderMode`, `InsertMode`, etc.
+
+When writing TypeScript, import types from `@nosto/nosto-js/client` for runtime shapes and from `@nosto/nosto-js` for `init`/`nostojs`.
+
+## Authoritative Sources
+
+- Tech docs:
+- JS APIs:
+- Typedoc:
+- Source:
diff --git a/skills/nosto/metadata.json b/skills/nosto/metadata.json
new file mode 100644
index 00000000..1bea949a
--- /dev/null
+++ b/skills/nosto/metadata.json
@@ -0,0 +1,16 @@
+{
+ "version": "1.0.0",
+ "organization": "Community",
+ "date": "April 2026",
+ "abstract": "Reference for Nosto's frontend JavaScript SDK and the @nosto/nosto-js npm package. Covers script loading, the session API (viewProduct, viewCategory, viewCart, viewSearch, addOrder), recommendation placements and attribution, customer identification, GraphQL search, GDPR/consent gating, React/Next.js integration via @nosto/nosto-react, and common gotchas (double-tracking, mixing Page Tagging with Session API, PII in customer_reference). Designed for AI agents implementing or debugging Nosto on storefronts.",
+ "references": [
+ "https://docs.nosto.com/techdocs",
+ "https://docs.nosto.com/techdocs/apis/frontend/js-apis",
+ "https://docs.nosto.com/techdocs/apis/frontend/implementation-guide-session-api",
+ "https://nosto.github.io/nosto-js/modules.html",
+ "https://nosto.github.io/nosto-js/modules/client.html",
+ "https://nosto.github.io/nosto-js/modules/core.html",
+ "https://github.com/Nosto/nosto-js",
+ "https://github.com/Nosto/nosto-react"
+ ]
+}
diff --git a/skills/nosto/references/customer-search-experiments.md b/skills/nosto/references/customer-search-experiments.md
new file mode 100644
index 00000000..eb6109ff
--- /dev/null
+++ b/skills/nosto/references/customer-search-experiments.md
@@ -0,0 +1,129 @@
+# Nosto — Customer, Search & Experiments
+
+## Customer identification
+
+Two equivalent forms — pick by integration mode.
+
+**Session API (chainable):**
+
+```js
+nostojs(api =>
+ api.defaultSession()
+ .setCustomer({
+ customer_reference: "b369f1235cf4f08153c560.82515936",
+ email: "buyer@example.com",
+ first_name: "Nosto",
+ last_name: "Test",
+ newsletter: true,
+ marketing_permission: true
+ })
+ .viewCart()
+ .update()
+);
+```
+
+**Page Tagging (one-shot):**
+
+```js
+nostojs(api =>
+ api.customer({
+ email: "jane.doe@example.com",
+ first_name: "Jane",
+ last_name: "Doe",
+ marketing_permission: true,
+ customer_reference: "5e3d4a9c-cf58-11ea-87d0-0242ac130003"
+ })
+);
+```
+
+### Field rules
+
+| Field | Required | Notes |
+|---|---|---|
+| `customer_reference` | Yes | Stable, pseudonymous ID. UUID or hashed user ID. **Never raw email or PII.** |
+| `email` | No | Goes in this field, not in `customer_reference` |
+| `marketing_permission` | For triggered email | Explicit GDPR opt-in. Without it, contact is treated as opted-out of triggered messages. |
+| `newsletter` | For triggered email | Equivalent on the order-level `info` object |
+
+## Search — Nosto Search GraphQL
+
+Search is exposed via GraphQL at `https://search.nosto.com/v1/graphql`. The client SDK ships TypeScript types but not a built-in fetcher — wire your own client (urql, Apollo, fetch):
+
+```graphql
+query Storefront($q: String!) {
+ search(
+ accountId: "shopify-12345"
+ query: $q
+ products: { size: 24 }
+ keywords: { size: 5 }
+ categories: { size: 5 }
+ popularSearches: { size: 5, emptyQueryMatchesAll: true }
+ ) {
+ products {
+ hits { productId name listPrice price imageUrl url }
+ total
+ fuzzy
+ }
+ keywords {
+ hits { keyword _redirect _highlight { keyword } }
+ }
+ categories {
+ hits { name url urlPath }
+ total
+ }
+ popularSearches {
+ hits { query total }
+ total
+ }
+ }
+}
+```
+
+```ts
+const res = await fetch("https://search.nosto.com/v1/graphql", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ query: STOREFRONT_QUERY,
+ variables: { q: input }
+ })
+}).then(r => r.json());
+
+const fuzzy = res.data.search.products.fuzzy; // boolean — populate analytics autoCorrect
+```
+
+### Relevant types from `@nosto/nosto-js/client`
+
+`SearchQuery`, `SearchResult`, `SearchProduct`, `InputSearchFilter`, `InputSearchFacetConfig`, `SearchAutocorrect`, `SearchExplain`.
+
+### Autocomplete
+
+Use Nosto's autocomplete component (configured in admin) rather than calling GraphQL directly for the dropdown — it handles rendering, debouncing, keyboard nav, and analytics. Drop-in via the embed script; no extra code needed beyond placing the `data-nosto-search` attribute on your input.
+
+## Experiments / A-B testing
+
+Most experiments are configured in the Nosto admin and surfaced through standard placement responses — no bespoke client code is needed.
+
+When you DO need to read or override variations from the client, the relevant types in `@nosto/nosto-js/client` are:
+
+- `ABTest` — admin-defined test definition
+- `Experiment` — currently-running experiment instance
+- `Campaign` — single campaign (placement) inside an experiment
+- `ForcedTestDTO` — admin-forced variation for QA
+- `TestPreviewsDTO` — preview data when entering preview mode
+
+### Reading the active variation in callbacks
+
+```js
+nostojs(api => {
+ api.listen("prerender", event => {
+ event.segments.forEach(segmentId => console.log("In segment:", segmentId));
+ });
+});
+```
+
+A/B variations show up as segment IDs on `prerender`. Conditional UI based on segment membership is the standard pattern.
+
+### Forcing a variation (QA / preview)
+
+Append `?nostodebug=true` to the URL and use the debug toolbar's "Test previews" panel to force a variation. There is no production-safe way to force variations from JS — that's by design (forcing would skew test results).
diff --git a/skills/nosto/references/initialization.md b/skills/nosto/references/initialization.md
new file mode 100644
index 00000000..d1cdceb7
--- /dev/null
+++ b/skills/nosto/references/initialization.md
@@ -0,0 +1,117 @@
+# Nosto — Initialization & Loading
+
+## Modern: `@nosto/nosto-js`
+
+```bash
+npm install @nosto/nosto-js
+```
+
+```ts
+import { init, nostojs, isNostoLoaded, getSettings } from "@nosto/nosto-js";
+
+await init({
+ merchantId: "shopify-12345",
+ // Optional fields below
+ env: "production", // BackendEnvironment
+ options: { /* ScriptLoadOptions */ }, // forwarded to script loader
+ scriptLoader: (src, opts) => myCustomLoader(src),
+ shopifyInternational: { language: "en", marketId: 1 },
+});
+
+// Run code once the client API is ready (callback is queued if init is still loading):
+nostojs(async api => {
+ console.log(isNostoLoaded(), getSettings());
+});
+```
+
+`init()` resolves once the embed script has loaded. `nostojs(cb)` queues `cb` if the script isn't ready yet, then runs it with the `client.API` object.
+
+### `InitProps` (key fields)
+
+| Field | Type | Purpose |
+|---|---|---|
+| `merchantId` | `string` | Account ID from Nosto admin, e.g. `shopify-12345` |
+| `env` | `"production" \| "staging"` | Backend environment |
+| `options` | `ScriptLoadOptions` | Forwarded to the script loader (CSP nonce, attributes) |
+| `scriptLoader` | `(src, opts) => Promise` | Override default loader (use for nonces, custom CDNs) |
+| `shopifyInternational` | `{ language, marketId }` | Shopify Markets multi-market resolution |
+
+## Legacy: embed `
+
+
+
+
+
+
+```
+
+The stub MUST come before the async script so that any pre-script `nostojs(...)` calls survive. Don't call `window.nosto.*` directly — those are internals and can change between releases.
+
+## Cookie consent / GDPR
+
+Three valid patterns, in order of preference:
+
+### 1. Don't load Nosto until consent
+
+The cleanest option — the embed script never runs, no `2c.cId` cookie is set.
+
+```ts
+if (cookieConsent.marketing) {
+ await init({ merchantId: "shopify-12345" });
+}
+```
+
+### 2. Load with tracking disabled, enable on consent
+
+Use when the script must load early (e.g. for non-tracking features) but tracking gates on consent:
+
+```js
+nostojs(api => api.visit.setDoNotTrack(true));
+
+// Later, after the visitor accepts marketing cookies:
+cookieConsent.on("accept", () => {
+ nostojs(api => api.visit.setDoNotTrack(false));
+});
+```
+
+`setDoNotTrack(true)` BEFORE any tracking call prevents the visitor cookie from being set. Toggling later does not retroactively delete an already-set cookie.
+
+### 3. Per-event opt-out via `marketing_permission` / `newsletter`
+
+For triggered email/push, the visitor must have consented. Pass `marketing_permission: true` on `customer()` or `setCustomer()`, and `newsletter: true` inside `addOrder().info`. Without these flags, Nosto treats the contact as opted-out for triggered messages even if the cookie is set.
+
+## Auto-load control
+
+By default, the client auto-fires the recommendation request after DOMContentLoaded. To take manual control:
+
+```js
+nostojs(api => api.setAutoLoad(false));
+
+// Later, when the DOM and session are ready:
+nostojs(api => api.load());
+```
+
+Use this for SPAs, deferred hydration, or when the cart/customer data is fetched async.
+
+## Debug toolbar
+
+Append `?nostodebug=true` to any storefront URL (use an incognito window). The toolbar shows fired events, resolved placements, segments, A/B variations, and tagging detection. Use this to verify integrations before going live.
+
+## Multi-store
+
+One Nosto implementation per store. Account IDs are platform-prefixed (`shopify-12345`, `bigcommerce-67890`). For Shopify Markets, pass `shopifyInternational: { language, marketId }` so Nosto resolves the correct market catalog.
diff --git a/skills/nosto/references/patterns-gotchas.md b/skills/nosto/references/patterns-gotchas.md
new file mode 100644
index 00000000..b7e5cbe0
--- /dev/null
+++ b/skills/nosto/references/patterns-gotchas.md
@@ -0,0 +1,121 @@
+# Nosto — Integration Patterns & Gotchas
+
+## React / Next.js — `@nosto/nosto-react`
+
+```bash
+npm install @nosto/nosto-react @nosto/nosto-js
+```
+
+```tsx
+import { NostoProvider, NostoSession, NostoPlacement } from "@nosto/nosto-react";
+
+export default function Layout({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
+```
+
+Per-page session calls:
+
+```tsx
+import { NostoHome, NostoProduct, NostoCategory } from "@nosto/nosto-react";
+
+// Home
+
+
+// Product
+
+
+// Category
+
+```
+
+The provider re-fires the session chain whenever the inner component remounts (e.g. on Next.js route change in App Router). For App Router specifically, mount these in **client components** — `NostoProvider` uses browser-only globals.
+
+### Custom rendering (JSON mode)
+
+```tsx
+import { useNosto } from "@nosto/nosto-react";
+
+function CustomRecs() {
+ const { recommendations } = useNosto();
+ const placement = recommendations?.["productpage-nosto-1"];
+ if (!placement) return null;
+ return ;
+}
+```
+
+## SPA route-change checklist
+
+On every route change you must:
+
+1. Build the new session: `defaultSession().viewX(...)`
+2. Set placements present on the new page: `.setPlacements([...])`
+3. Trigger fetch: `.load()`
+
+Without `load()` the page will display stale recommendations. The router's success hook (Next.js `useEffect` on `pathname`, React Router `useLocation`, etc.) is the right place.
+
+## Common gotchas
+
+### 1. Mixing Page Tagging and Session API on the same page
+
+Pick ONE per page. Page Tagging expects `` tags in the DOM and reads them automatically. Session API expects nothing in the DOM and you build state imperatively. Mixing produces double-tracked sessions or empty placements.
+
+### 2. Calling `window.nosto.*` directly
+
+Don't. Always go through `nostojs(api => ...)`. The `window.nosto` namespace is internal and breaks across releases. The stub queue exists specifically to bridge pre-load and post-load calls.
+
+### 3. PII in `customer_reference`
+
+`customer_reference` MUST be a stable pseudonymous ID — UUID, hashed user ID, anything non-reversible. Putting raw email there is a privacy bug and will produce duplicate visitor records (Nosto already has `email` as a separate field).
+
+### 4. Forgetting attribution
+
+`recommendedProductAddedToCart()` records *attribution only*. You also need to:
+
+- (Page Tagging) update cart tagging via `setTaggingProvider("cart", ...)` + `resendCartTagging()`, OR
+- (Session API) re-call `viewCart()` with the new cart on `setCart()`.
+
+Skipping the cart-content update means the cart appears empty in Nosto's view even though attribution recorded.
+
+### 5. Calling `api.load()` before the DOM is ready
+
+When you've turned off auto-load with `setAutoLoad(false)`, only call `api.load()` after the page is rendered AND your session has the correct page-type method on it. Calling earlier produces empty placements (Nosto returns recs but there's no `
` to inject into yet).
+
+### 6. Double-firing `addOrder()`
+
+`addOrder()` must run once per confirmation page. Idempotency relies on `external_order_ref` — but only at the backend level. Re-running on the same page (e.g. via React StrictMode double-mount) will fire two requests; the backend dedupes but you've doubled your network spend and skewed timing data. Gate with a ref/flag in React.
+
+### 7. Wrong `merchantId` per market
+
+Per-store account IDs only work for that store's catalog. Sending `shopify-us-12345` to a UK storefront silently produces empty recs because no products match. For Shopify Markets, pass `shopifyInternational: { language, marketId }` so Nosto resolves the right market catalog automatically.
+
+### 8. GDPR cookie set before consent
+
+Once `2c.cId` is set, opting out doesn't retroactively delete it. Either:
+
+- don't load the script until consent, or
+- call `nostojs(api => api.visit.setDoNotTrack(true))` BEFORE any tracking call (i.e. in the same script tag, before any other `nostojs(...)` callbacks).
+
+### 9. SSR + personalization
+
+`defaultSession()` is client-only. Don't try to call it during SSR. For SSR personalization, use Nosto's GraphQL `updateSession()` mutation server-side then re-sync the client. GraphQL session updates do NOT support OCP (on-site content personalization), dynamic filtering, or built-in A/B testing — those require the JS client.
+
+### 10. Empty placement bodies in production
+
+Three things to check, in order:
+
+1. Did `setPlacements([...])` actually include the placement ID? (typo? mismatch with admin?)
+2. Did `.load()` run? (missed route hook? `setAutoLoad(false)` without a follow-up `load()`?)
+3. Does the placement have products in admin given the current segment/filters? (Use `?nostodebug=true` to inspect.)
+
+## Debugging recipe
+
+1. Open the page in incognito with `?nostodebug=true` appended
+2. Check the toolbar's "Events" tab — should show one of `viewProduct`/`viewCategory`/etc. firing
+3. Check "Placements" tab — green = filled, red = empty
+4. If empty: open the placement in admin, check its targeting rules and product feed
+5. If event missing: your Session API chain isn't running on this route — verify the router hook
diff --git a/skills/nosto/references/recommendations.md b/skills/nosto/references/recommendations.md
new file mode 100644
index 00000000..fe108791
--- /dev/null
+++ b/skills/nosto/references/recommendations.md
@@ -0,0 +1,153 @@
+# Nosto — Recommendations & Placements
+
+Placements are named slots in the page (e.g. `frontpage-nosto-1`, `productpage-nosto-3`) configured in the Nosto admin. The client requests one or more placements per page and Nosto returns recommendation HTML/JSON for each.
+
+## Declaring placements
+
+The Session API expects placement IDs:
+
+```js
+nostojs(api =>
+ api.defaultSession()
+ .viewFrontPage()
+ .setPlacements(["frontpage-nosto-1", "frontpage-nosto-2"])
+ .load()
+);
+```
+
+Or read every placement currently in the DOM (Page Tagging mode or hybrid):
+
+```js
+nostojs(api => api.placements.getPlacements()); // string[]
+```
+
+## `setAutoLoad(false)` + manual `load()`
+
+By default, the client auto-loads after DOMContentLoaded. To delay (e.g. waiting for hydration or consent):
+
+```html
+
+```
+
+```js
+// When ready:
+nostojs(api => api.load());
+```
+
+## Re-rendering after a route change (`loadRecommendations`)
+
+In a quasi-SPA where a partial page swap happens but you don't want to rebuild the whole session:
+
+```js
+nostojs(api => api.loadRecommendations());
+```
+
+Pass attribution metadata when re-rendering due to a click on an existing rec (e.g. opening a quick-view modal):
+
+```js
+nostojs(api =>
+ api.loadRecommendations({ markNostoElementClicked: "nosto-categorypage-1" })
+);
+```
+
+## Imperative requests outside the session chain — `createRecommendationRequest`
+
+Use when you need fine-grained control: extra tags, custom fields, restricted to specific elements.
+
+```js
+nostojs(api => {
+ api.createRecommendationRequest({ includeTagging: true })
+ .setCurrentTags(["color:red", "season:fall"])
+ .addCurrentCustomFields({ gender: "male", material: "cotton" })
+ .setElements(["productpage-nosto-3"])
+ .load()
+ .then(response => console.log(response));
+});
+```
+
+Quick-view of a specific product/SKU variant:
+
+```js
+nostojs(api =>
+ api.createRecommendationRequest({ includeTagging: true })
+ .setProducts([{ product_id: "6961338417345", selected_sku_id: "40822930473153" }])
+ .load()
+);
+```
+
+## Dynamic filtering
+
+Pass per-request filter values to a placement:
+
+```js
+nostojs(api =>
+ api.createRecommendationRequest({ includeTagging: true })
+ .setElements(["category-recs"])
+ .addCurrentCustomFields({
+ in_stock: "true",
+ price_max: "100"
+ })
+ .load()
+);
+```
+
+The placement must be configured in the Nosto admin to expose those filter fields.
+
+## Lifecycle callbacks — `prerender` / `postrender`
+
+```js
+nostojs(api => {
+ api.listen("prerender", event => {
+ // Fired BEFORE recommendations are inserted into the DOM
+ console.log(event.affinityScores); // { brands: [{name, score}], categories: [...] }
+ console.log(event.segments); // string[] — segment IDs
+ });
+
+ api.listen("postrender", event => {
+ // Fired AFTER recommendations are inserted
+ console.log(event.filledElements); // string[] — placement IDs that got recs
+ console.log(event.unFilledElements); // string[] — placement IDs that came back empty
+ });
+});
+```
+
+Use `prerender` to add segment-based DOM tweaks before the recs are visible. Use `postrender` to swap layouts or hide empty wrappers based on `unFilledElements`.
+
+## `RenderMode` and `InsertMode`
+
+The `client` module exposes `RenderMode` (`"HTML" | "JSON_ORIGINAL" | "JSON_REDUCED"`) and `InsertMode` (`"REPLACE" | "APPEND" | "PREPEND" | ...`). These are configured per placement in the Nosto admin; in client code you typically only inspect them when handling JSON responses:
+
+```js
+nostojs(api =>
+ api.defaultSession()
+ .viewProduct("p1")
+ .setPlacements(["json-placement"])
+ .setResponseMode("JSON_ORIGINAL")
+ .load()
+ .then(({ recommendations }) => {
+ // recommendations[placementId] = { products: [...], title, etc. }
+ const placement = recommendations["json-placement"];
+ renderInReact(placement.products);
+ })
+);
+```
+
+`JSON_ORIGINAL` returns the full product objects (use this in React/headless). `HTML` returns server-rendered markup that the client injects into `
`.
+
+## Attribution
+
+Every recommendation click/add-to-cart should be attributed back to its placement so reports work:
+
+```js
+// On add-to-cart from a Nosto rec:
+nostojs(api =>
+ api.recommendedProductAddedToCart(productId, "nosto-categorypage-1")
+);
+
+// On click attribution for navigation (rec → PDP):
+nostojs(api =>
+ api.loadRecommendations({ markNostoElementClicked: "nosto-categorypage-1" })
+);
+```
+
+Without these calls, Nosto's "revenue from recommendations" report will be empty.
diff --git a/skills/nosto/references/session-events.md b/skills/nosto/references/session-events.md
new file mode 100644
index 00000000..d7841963
--- /dev/null
+++ b/skills/nosto/references/session-events.md
@@ -0,0 +1,186 @@
+# Nosto — Session API & Events
+
+The Session API is the imperative way to tell Nosto about a page view. Use it on SPAs, headless storefronts, or any place where Page Tagging isn't appropriate.
+
+**Pattern:** `api.defaultSession().().setPlacements([...]).load()`
+
+`defaultSession()` returns a chainable `Session` builder. Every method on it returns the same builder (except terminal `load()`, which returns a `Promise`). Run the whole chain on every route change.
+
+## Page-type methods
+
+| Method | Args | Purpose |
+|---|---|---|
+| `viewFrontPage()` | — | Home / landing page |
+| `viewCategory(path)` | `string` (e.g. `"/Women/Dresses"`) | Category listing |
+| `viewProduct(productId)` | `string` | Product detail |
+| `viewCart()` | — | Cart page |
+| `viewSearch(query)` | `string` | Search results |
+| `viewOther()` | — | 404, content, account, anything else |
+
+```js
+nostojs(api =>
+ api.defaultSession()
+ .viewCategory("/Womens/Dresses")
+ .setPlacements(["category-related"])
+ .load()
+);
+```
+
+Use `api.placements.getPlacements()` to read every placement ID currently declared in the page (handy on the front page where placements vary):
+
+```js
+nostojs(api =>
+ api.defaultSession()
+ .viewFrontPage()
+ .setPlacements(api.placements.getPlacements())
+ .load()
+);
+```
+
+## Cart & customer context on the session
+
+```js
+nostojs(api =>
+ api.defaultSession()
+ .setCart({
+ items: [{
+ product_id: "406",
+ sku_id: "243",
+ name: "Linen Blazer",
+ quantity: 1,
+ unit_price: 49.5,
+ price_currency_code: "USD"
+ }]
+ })
+ .setCustomer({
+ customer_reference: "5e3d4a9c-cf58-11ea-87d0-0242ac130003",
+ email: "buyer@example.com",
+ first_name: "Jane",
+ last_name: "Doe",
+ newsletter: true,
+ marketing_permission: true
+ })
+ .viewCart()
+ .setPlacements(["cart-related"])
+ .load()
+);
+```
+
+`customer_reference` MUST be a stable pseudonymous ID — never the raw email. Email goes in `email`. Hashed IDs or UUIDs are fine.
+
+## Order tracking — `addOrder()`
+
+Call once on the order-confirmation page. Records the conversion and produces order-page recommendations.
+
+```js
+nostojs(api =>
+ api.defaultSession()
+ .addOrder({
+ external_order_ref: "145000006",
+ info: {
+ order_number: "195",
+ email: "buyer@example.com",
+ first_name: "Jane",
+ last_name: "Doe",
+ type: "order",
+ newsletter: true
+ },
+ items: [{
+ product_id: "406",
+ sku_id: "243",
+ name: "Linen Blazer (White, S)",
+ quantity: 1,
+ unit_price: 455,
+ price_currency_code: "EUR"
+ }]
+ })
+ .setPlacements(["order-related"])
+ .load()
+);
+```
+
+Idempotency relies on `external_order_ref` — re-firing with the same ref is a no-op on the backend. Don't omit it.
+
+## Cart updates mid-session (Page Tagging)
+
+When the cart changes without a full page reload (mini-cart, ajax add):
+
+```js
+nostojs(api => {
+ fetch("/cart.json")
+ .then(r => r.json())
+ .then(cart => {
+ const taggingHtml = renderCartTagging(cart); // your function
+ api.setTaggingProvider("cart", taggingHtml);
+ api.resendCartTagging();
+ });
+});
+```
+
+For Session API mode, just call the next session chain with the new cart on `setCart()` — there's no separate "resend".
+
+## Cart updates mid-session (Session API)
+
+```js
+nostojs(api =>
+ api.defaultSession()
+ .setCart({ items: newItems })
+ .viewCart() // or whatever page the user is on
+ .update() // re-evaluates without re-fetching recommendations
+);
+```
+
+Use `.update()` (not `.load()`) when you only need to update session state without re-rendering placements.
+
+## Add-to-cart attribution
+
+```js
+nostojs(api =>
+ api.recommendedProductAddedToCart("productId1", "nosto-categorypage-1")
+);
+```
+
+Alias: `reportAddToCart`. Records *attribution only*. You still need to update cart contents (above) for the cart to reflect the actual state.
+
+## Common `Cart`/`Order`/`Customer` field reference
+
+### `CartItem`
+
+```ts
+{
+ product_id: string;
+ sku_id: string;
+ name: string;
+ quantity: number;
+ unit_price: number;
+ price_currency_code: string; // ISO 4217, e.g. "USD"
+}
+```
+
+### `OrderInfo`
+
+```ts
+{
+ order_number: string;
+ email: string;
+ first_name: string;
+ last_name: string;
+ type: "order";
+ newsletter?: boolean; // GDPR opt-in for triggered email
+}
+```
+
+### `Customer`
+
+```ts
+{
+ customer_reference: string; // stable, non-PII pseudonymous ID
+ email?: string;
+ first_name?: string;
+ last_name?: string;
+ newsletter?: boolean;
+ marketing_permission?: boolean; // explicit GDPR opt-in
+}
+```
+
+Full TypeScript types: .