---
title: Fetching Customer Products
slug: fetching-customer-products
docTags: 
createdAt: 2026-04-14T01:06:02.683Z
---

This guide covers the customer-scoped product catalog endpoint (`GET /customers/{customerId}/products`), which returns products filtered to only the price book entries relevant to a specific customer based on their account-level pricing attributes.

## Prerequisites

Before you begin, ensure you have:

- A valid Nue API key
- A customer ID (Nue ID or Salesforce external ID)
- Products published with pricing attributes configured in the catalog
- Basic knowledge of REST APIs and JSON

:::hint{type="info"}
**When to Use This Endpoint**

Use `GET /customers/{customerId}/products` instead of `GET /catalog/products` when you need to show a customer only the products and pricing tiers they are eligible for. This is especially useful for self-service portals and customer-facing storefronts where different customer segments see different product offerings.

- `GET /catalog/products` — Returns the full published catalog with all price book entries
- `GET /customers/{customerId}/products` — Returns the catalog filtered to only the price book entries that match the customer's account attributes
:::

## Authentication

All requests require authentication using your Nue API key in the `nue-api-key` header:

```javascript
const myHeaders = new Headers();
myHeaders.append("nue-api-key", "YOUR_API_KEY_HERE");
myHeaders.append("Content-Type", "application/json");
```

## How Filtering Works

The endpoint evaluates each price book entry's pricing attributes against the customer's account fields:

| Rule                         | Behavior                                                                                                                                                                   |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **No pricing attributes**    | Price book entry is universal — always included for every customer.                                                                                                        |
| **Account-level attributes** | Attributes mapped to `Quote.AccountId.{FieldName}` are resolved against the customer's account fields. All account-level conditions must match for the PBE to be included. |
| `__ANY__`**&#x20;wildcard**  | A pricing attribute with value `__ANY__` matches any customer, regardless of the customer's field value.                                                                   |
| **Quote-level attributes**   | Attributes not mapped to `Quote.AccountId.*` are ignored at catalog level — they are evaluated at order time.                                                              |
| **Product exclusion**        | Products with zero matching price book entries after filtering are excluded from the response entirely.                                                                    |
| **Bundle options**           | Product options within bundles are filtered by the same rules. Options whose PBE does not match are removed.                                                               |

### Example

Given a catalog with pricing attributes based on the customer's `Type` field:

| Product          | PBE          | Pricing Attribute                          | Matches "Channel Partner" | Matches "Prospect" |
| ---------------- | ------------ | ------------------------------------------ | ------------------------- | ------------------ |
| Platform License | Standard     | *(none)*                                   | Yes (universal)           | Yes (universal)    |
| Platform License | Partner Tier | `Quote.AccountId.Type` = `Channel Partner` | Yes                       | No                 |
| Support License  | Enterprise   | `Quote.AccountId.Type` = `Enterprise`      | No                        | No                 |

A customer with `Type = "Channel Partner"` would see Platform License with both PBEs (Standard + Partner Tier). A prospect would see Platform License with only the Standard PBE. Support License would be excluded for both since the Enterprise PBE does not match either customer type.

***

## Fetching Customer Products

### Basic Example

Fetch the filtered product catalog for a specific customer:

```javascript
const myHeaders = new Headers();
myHeaders.append("nue-api-key", "YOUR_API_KEY_HERE");
myHeaders.append("Content-Type", "application/json");

const customerId = "d2e04653-ae90-49df-a986-134cf64f6d03";

fetch(`https://api.nue.io/customers/${customerId}/products`, {
  method: 'GET',
  headers: myHeaders
})
.then(response => response.json())
.then(result => {
  console.log(`Status: ${result.status}`);
  console.log(`Products available: ${result.data.length}`);

  result.data.forEach(product => {
    console.log(`\n--- ${product.name} ---`);
    console.log(`SKU: ${product.sku}`);
    console.log(`Price Book Entries: ${product.priceBookEntries.length}`);

    product.priceBookEntries.forEach(pbe => {
      console.log(`  ${pbe.uom.name}: $${pbe.listPrice} (${pbe.currencyIsoCode})`);
    });

    // Display bundle options if applicable
    if (product.configurable && product.productOptions) {
      console.log(`Bundle Options: ${product.productOptions.length}`);
      product.productOptions.forEach(opt => {
        console.log(`  - ${opt.product?.name || opt.productOptionId}`);
      });
    }
  });
})
.catch(error => console.log('Error:', error));
```

### Scoping the Request with `productIds`

By default this endpoint reads the entire published catalog before filtering it. On a large catalog that single read can exceed the upstream response size limit and the request fails with HTTP 500.

If you already know which products you want to show, pass their IDs and only those products are fetched:

```javascript
const productIds = [
  "01tQC00000Iu1ofYAB",
  "01tQC00000KBr8jYAD",
  "01tQC00000KVUXlYAP"
];

const url = `https://api.nue.io/customers/${customerId}/products`
  + `?productIds=${encodeURIComponent(JSON.stringify(productIds))}`;

fetch(url, { method: 'GET', headers: myHeaders })
  .then(response => response.json())
  .then(result => {
    console.log(`Products returned: ${result.data.length}`);

    // IDs that could not be returned are reported rather than silently dropped
    result.warnings.forEach(w => console.log(`${w.code}: ${w.message}`));
  })
  .catch(error => console.log('Error:', error));
```

A few things worth knowing before you use it:

- **These are product IDs, not SKUs.** If you maintain your catalog list as SKUs, map them to IDs once and store the result. There is no SKU filter on this endpoint.
- **The value must be a JSON array of strings.** Double quotes are required. `[]`, a value that is not valid JSON, and a non-string element each return `400 INVALID_PARAMETER`.
- **Repeated IDs are de-duplicated**, so passing the same ID twice costs one lookup.
- **A product you asked for may not come back.** If the ID does not resolve, or the customer has no matching price book entry for it, the request still returns `200` and the ID is listed in a `PRODUCTS_NOT_FOUND` warning. Read `warnings` rather than assuming `data` mirrors your request.
- **Customer filtering still applies.** Scoping changes which products are fetched, not how they are filtered. The pricing attribute rules above are applied exactly as they are without the parameter.

Omit the parameter entirely to keep the original behaviour of returning the whole catalog.

### Using a Salesforce External ID

The endpoint accepts either a Nue customer ID or a Salesforce account ID:

```javascript
const salesforceAccountId = "001Em00001F0992IAB";

fetch(`https://api.nue.io/customers/${salesforceAccountId}/products`, {
  method: 'GET',
  headers: myHeaders
})
.then(response => response.json())
.then(result => {
  console.log(`Products for SF account: ${result.data.length}`);
})
.catch(error => console.log('Error:', error));
```

### Building a Customer Storefront

Use the filtered catalog to build a product selection UI where customers only see what they are eligible to purchase:

```javascript
async function loadStorefront(customerId) {
  const response = await fetch(
    `https://api.nue.io/customers/${customerId}/products`,
    { method: 'GET', headers: myHeaders }
  );
  const result = await response.json();

  if (result.status !== 'SUCCESS') {
    console.error('Failed to load catalog:', result);
    return;
  }

  const standaloneProducts = result.data.filter(p => !p.configurable);
  const bundles = result.data.filter(p => p.configurable);

  console.log('Standalone Products:');
  standaloneProducts.forEach(p => {
    const entry = p.priceBookEntries[0];
    console.log(`  ${p.name} — $${entry.listPrice}/${entry.uom.name}`);
  });

  console.log('\nBundles:');
  bundles.forEach(p => {
    console.log(`  ${p.name}`);
    p.productOptions?.forEach(opt => {
      const type = opt.bundled ? 'Included' : opt.required ? 'Required' : 'Optional';
      console.log(`    [${type}] ${opt.product?.name}`);
    });
  });
}

loadStorefront("d2e04653-ae90-49df-a986-134cf64f6d03");
```

***

## Response Reference

The response shape is identical to `GET /catalog/products`, wrapped in the standard self-service response envelope:

| Field      | Type   | Description                                                                        |
| ---------- | ------ | ---------------------------------------------------------------------------------- |
| `status`   | string | `"SUCCESS"` or `"ERROR"`.                                                          |
| `data`     | array  | Array of product objects, filtered to only those with matching price book entries. |
| `warnings` | array  | Any warnings generated during processing.                                          |

Each product in `data` has the same schema as `GET /catalog/products` — including `priceBookEntries`, `productOptions`, `productFeatures`, and all standard product fields. The only difference is that non-matching price book entries and product options have been removed.

## Error Responses

| Status | Error Code           | Description                                                                                              |
| ------ | -------------------- | -------------------------------------------------------------------------------------------------------- |
| 404    | `CUSTOMER_NOT_FOUND` | No customer found with the provided ID.                                                                  |
| 401    | `UNAUTHORIZED`       | Invalid or missing API key.                                                                              |
| 400    | `INVALID_PARAMETER`  | `productIds` was supplied but is not a JSON array of non-empty strings. An empty array is also rejected. |

## Warnings

Warnings are returned alongside a successful response rather than replacing it.

| Code                 | Description                                                                                                                                                                                                     |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PRODUCTS_NOT_FOUND` | One or more IDs passed in `productIds` were not returned, either because the ID does not resolve or because the customer has no matching price book entry for that product. The message lists the affected IDs. |
