Fetching Customer Products
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
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:
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__ 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:
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:
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:
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:
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. |