> For the complete documentation index, see [llms.txt](https://the-wishlist-documentation.gitbook.io/the-wishlist-documentation-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://the-wishlist-documentation.gitbook.io/the-wishlist-documentation-docs/developer-reference/shopify-headless-developer-guide.md).

# Shopify Headless: Developer Guide

This guide covers integration with the TWC REST and JavaScript APIs for Shopify headless implementations. It maps every user-facing web scenario to the corresponding TWC API calls, and is the authoritative reference for headless and custom Shopify storefronts.

> Full details for all API endpoints mentioned in this document are available in API REFERENCE docs.

> **Shopify App (Liquid) customisation:** Developers customising our existing Shopify App Liquid theme should refer to our JavaScript guides rather than this document. This REST/JS API guide is for headless and custom integration partners.

> **Shopify POS:** Users of our Shopify POS App should conform to this guide to ensure seamless operation with Shopify POS in store. Where sections are not applicable to POS, this is called out explicitly.

***

### Contents

1. Best Practices & Key Rules
2. Customer Validation (Pre-Wishlist)
3. Shopify New Account Redirect Handling
4. Authentication
5. Pagination
6. Wishlist Management
7. Cart Interactions
8. Sharing
9. View Wishlist
10. Back-in-Stock & Notifications
11. Customer Preferences
12. Error Handling
13. Open Questions

***

### 1. Best Practices & Key Rules

These rules apply across all wishlist operations. Read this section before implementing any feature.

> **Shopify App (Liquid) developers:** The rules in this section still apply, but your primary implementation references are the TWC JavaScript guides. This document covers the underlying REST API calls those guides abstract.

#### 1.1 Wishlist naming

* **Wishlist name is optional but strongly recommended.** TWC does not default this value, so you should default it in your own code (e.g. `"My Wishlist"` for the first list). The name is displayed in TWC reports and admin tools, and unnamed wishlists reduce the readability of reporting data.

#### 1.2 Default wishlist

* **Set the default wishlist flag explicitly.** TWC does not set this flag automatically. When creating or managing wishlists, your code should designate one wishlist as the default.
* **If using a legacy POS, we recommend also selecting the least recently created wishlist as the default** when retrieving and displaying wishlist items. This provides a stable, predictable experience across integrations, and is particularly important if you plan to integrate with a legacy POS that does not support multiple wishlists. If your POS only supports a single wishlist, either:
  * Use the `isDefault` flag to identify the primary list, or
  * Display the oldest wishlist in the POS.

#### 1.3 Wishlist item rules

* **Items can only be added to an already-created wishlist.** You must call `POST /wishlists` before calling `POST /wishlists/item`. Do not attempt to create and add items in the same call.
* **Always add items at the variant level**, not the parent product level. Every wishlist item must reference a specific `variantId` to ensure accurate stock, price, and attribute tracking.

> **Variant identifiers:** The Wishlist internal identifier for a variant is *variantId* (or just *id*). We store the Shopify variant number as the *variantRef* (or just *ref*). Typically you will use *variantref* in your APIs when integrating with Shopify.

#### 1.4 Cart saves

* **`addedFromCart` flag:** Use this optional flag on the wishlist item when an item is added from the shopping cart (e.g. as a "save for later" behaviour). This allows accurate attribution in reporting.

#### 1.5 In-store item adds

> **Shopify POS:** This section is not applicable to Shopify POS. TWC provides a dedicated custom Shopify POS app that handles in-store wishlist operations natively. Contact the TWC team for access and setup details.

For other in-store or custom POS integrations (non-Shopify POS), include the following three fields on the wishlist item record when adding items in-store:

| Field          | Description                                                   |
| -------------- | ------------------------------------------------------------- |
| `addedByStaff` | Boolean flag - indicates the item was added by a staff member |
| `locationRef`  | Store ID - identifies which store the item was added at       |
| `staffRef`     | Staff ID - identifies which staff member added the item       |

These three fields together enable staff attribution and appear in TWC reporting dashboards. Without all three, staff attribution will be incomplete.

> **`noPostStore` flag:** There is an additional optional flag `noPostStore` which disables the standard post-store-visit notification for a specific item, if post-store notifications are enabled at the company level. Use this when a staff member adds an item that should not trigger a follow-up notification to the customer.

### 1.6 Sharing wishlists

For headless and non-Shopify storefronts, we recommend using the TWC `wishlistId` as the identifier in the share URL:

```
https://yourdomain.com/share/{wishlistId}
```

The `wishlistId` is a randomly generated UUID. It can be used directly as an unguessable share identifier, without requiring the storefront to generate, encrypt or maintain a separate sharing token.

Your storefront must implement the share route—for example, `/share/{wishlistId}`—and use the supplied `wishlistId` to retrieve and display the shared wishlist through the appropriate TWC API.

For example:

```
https://yourdomain.com/share/550e8400-e29b-41d4-a716-446655440000
```

Using the persistent TWC wishlist identifier keeps the link stable for the lifetime of the wishlist and avoids the need for a separate token lookup.

#### Security considerations

A shared wishlist URL should be treated as a bearer link: anyone who receives the URL may be able to view the shared wishlist.

The shared-wishlist experience should therefore:

* display only wishlist information intended for sharing;
* avoid exposing customer or account information;
* provide read-only access; and
* prevent unauthenticated update or delete operations.

> This approach applies to headless and non-Shopify implementations. Standard TWC Shopify app installations use TWC’s existing Shopify sharing mechanism and do not need to implement this URL pattern.

#### 1.7 Wishlist item identifiers

* **Use `wishlistItemId` as the primary identifier for wishlist items.**
* There is generally no need to use `wishlistItemRef`.
* `wishlistItemId` is the TWC-generated unique identifier and should be used for all item operations (update, delete, move, mark purchased).

#### 1.8 Customer data rules

* **Email address and mobile must be unique** per customer record. Do not create duplicate customers — always look up by email first (see Section 2).
* **`customerRef` is the Shopify customer ID.** This is the primary external identifier linking a TWC customer record to Shopify. Always populate `customerRef` with the Shopify customer ID when it is available.
  * Ensure `customerRef` has a **consistent data type** across all records.
  * **When creating a customer, if there is no Shopify customer ID yet (i.e. customer has not signed up/logged in), leave `customerRef` blank.** Do not substitute the customer's email address or any other value as this will cause data integrity issues and break lookups. The correct resolution is described in Section 2, Path C.

***

### 2. Customer Validation (Pre-Wishlist)

> **This check is mandatory before every wishlist create or item add operation.**

Before creating a wishlist or adding a wishlist item, the TWC customer record must exist and must carry a valid Shopify customer identifier (`customerRef`). As Shopify account-creation webhooks are not guaranteed to fire reliably, the required approach is to validate the customer proactively at the point of action (e.g. when the user clicks "Save to Wishlist").

#### 2.1 Step 1 — Resolve the customer by email

Always the first call before any wishlist write.

| Step                      | API call                              | Notes                                                    |
| ------------------------- | ------------------------------------- | -------------------------------------------------------- |
| Look up customer by email | `GET /customers/{encodedEmail}/email` | Obtain the email from the authenticated Shopify session. |

**Response payload — key fields to evaluate:**

| Field           | Type          | Notes                                                                                            |
| --------------- | ------------- | ------------------------------------------------------------------------------------------------ |
| `CustomerId`    | String (UUID) | TWC unique identifier. Primary key for all subsequent wishlist API calls.                        |
| customer`Ref`   | String        | Shopify customer ID. Must be present before wishlist creation. Null on partial records (Path C). |
| `email`         | String        | Echoed back for confirmation.                                                                    |
| `wishlistCount` | Integer       | Number of wishlists currently held. Useful to skip `GET /wishlists` if 0.                        |

#### 2.2 Step 2 — Apply resolution path

Evaluate the response and branch into one of three paths:

***

**Path A — Full match ✅**

**Condition:** `customerId` (TW) is present **AND** `customerRef` is present.

```
1. POST /wishlists              → create wishlist
2. POST /wishlists/items        → add item
```

No customer record changes needed. Proceed directly to wishlist operations.

***

**Path B — No customer record 🆕**

**Condition:** Empty array returned — no matching customer exists.

```
1. POST /customers              → { email, customerRef }
2. POST /wishlists              → create wishlist
3. POST /wishlists/items        → add item
```

***

**Path C — Partial record (no `customerRef`) ⚠️**

**Condition:** `customerId` (TW) is present **BUT** `customerRef` is null or absent.

```
1. PUT /customers?{customerRef}       → { id }  # pass customerRef as a parameter and Id in the body
2. POST /wishlists                    → create wishlist
3. POST /wishlists/items              → add item
```

**Why does Path C occur?** A customer previously submitted their email via a Notify Me or Register Interest widget while not logged in to Shopify. A TWC lead record was created with the email address, but no Shopify customer identifier was available at the time. Once the customer logs in, the `customerRef` becomes available from the session and must be patched onto the existing record before proceeding.

***

#### 2.3 Flow diagram

```
User clicks "Save to Wishlist"
         │
         ▼
GET /customers/{encodedEmail}/email
         │
         ├─── Empty array ──────────────────────────────► PATH B
         │                                                  POST /customers { email, customerRef }
         │                                                  │
         ├─── customerId ✓  customerRef ✓ ─────────────► PATH A
         │                                                  │
         └─── customerId ✓  customerRef ✗ ─────────────► PATH C
                                                            PUT /customers/{customerRef} { id }
                                                            │
                                        ┌───────────────────┘
                                        │
                                        ▼
                               POST /wishlists
                                        │
                                        ▼
                               POST /wishlists/items
                                        │
                                        ▼
                               POST /events  (type: WISHLIST_ADD)
                                        │
                                        ▼
                               Update UI / item count badge
```

***

### 3. Shopify New Account Redirect Handling

When a customer creates a new Shopify account mid-flow (e.g. prompted during checkout or on first wishlist save), the browser is redirected away from the page before the wishlist action can complete. The wishlist intent must be preserved across this redirect.

**Recommended pattern:**

1. Before the redirect occurs, serialise the pending wishlist action and store it in `sessionStorage` under the key `twc_pending_action`.
2. After the Shopify OAuth callback returns and the authenticated session is established, check `sessionStorage` for a pending action.
3. If found, replay the full customer validation flow (Section 2) and complete the wishlist operation.
4. Clear `twc_pending_action` from `sessionStorage` once the action has been successfully replayed.

```javascript
// Before redirect — store the intent
sessionStorage.setItem('twc_pending_action', JSON.stringify({
  type: 'ADD_TO_WISHLIST',
  variantId: '12345678',
  productId: '87654321'
}));

// After OAuth callback — replay
const pending = sessionStorage.getItem('twc_pending_action');
if (pending) {
  const action = JSON.parse(pending);
  await replayWishlistAction(action); // run customer validation + wishlist flow
  sessionStorage.removeItem('twc_pending_action');
}
```

> This pattern applies to both legacy Shopify customer accounts and the new Shopify Customer Accounts API (2024+). Always validate the customer session server-side before replaying the action — never trust a client-supplied customer ID.

***

### 4. Authentication

TW API authentication supports two modes:

* **Direct authentication** — API key or token-based auth issued directly by TWC. Suitable for server-side integrations and custom backends.

> Please contact us for your credentials if you wish to authenticate directly to our REST API.  For more details, please refer to our API Reference docs.

* **Shopify Proxy App** — authentication routed through the TW Shopify Proxy App, which handles token exchange and session validation within the Shopify storefront context. This is the recommended approach for Shopify headless implementations as it avoids exposing API credentials client-side.

> Newer deployments of The Wishlist have the proxy app available by default.  For older clients, please contact us to set up the Shopify Proxy App for you.

***

### 5. Pagination

All list endpoints in the TW API return paginated results. You must handle pagination correctly to avoid incomplete data sets, particularly for customers with large wishlists or when loading item lists.

#### 5.1 Pagination strategy

TWC uses **cursor-based pagination** (not offset/page-number). Each response includes a cursor pointing to the next page of results.

#### 5.2 Request parameters

| Parameter    | Type    | Default     | Description                                                          |
| ------------ | ------- | ----------- | -------------------------------------------------------------------- |
| `pageSize`   | Integer | 50          | Number of records to return per page.                                |
| `lastItemId` | String  | —           | Opaque cursor from the previous response. Omit on the first request. |
| `sortBy`     | String  | `createdAt` | Field to sort by. Common values: `createdAt`, `updatedAt`, `name`.   |
| `sortOrder`  | String  | `desc`      | Sort direction: `asc` or `desc`.                                     |

#### 5.3 Response envelope

```json
{
  "data": [ ... ],
  "lastItemId": "XXXXXX",
  "wishlistCount": 10
}
```

| Field         | Description                                                                      |
| ------------- | -------------------------------------------------------------------------------- |
| `lastItemId`  | Pass this value as the `lastItemId` parameter on the next request.               |
| wishlistCount | Total number of records across all pages. Use for UI display (e.g. "148 items"). |

#### 5.4 Pagination pattern (pseudocode)

```javascript
const PAGE_SIZE = 50; 
let lastItemId = null; 
let allItems = []; 

do 
{ const params = { id: wishlistId, pageSize:PAGE_SIZE, ...(lastItemId && { lastItemId }) }; 
  const response = await get('/wishlist/items', params); 
  const {wishlistItems, itemCount, lastEvaluatedItemId } = response; allItems = allItems.concat(wishlistItems); 
  // If we get fewer items than requested, we've reached the end 
  lastItemId = itemCount === PAGE_SIZE ? lastEvaluatedItemId : null; 
}
while (lastItemId);

```

#### 5.5 UI recommendations

* **Don't load all pages upfront** for performance — use "load more" or infinite scroll triggered at the bottom of the list.
* **Display `totalCount`** as a badge or subtitle so customers know how many items they have.
* **Preserve the cursor in component state** (not URL) — wishlist pages are typically not deep-linked at the item level.
* For **POS / admin views** that need to show all items, it is acceptable to paginate through all pages on load (using the loop above), but set a reasonable ceiling (e.g. stop at 500 items and display a warning).

#### 5.6 Paginated endpoints

| Endpoint                                                                                                                                                                    | Notes                                                                                                                                 |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /wishlists/byCustomer?customerId={id}`                                                                                                                                 | Returns all wishlists + total item count + per-list counts. Not paginated — always returns all lists. Use this before fetching items. |
| `GET /wishlists/items?id={wishlistId}`                                                                                                                                      | Primary paginated endpoint. Use `limit: 20–50` for web, `limit: 100` for POS/export.                                                  |
| `GET /customers/{encodedEmail}/email`                                                                                                                                       | Returns 0 or 1 result. No pagination required.                                                                                        |
| <p><code>GET /wishlists/byCustomer?customerId={customerId}</code></p><p><code>GET /wishlists/search?updated\_after={date}</code></p><p><code>GET /wishlists/{id}</code></p> | Return wishlist + items. Paginate items the same as above.                                                                            |
|                                                                                                                                                                             |                                                                                                                                       |

***

### 6. Wishlist Management

#### 6.1 Create first wishlist (authenticated user)

*Triggered on first "Save" action when the customer has no wishlist. Always run customer validation (Section 2) first.*

| Step                        | API call                              | Notes                                                              |
| --------------------------- | ------------------------------------- | ------------------------------------------------------------------ |
| Check for existing customer | `GET /customers/{encodedEmail}/email` | See Section 2 for full resolution logic                            |
| Create default wishlist     | `POST /wishlists`                     | `{ customerId, name, type: "DEFAULT", storeRef, isDefault: true }` |
| Add first item              | `POST /wishlists/items`               | `{ variantId, productId, quantity: 1 }` — always use variantId     |
| Return updated wishlist     | `GET /wishlists/{wishlistId}`         | Response used to update UI state and item count badge              |

> **Naming:** Wishlist name is optional but recommended. TWC does not set a default — recommend defaulting to `"My Wishlist"` in your code. This name appears in TWC reports.

> **Default flag:** Set `isDefault: true` on the first wishlist created. TWC does not set this automatically.

#### 6.2 Add item to existing wishlist

| Step             | API call                                    | Notes                                                                  |
| ---------------- | ------------------------------------------- | ---------------------------------------------------------------------- |
| Resolve wishlist | `GET /wishlists/byCustomer?customerId={id}` | Returns all lists with counts; UI picks default or prompts if multiple |
| Add item         | `POST /wishlists/items`                     | `{ variantId, productId }` — variant level only                        |

> If the variant is OOS at time of add, consider back-in-stock notifications at this point (configurable per merchant).

#### 6.3 Add item in-store (non-Shopify POS)

> **Shopify POS users:** Use the dedicated TWC Shopify POS app. This section covers other custom in-store integrations only.

| Step                                        | API call                                   | Notes                                                                                                |
| ------------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| Resolve or validate customer                | `GET /wishlist/byCustomer?customerId={id}` | See Section 2                                                                                        |
| Add item with staff attribution             | `POST /wishlists/items`                    | Include `addedByStaff: true`, `locationRef`, `staffRef` — all three required for attribution         |
| (Optional) Suppress post-store notification | Include `noPostStore: true` on the item    | Only if post-store notifications are enabled at company level and should be suppressed for this item |

#### 6.4 Create additional wishlist

*Customer explicitly adds a new named wishlist.*

| Step                  | API call          | Notes                                           |
| --------------------- | ----------------- | ----------------------------------------------- |
| Create named wishlist | `POST /wishlists` | `{ customerId, name, type: "NAMED", storeRef }` |

> Max wishlist count per customer is a merchant-configurable setting. Enforce both client-side and server-side. Names: max 50 chars, UTF-8. Reserved name: `"Saved for Later"` (system-managed, do not use).

#### 6.5 Delete / remove item

| Step                 | API call                        | Notes                                              |
| -------------------- | ------------------------------- | -------------------------------------------------- |
| Remove single item   | `DELETE /wishlists/items/{iId}` | Use `wishlistItemId` — not `wishlistItemRef`       |
| Optimistic UI update | *(client-side)*                 | Remove from DOM immediately; rollback on API error |

> Soft-delete recommended; allow undo (5 s toast). Do not permanently delete until the undo window expires or the page navigates.

#### 6.6 Move item between wishlists

| Step                          | API call                        | Notes                               |
| ----------------------------- | ------------------------------- | ----------------------------------- |
| Add item to new list          | `POST /wishlists/items`         | `{ itemIds: [], targetWishlistId }` |
| Remove item from current list | `DELETE /wishlists/items/{iId}` | Refresh target list UI              |

> If `targetWishlistId = "new"`, create the wishlist first (Section 6.4) then move. Prevent moves to `"Saved for Later"` (reserved system list).

#### 6.7 Delete entire wishlist

| Step                        | API call                         | Notes                                                     |
| --------------------------- | -------------------------------- | --------------------------------------------------------- |
| Validate not last/only list | *(client + server guard)*        | Recommend do not delete if it is the customer's only list |
| Delete wishlist             | `DELETE /wishlists/{wishlistId}` | Cascades to items; `storeRef` used for audit log          |

> If wishlist has items, prompt "Move items before deleting" or "Delete all items". Deleting a shared wishlist should invalidate its share URL.

***

### 7. Cart Interactions

#### 7.1 Add wishlist item to cart

| Step                            | API call                                          | Notes                                       |
| ------------------------------- | ------------------------------------------------- | ------------------------------------------- |
| Add to cart (Shopify)           | `POST /cart/add.js` *(Shopify Storefront JS API)* | `{ items: [{ id: variantId, quantity }] }`  |
| (Optional) Remove from wishlist | `DELETE /wishlist/items/{wishlistItemId}`         | Merchant config: auto-remove on add-to-cart |

#### 7.2 Save for later (cart → wishlist)

*Customer clicks "Save for Later" on a cart line item.*

*Recommend either create a new wishlist 'Saved items' or use default wishlist.*

| Step                          | API call                                             | Notes                                                       |
| ----------------------------- | ---------------------------------------------------- | ----------------------------------------------------------- |
| Resolve or create system list | `POST /wishlists` *(if "Saved for Later" not found)* | `type: "SAVE_FOR_LATER"`, auto-created, hidden from main UI |
| Add item to Save for Later    | `POST /wishlists/items`                              | `{ variantId, productId, quantity, addedFromCart: true }`   |
| Remove from Shopify cart      | `POST /cart/change.js` *(Shopify Storefront JS API)* | `{ id: variantId, quantity: 0 }`                            |

#### **7.3 Remove a wishlist item after purchase**

By default, TWC does not remove an item from a customer’s wishlist after it is purchased.

If your implementation removes purchased items, first update the wishlist item to record why it is being removed. After that update succeeds, delete the item.

| **Step**                            | **API call**                              | **Notes**                                                                                                                                                                                                                                      |
| ----------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Record the purchase-removal context | `PUT /wishlist/items`                     | Set `addedFromCart: true` and `purchasedAndRemoved: true` on the wishlist item. See [Update Wishlist Item](https://the-wishlist-documentation.gitbook.io/the-wishlist-documentation-docs/wishlist-rest-api/wishlist-items/updatewishlistitem). |
| Remove the wishlist item            | `DELETE /wishlist/items/{wishlistItemId}` | Call this only after the update succeeds. See [Delete Wishlist Item by ID](https://the-wishlist-documentation.gitbook.io/the-wishlist-documentation-docs/wishlist-rest-api/wishlist-items/deletewishlistitembyid).                             |

The relevant fields in the update request are:

```json
{
  "addedFromCart": true,
  "purchasedAndRemoved": true
}
```

This example shows only the fields relevant to purchase removal. Include any other fields required by the [Update Wishlist Item](https://the-wishlist-documentation.gitbook.io/the-wishlist-documentation-docs/wishlist-rest-api/wishlist-items/updatewishlistitem) request.

> Do not set `purchased` or `datePurchased`. TWC sets these fields during order processing.
>
> You can also optionally set the purchasedAndRemoved=true flag on the DELETE endpoint:  `DELETE /wishlist/items/{wishlistItemId}?purchasedAndRemoved=true`

Only perform this process after the purchase has been confirmed. Adding an item to the Shopify cart is not evidence that it has been purchased.

***

### 8. Sharing

#### 8.1 Share wishlist

| Step                | API call        | Notes                                                                                            |
| ------------------- | --------------- | ------------------------------------------------------------------------------------------------ |
| Generate share link | *(client-side)* | Construct URL as `https://yourdomain.com/share/{wishlistId}` — no separate token endpoint needed |
| Display share UI    | *(client-side)* | Copy link / native share sheet / email options                                                   |

> Share URLs use the TWC `wishlistId` directly appended to your domain's share path. This keeps links stable with no token expiry to manage.

#### 8.2 View shared wishlist (recipient)

| Step                        | API call                                            | Notes                                                    |
| --------------------------- | --------------------------------------------------- | -------------------------------------------------------- |
| Load wishlist by ID         | `GET /wishlists/{wishlistId}`                       | Returns wishlist + items (public fields only)            |
| Display shared view         | *(client-side read-only render)*                    | No edit controls; show "Add to my wishlist" / "Buy" CTAs |
| Recipient: copy to own list | `POST /wishlists/{myWishlistId}/items` *(per item)* | Loop or bulk endpoint; skip duplicates                   |

> If wishlist is not found or not public, return a friendly "This wishlist is no longer available" page.

***

### 9. View Wishlist

#### 9.1 Load customer's wishlists and items

This is a deliberate two-step sequence. **Do not attempt to fetch items without first calling `GET /wishlist/all`** — the summary call is cheap and gives you everything you need to render the wishlist UI shell and decide which list to load items for.

**Step 1 — Fetch wishlist summary (`GET /wishlist/byCustomer`)**

| Step                         | API call                                            | Notes                                                                                             |
| ---------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Authenticate customer        | `GET /customers/{customerId}` *(or via session)*    | Shopify customer session → resolve TWC `customerId`                                               |
| Fetch all wishlists + totals | `GET /wishlists/byCustomer?customerId={customerId}` | Returns all wishlists for the customer, total items across all lists, and per-wishlist item count |

**Response includes:**

| Field                    | Description                                                                                                                                    |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `totalItems`             | Total number of wishlist items across **all** of the customer's wishlists — use this to drive the wishlist badge/icon count in the site header |
| `wishlists[]`            | Array of all wishlists for the customer                                                                                                        |
| `wishlists[].wishlistId` | Wishlist identifier                                                                                                                            |
| `wishlists[].name`       | Wishlist display name                                                                                                                          |
| `wishlists[].itemCount`  | Number of items in this specific wishlist — use for per-list badges and list picker UI                                                         |
| `wishlists[].isDefault`  | Whether this is the designated default wishlist                                                                                                |
| `wishlists[].createdAt`  | Used to identify the oldest list (recommended default for display — see Section 1.2)                                                           |

> Use `totalItems` to populate the wishlist icon badge in your site header without needing to load any item detail. Use `wishlists[].itemCount` to show counts in a multi-wishlist picker or tab UI. **Do not derive these counts client-side from item arrays** — always use the values returned by this endpoint.

**Step 2 — Fetch items for the selected wishlist**

Only call this once the customer opens or selects a specific wishlist. Do not pre-fetch items for all wishlists on page load.

| Step                          | API call                                              | Notes                                                                          |
| ----------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------ |
| Fetch items for active list   | `GET /wishlists/items?id={wishlistId}`                | Paginated — see Section 5. Call only for the wishlist the customer is viewing. |
| Enrich with live Shopify data | `GET /products/{productId}.json` *(Shopify REST API)* | Live price, availability, variant status                                       |

> Cache product enrichment for \~60 s to avoid Shopify rate-limiting. Surface OOS badges inline. Default sort order: most recently added first (merchant-configurable).

**Sequence summary**

```
Page load / wishlist icon render
        │
        ▼
GET /wishlist/byCustomer?customerId={id}
        │
        ├── totalItems         → update header badge
        ├── wishlists[].name   → populate wishlist picker / tabs
        └── wishlists[].itemCount → per-list counts in UI
        │
        (customer opens a wishlist)
        │
        ▼
GET /wishlist/items?id={wishlistId}   (paginated)
        │
        ▼
GET /products/{id}.json  ×N         (Shopify enrichment, cached)
        │
        ▼
Render wishlist items
```

***

### 10. Back-in-Stock & Notifications

#### 10.1 Notify me when back in stock

*A notification that does not require the customer to have an active wishlist. When there is no stock, change the 'Add to cart' button to 'Notify Me'.*&#x20;

> We recommend you use our Javascript widgets for Notify me. &#x20;

| Step                          | API call                                                  | Notes                                                                    |
| ----------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------ |
| Register notification request | `POST /wishlist/items/customerInterest`                   | `{ customerId / email, variantId, productId, storeRef, notifyMe= true }` |
| Confirm subscription          | *(response)*                                              | Returns `{ subscriptionId, status: "PENDING" }`                          |
| Unsubscribe (optional)        | `DELETE /wishlist/items/customerInterest?customerId={id}` | Customer-initiated                                                       |

> For anonymous users: collect email address. On restock Shopify webhook (`inventory_level/update`), TWC back-end queries active subscriptions for that `variantId` and dispatches email notifications.

#### 10.2 Register interest (pre-launch / waitlist)

*For products not yet available or coming soon. Note, there are javascript widgets available for this feature.*

> We recommend you use our Javascript widgets for Register Interest / Coming soon.

| Step                       | API call                                                          | Notes                                                                       |
| -------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Register interest          | `POST /wishlist/items/customerInterest`                           | `{ email / customerId, productId, storeRef, source }`                       |
| List customer interests    | `GET /wishlist/items/customerInterest/{email}`                    | For display in customer account / preferences UI. Paginate — see Section 5. |
| Cancel interest (optional) | `DELETE /wishlist/items/customerInterest?customerId={customerId}` |                                                                             |

> Register Interest differs from Back-in-Stock: the product may not exist in Shopify yet (e.g. pre-order). Store `productHandle` or the merchant's own SKU reference. Dispatch is manual or triggered by the merchant via TWC admin.

***

### 11. Customer Preferences

#### 11.1 Like / dislike a category

*Affinity signal — updates the customer preference profile. Note, there are javascript widgets available for this feature.*

> We recommend you use our Javascript widgets for managing preferences.

| Step                         | API call                                           | Notes                                                            |
| ---------------------------- | -------------------------------------------------- | ---------------------------------------------------------------- |
| Record preference signal     | `POST /customer/preference`                        | `{ type: "CATEGORY", referenceId, signal: "LIKE" \| "DISLIKE" }` |
| Retrieve current preferences | `GET /customer/preference?customerId={customerId}` | Returns `[{ type, referenceId, signal, updatedAt }]`             |
| Remove / reset preference    | `DELETE /customer/preference/{prefId}`             |                                                                  |

> `referenceId` = Shopify `product_type` or custom taxonomy tag. Signals feed the TWC recommendation engine and Outreach targeting rules. Consider debouncing rapid like/dislike toggles (300 ms) before firing the API call.

#### 11.2 Preference management (full profile)

*Customer account page — manage all stored preferences.*

> We recommend you use our Javascript widgets for managing preferences.

| Step                             | API call                                           | Notes                                                                  |
| -------------------------------- | -------------------------------------------------- | ---------------------------------------------------------------------- |
| Fetch full preference profile    | `GET /customer/preference?customerId={customerId}` | Include categories, brands, sizes, notification preferences            |
| Bulk update preferences          | `PUT /customer/preference/{prefId}`                | `{ preferences: [...] }` — idempotent replace                          |
| Update communication preferences | `PUT /customers`                                   | `{ id, optin_preferences }`                                            |
| Sync to Shopify customer         | *(TWC back-end)*                                   | `PUT /admin/api/customers/{shopifyId}.json` — `marketing_opt_in_level` |

> Consent updates must propagate to Shopify within the same transaction boundary. Honour Shopify's `customer.updated_at` as the conflict-resolution timestamp.

***

### 12. Error Handling

All error responses include a machine-readable `code` field in addition to the HTTP status. Use `code` for client-side branching — do not branch on HTTP status alone. Implement a global error boundary that maps error codes to user-friendly messages.

<table><thead><tr><th width="178.2265625">Scenario</th><th width="117.421875">HTTP status</th><th width="228.3515625">Error code</th><th>Recommended UI behaviour</th></tr></thead><tbody><tr><td>Duplicate item in wishlist</td><td>409</td><td><code>ITEM_EXISTS</code></td><td>Show "Already in your wishlist"</td></tr><tr><td>Wishlist not found</td><td>404</td><td><code>WISHLIST_NOT_FOUND</code></td><td>Reload wishlist list</td></tr><tr><td>Customer not found</td><td>404</td><td><code>CUSTOMER_NOT_FOUND</code></td><td>Run customer creation flow (Section 2, Path B)</td></tr><tr><td>Session expired</td><td>401</td><td><code>SESSION_EXPIRED</code></td><td>Trigger re-auth; preserve pending action via <code>twc_pending_action</code></td></tr><tr><td>Validation error</td><td>422</td><td><code>VALIDATION_ERROR</code></td><td>Surface field-level error message</td></tr></tbody></table>

***
