Multi-Attribute Pricing
Multi-Attribute Pricing
Multi-attribute pricing allows the pricing engine to select different Price Book Entries (PBEs) based on attribute values. Instead of resolving a single PBE per SKU + UOM combination, the engine evaluates additional fields -- such as Account type, region, or custom pricing attributes -- to find the PBE that matches the current context.
This is useful when the same product has different prices for different customer segments, partner tiers, or configuration options.
How Multi-Attribute PBE Resolution Works
When the pricing engine resolves a Price Book Entry, it follows this order:
- Account field auto-resolution -- The engine reads attribute values from the Account record linked to the Opportunity (e.g., Account.Type). If a PBE has a matching PricingAttribute value, that PBE is selected.
- Custom pricing attributes -- If the request includes customPricingAttributes, those values override or supplement the Account-level values for PBE resolution.
- __ANY__ fallback -- If a PBE is configured with the special value __ANY__ for a pricing attribute, it acts as a universal fallback when no specific match is found.
- Default PBE -- If no pricing attribute match is found and no __ANY__ PBE exists, the engine falls back to the default PBE (one with no pricing attribute constraints).
Resolution Priority
Priority | Source | Description |
|---|---|---|
1 (highest) | customPricingAttributes in request | Explicit override sent in the API call |
2 | Account field auto-resolution | Read from the Account record on the Opportunity |
3 | __ANY__ PBE | Universal fallback PBE |
4 (lowest) | Default PBE | PBE with no pricing attribute constraints |
AND Logic for Multiple Attributes
When a PBE has multiple pricing attribute fields, all fields must match for that PBE to be selected. This is AND logic -- every attribute must satisfy its condition.
Example Price Book Entries
The examples below reference a product NUE_ON_SALESFORCE (SKU) with the following PBE configurations:
PBE | UOM | PricingAttribute_1 (Account.Type) | PricingAttribute_2 | List Price |
|---|---|---|---|---|
PBE-DEFAULT | User/Month | (none) | (none) | $29.90 |
PBE-TECH | User/Month | Technology Partner | (none) | $19.90 |
PBE-CHANNEL | User/Month | Channel Partner | (none) | $24.90 |
PBE-ANY | User/Month | __ANY__ | (none) | $27.50 |
PBE-TECH-ENT | User/Month | Technology Partner | Enterprise | $14.90 |
Authentication
const myHeaders = new Headers();
myHeaders.append("nue-api-key", "YOUR_API_KEY_HERE");
myHeaders.append("Content-Type", "application/json");Use Case 1: Auto-Resolution from Account.Type
When the Opportunity's Account has Type = "Technology Partner", the pricing engine automatically resolves to the PBE whose PricingAttribute_1 matches "Technology Partner". No extra fields are needed in the request.
const myHeaders = new Headers();
myHeaders.append("nue-api-key", "YOUR_API_KEY_HERE");
myHeaders.append("Content-Type", "application/json");
const quoteData = {
opportunityId: "006xx000001abc123",
name: "Tech Partner Auto-Resolve Quote",
subscriptionStartDate: "2026-01-01",
subscriptionEndDate: "2027-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
products: [
{ productSku: "NUE_ON_SALESFORCE", uom: "User/Month", quantity: 10 }
]
};
fetch('https://api.nue.io/cpq/quotes:preview', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(quoteData)
})
.then(response => response.json())
.then(result => {
const line = result.quoteLineItems[0];
console.log(`Resolved unit price: $${line.listUnitPrice}`); // $19.90 (Technology Partner PBE)
console.log(`Total: $${line.totalPrice}`);
})
.catch(error => console.log('Error:', error));curl -X POST 'https://api.nue.io/cpq/quotes:preview' \
-H 'nue-api-key: YOUR_API_KEY_HERE' \
-H 'Content-Type: application/json' \
-d '{
"opportunityId": "006xx000001abc123",
"name": "Tech Partner Auto-Resolve Quote",
"subscriptionStartDate": "2026-01-01",
"subscriptionEndDate": "2027-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"products": [
{ "productSku": "NUE_ON_SALESFORCE", "uom": "User/Month", "quantity": 10 }
]
}'The pricing engine reads Account.Type from the Opportunity's Account, finds "Technology Partner", and selects PBE-TECH at $19.90/user/month.
Use Case 2: Different Account.Type Values Resolving to Different PBEs
The same product SKU resolves to a different unit price depending on the Account's type. An Opportunity linked to a "Channel Partner" Account gets channel pricing.
const quoteData = {
opportunityId: "006xx000002def456",
name: "Channel Partner Quote",
subscriptionStartDate: "2026-01-01",
subscriptionEndDate: "2027-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
products: [
{ productSku: "NUE_ON_SALESFORCE", uom: "User/Month", quantity: 10 }
]
};
fetch('https://api.nue.io/cpq/quotes:preview', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(quoteData)
})
.then(response => response.json())
.then(result => {
const line = result.quoteLineItems[0];
console.log(`Resolved unit price: $${line.listUnitPrice}`); // $24.90 (Channel Partner PBE)
})
.catch(error => console.log('Error:', error));curl -X POST 'https://api.nue.io/cpq/quotes:preview' \
-H 'nue-api-key: YOUR_API_KEY_HERE' \
-H 'Content-Type: application/json' \
-d '{
"opportunityId": "006xx000002def456",
"name": "Channel Partner Quote",
"subscriptionStartDate": "2026-01-01",
"subscriptionEndDate": "2027-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"products": [
{ "productSku": "NUE_ON_SALESFORCE", "uom": "User/Month", "quantity": 10 }
]
}'The Opportunity 006xx000002def456 is linked to an Account with Type = "Channel Partner", so the engine selects PBE-CHANNEL at $24.90/user/month.
Use Case 3: Null Account.Type (Default PBE Fallback)
When the Account's Type field is null or empty, and no __ANY__ PBE exists for that attribute, the engine falls back to the default PBE with no pricing attribute constraints.
const quoteData = {
opportunityId: "006xx000003ghi789",
name: "Default Pricing Fallback",
subscriptionStartDate: "2026-01-01",
subscriptionEndDate: "2027-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
products: [
{ productSku: "NUE_ON_SALESFORCE", uom: "User/Month", quantity: 10 }
]
};
fetch('https://api.nue.io/cpq/quotes:preview', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(quoteData)
})
.then(response => response.json())
.then(result => {
const line = result.quoteLineItems[0];
console.log(`Resolved unit price: $${line.listUnitPrice}`); // $29.90 (default PBE)
})
.catch(error => console.log('Error:', error));curl -X POST 'https://api.nue.io/cpq/quotes:preview' \
-H 'nue-api-key: YOUR_API_KEY_HERE' \
-H 'Content-Type: application/json' \
-d '{
"opportunityId": "006xx000003ghi789",
"name": "Default Pricing Fallback",
"subscriptionStartDate": "2026-01-01",
"subscriptionEndDate": "2027-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"products": [
{ "productSku": "NUE_ON_SALESFORCE", "uom": "User/Month", "quantity": 10 }
]
}'With a null Account.Type, no attribute-specific PBE matches. If a __ANY__ PBE exists it would be used; otherwise the engine selects PBE-DEFAULT at $29.90/user/month.
Use Case 4: Bundle Parent Auto-Resolution
Multi-attribute pricing works the same way for bundle parent products. The engine resolves the parent's PBE based on the Account's attributes, and each add-on resolves independently.
const quoteData = {
opportunityId: "006xx000001abc123",
name: "Bundle with Attribute Resolution",
subscriptionStartDate: "2026-01-01",
subscriptionEndDate: "2027-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
products: [
{
productSku: "ENTERPRISE_BUNDLE",
uom: "License/Month",
quantity: 50,
addOns: [
{ productSku: "ADDON_SUPPORT", uom: "License/Month", quantity: 1 },
{ productSku: "ADDON_STORAGE", uom: "GB/Month", quantity: 200 }
]
}
]
};
fetch('https://api.nue.io/cpq/quotes:preview', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(quoteData)
})
.then(response => response.json())
.then(result => {
const parent = result.quoteLineItems[0];
console.log(`Parent unit price: $${parent.listUnitPrice}`);
parent.childrenLineItems.forEach(child => {
console.log(` ${child.product.sku}: $${child.listUnitPrice}`);
});
})
.catch(error => console.log('Error:', error));curl -X POST 'https://api.nue.io/cpq/quotes:preview' \
-H 'nue-api-key: YOUR_API_KEY_HERE' \
-H 'Content-Type: application/json' \
-d '{
"opportunityId": "006xx000001abc123",
"name": "Bundle with Attribute Resolution",
"subscriptionStartDate": "2026-01-01",
"subscriptionEndDate": "2027-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"products": [
{
"productSku": "ENTERPRISE_BUNDLE",
"uom": "License/Month",
"quantity": 50,
"addOns": [
{ "productSku": "ADDON_SUPPORT", "uom": "License/Month", "quantity": 1 },
{ "productSku": "ADDON_STORAGE", "uom": "GB/Month", "quantity": 200 }
]
}
]
}'Each product in the bundle hierarchy resolves its own PBE based on the Account's attributes. The parent, ADDON_SUPPORT, and ADDON_STORAGE each independently match their attribute-specific PBEs.
Use Case 5: Custom Pricing Attributes (Explicit Override)
Use customPricingAttributes to explicitly specify attribute values for PBE resolution. This overrides the auto-resolved values from the Account record.
Each entry in the array requires:
- name -- The API name of the pricing attribute field on the PBE (e.g., Ruby__PricingAttribute_1__c)
- value -- The value to match against
const quoteData = {
opportunityId: "006xx000001abc123",
name: "Custom Attribute Override",
subscriptionStartDate: "2026-01-01",
subscriptionEndDate: "2027-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
products: [
{
productSku: "NUE_ON_SALESFORCE",
uom: "User/Month",
quantity: 10,
customPricingAttributes: [
{ name: "Ruby__PricingAttribute_1__c", value: "Technology Partner" }
]
}
]
};
fetch('https://api.nue.io/cpq/quotes:preview', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(quoteData)
})
.then(response => response.json())
.then(result => {
const line = result.quoteLineItems[0];
console.log(`Resolved unit price: $${line.listUnitPrice}`); // $19.90 (Technology Partner PBE)
console.log(`Total: $${line.totalPrice}`);
})
.catch(error => console.log('Error:', error));curl -X POST 'https://api.nue.io/cpq/quotes:preview' \
-H 'nue-api-key: YOUR_API_KEY_HERE' \
-H 'Content-Type: application/json' \
-d '{
"opportunityId": "006xx000001abc123",
"name": "Custom Attribute Override",
"subscriptionStartDate": "2026-01-01",
"subscriptionEndDate": "2027-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"products": [
{
"productSku": "NUE_ON_SALESFORCE",
"uom": "User/Month",
"quantity": 10,
"customPricingAttributes": [
{ "name": "Ruby__PricingAttribute_1__c", "value": "Technology Partner" }
]
}
]
}'Even if the Account's Type is different (or null), the customPricingAttributes value takes precedence and forces resolution to PBE-TECH at $19.90/user/month.
Use Case 6: __ANY__ Universal PBE Fallback
A PBE configured with __ANY__ as its pricing attribute value matches any attribute value that does not have a dedicated PBE. This provides a catch-all price for attribute values you have not explicitly configured.
For example, if Account.Type = "Reseller" and there is no PBE specifically for "Reseller", the engine falls back to PBE-ANY (with __ANY__) at $27.50/user/month.
const quoteData = {
opportunityId: "006xx000004jkl012",
name: "ANY Fallback Quote",
subscriptionStartDate: "2026-01-01",
subscriptionEndDate: "2027-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
products: [
{ productSku: "NUE_ON_SALESFORCE", uom: "User/Month", quantity: 10 }
]
};
fetch('https://api.nue.io/cpq/quotes:preview', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(quoteData)
})
.then(response => response.json())
.then(result => {
const line = result.quoteLineItems[0];
console.log(`Resolved unit price: $${line.listUnitPrice}`); // $27.50 (__ANY__ PBE)
})
.catch(error => console.log('Error:', error));curl -X POST 'https://api.nue.io/cpq/quotes:preview' \
-H 'nue-api-key: YOUR_API_KEY_HERE' \
-H 'Content-Type: application/json' \
-d '{
"opportunityId": "006xx000004jkl012",
"name": "ANY Fallback Quote",
"subscriptionStartDate": "2026-01-01",
"subscriptionEndDate": "2027-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"products": [
{ "productSku": "NUE_ON_SALESFORCE", "uom": "User/Month", "quantity": 10 }
]
}'The Account's Type is "Reseller", which has no dedicated PBE. The engine matches the __ANY__ PBE at $27.50 instead of falling through to the no-attribute default at $29.90.
Use Case 7: No Matching PBE (Error Scenario)
If the pricing engine cannot find any PBE that matches the resolved attributes -- no specific match, no __ANY__ fallback, and no default PBE -- it returns an error.
const quoteData = {
opportunityId: "006xx000001abc123",
name: "No Matching PBE",
subscriptionStartDate: "2026-01-01",
subscriptionEndDate: "2027-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
products: [
{
productSku: "LIMITED_PRODUCT",
uom: "User/Month",
quantity: 10,
customPricingAttributes: [
{ name: "Ruby__PricingAttribute_1__c", value: "Nonexistent Tier" }
]
}
]
};
fetch('https://api.nue.io/cpq/quotes:preview', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(quoteData)
})
.then(response => response.json())
.then(result => {
if (result.errorDetails) {
result.errorDetails.forEach(err => {
console.log(`Error: ${err.errorCode} -- ${err.message}`);
// PRICEBOOK_ENTRY_MISMATCH -- No matching PBE for the given attributes
});
}
})
.catch(error => console.log('Error:', error));curl -X POST 'https://api.nue.io/cpq/quotes:preview' \
-H 'nue-api-key: YOUR_API_KEY_HERE' \
-H 'Content-Type: application/json' \
-d '{
"opportunityId": "006xx000001abc123",
"name": "No Matching PBE",
"subscriptionStartDate": "2026-01-01",
"subscriptionEndDate": "2027-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"products": [
{
"productSku": "LIMITED_PRODUCT",
"uom": "User/Month",
"quantity": 10,
"customPricingAttributes": [
{ "name": "Ruby__PricingAttribute_1__c", "value": "Nonexistent Tier" }
]
}
]
}'Expected error:
{
"status": 400,
"error": "Failed to create quote",
"message": "No matching Price Book Entry found for product LIMITED_PRODUCT with the given pricing attributes"
}The response contains a PRICEBOOK_ENTRY_MISMATCH error indicating that no PBE could be resolved for the given product, UOM, and attribute combination.
Use Case 8: Multi-Field Custom Attributes (AND Logic)
When a PBE requires multiple pricing attributes to match, provide all of them in the customPricingAttributes array. The engine uses AND logic -- all attributes must match for the PBE to be selected.
const quoteData = {
opportunityId: "006xx000001abc123",
name: "Multi-Attribute AND Logic",
subscriptionStartDate: "2026-01-01",
subscriptionEndDate: "2027-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
products: [
{
productSku: "NUE_ON_SALESFORCE",
uom: "User/Month",
quantity: 10,
customPricingAttributes: [
{ name: "Ruby__PricingAttribute_1__c", value: "Technology Partner" },
{ name: "Ruby__PricingAttribute_2__c", value: "Enterprise" }
]
}
]
};
fetch('https://api.nue.io/cpq/quotes:preview', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(quoteData)
})
.then(response => response.json())
.then(result => {
const line = result.quoteLineItems[0];
console.log(`Resolved unit price: $${line.listUnitPrice}`); // $14.90 (Tech + Enterprise PBE)
console.log(`Total: $${line.totalPrice}`);
})
.catch(error => console.log('Error:', error));curl -X POST 'https://api.nue.io/cpq/quotes:preview' \
-H 'nue-api-key: YOUR_API_KEY_HERE' \
-H 'Content-Type: application/json' \
-d '{
"opportunityId": "006xx000001abc123",
"name": "Multi-Attribute AND Logic",
"subscriptionStartDate": "2026-01-01",
"subscriptionEndDate": "2027-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"products": [
{
"productSku": "NUE_ON_SALESFORCE",
"uom": "User/Month",
"quantity": 10,
"customPricingAttributes": [
{ "name": "Ruby__PricingAttribute_1__c", "value": "Technology Partner" },
{ "name": "Ruby__PricingAttribute_2__c", "value": "Enterprise" }
]
}
]
}'Both attributes must match: PricingAttribute_1 = "Technology Partner" AND PricingAttribute_2 = "Enterprise". This resolves to PBE-TECH-ENT at $14.90/user/month. If only one attribute matches, the engine does not select this PBE and falls back to the next best match.
Key Behaviors
Behavior | Description |
|---|---|
Auto-resolution | Pricing attributes are automatically read from the Account record on the Opportunity |
Custom override | customPricingAttributes overrides auto-resolved values for the specific product |
AND logic | Multiple pricing attributes on a PBE require all attributes to match |
__ANY__ fallback | A PBE with __ANY__ matches any attribute value without a dedicated PBE |
Default fallback | If no attribute-specific or __ANY__ PBE exists, the default PBE (no constraints) is used |
Per-product scope | customPricingAttributes are set per product in the products array |
Bundle support | Each product in a bundle hierarchy resolves its own PBE independently |
Error on no match | If no PBE can be resolved, the API returns a PRICEBOOK_ENTRY_MISMATCH error |
CustomPricingAttribute Field Reference
Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The API field name of the pricing attribute on the Price Book Entry (e.g., Ruby__PricingAttribute_1__c) |
value | string | Yes | The value to match against the PBE's pricing attribute field |
Troubleshooting
Symptom | Likely Cause | Resolution |
|---|---|---|
Unexpected price resolved | Account.Type auto-resolution overriding expectations | Use customPricingAttributes to explicitly set the desired attribute value |
PRICEBOOK_ENTRY_MISMATCH error | No PBE matches the resolved attributes | Verify PBE configurations in the Price Book; add a __ANY__ or default PBE |
Wrong PBE selected in bundle | Add-on resolving against parent's attributes | Each product resolves independently; check the add-on's own PBE configurations |
customPricingAttributes ignored | Incorrect field name | Use the full API field name (e.g., Ruby__PricingAttribute_1__c), not a label |