Bundles Advanced
Bundles Advanced
This guide covers advanced bundle scenarios for the Nue CPQ Quote API, including dynamic product options, per-addon overrides, nested bundles, quantity scaling, and multi-bundle quotes. For basic bundle usage (auto-populated children, optional add-ons, simple quantities), see BundlesBundles.
Use this guide when you need to accept or reject specific add-ons, override billing or term settings on individual add-ons, work with bundles inside bundles, or understand how quantity scaling behaves across bundle hierarchies.
Quantity Scaling Modes
When a bundle parent has a quantity greater than 1, child add-on quantities can scale in different ways depending on the product configuration:
Mode | Behavior | Example (parent qty = 5, child configured qty = 2) |
|---|---|---|
LinkToBundleQuantity | Child quantity = parent quantity * child configured quantity | Child qty = 5 * 2 = 10 |
Independent | Child quantity is independent of the parent | Child qty = 2 (unchanged) |
The quantity mode is configured in the product catalog on each add-on's relationship to its parent. It cannot be changed at quote time.
Auto-Renew on Nested Bundles
When autoRenew: true is set on the quote header, it applies to all products including nested bundle hierarchies. Each subscription created from the quote -- parent and children alike -- inherits the auto-renew setting.
Authentication Setup
All examples below use the following headers:
const myHeaders = new Headers();
myHeaders.append("nue-api-key", "YOUR_API_KEY_HERE");
myHeaders.append("Content-Type", "application/json");Use Case 1: Dynamic Product Options (Accepted and Rejected Add-Ons)
Control which optional add-ons are included on a bundle by listing only the desired add-ons in the addOns array. Add-ons not listed are excluded from the quote. Bundled (required) add-ons are always auto-populated regardless of the addOns array contents.
const quoteData = {
opportunityId: "006xx000001abc123",
name: "Selective Add-Ons",
subscriptionStartDate: "2025-01-01",
subscriptionEndDate: "2026-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
products: [
{
productSku: "ENTERPRISE_SUITE",
uom: "User/Month",
quantity: 50,
addOns: [
// Accept PREMIUM_SUPPORT and DATA_EXPORT
{ productSku: "PREMIUM_SUPPORT", uom: "User/Month", quantity: 50 },
{ productSku: "DATA_EXPORT", uom: "User/Month", quantity: 50 }
// ADVANCED_ANALYTICS and COMPLIANCE_MODULE are optional but not listed -- they are excluded
]
}
]
};
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: ${parent.product.sku}`);
console.log(`Children: ${parent.childrenLineItems.length}`);
parent.childrenLineItems.forEach(child => {
console.log(` └─ ${child.product.sku}: qty=${child.quantity}, $${child.totalPrice}`);
});
// Includes: auto-populated bundled items + PREMIUM_SUPPORT + DATA_EXPORT
// Excludes: ADVANCED_ANALYTICS, COMPLIANCE_MODULE (not in addOns)
})
.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": "Selective Add-Ons",
"subscriptionStartDate": "2025-01-01",
"subscriptionEndDate": "2026-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"products": [
{
"productSku": "ENTERPRISE_SUITE",
"uom": "User/Month",
"quantity": 50,
"addOns": [
{ "productSku": "PREMIUM_SUPPORT", "uom": "User/Month", "quantity": 50 },
{ "productSku": "DATA_EXPORT", "uom": "User/Month", "quantity": 50 }
]
}
]
}'The response childrenLineItems contains only the auto-populated bundled items plus the two explicitly accepted add-ons. Optional add-ons not listed in addOns are not included.
Use Case 2: Per-Add-On Overrides (Term, Billing Period, Billing Timing)
Individual add-ons within a bundle can have their own subscriptionTerm, billingPeriod, and billingTiming values that differ from the parent bundle and from each other.
const quoteData = {
opportunityId: "006xx000001abc123",
name: "Per-AddOn Overrides",
subscriptionStartDate: "2025-01-01",
subscriptionEndDate: "2026-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
billingPeriod: "Annual",
billingTiming: "In Advance",
products: [
{
productSku: "ENTERPRISE_SUITE",
uom: "User/Month",
quantity: 50,
addOns: [
{
productSku: "PREMIUM_SUPPORT",
uom: "User/Month",
quantity: 50,
billingPeriod: "Monthly",
billingTiming: "In Arrears"
// Override: billed monthly in arrears instead of annual in advance
},
{
productSku: "DATA_EXPORT",
uom: "User/Month",
quantity: 50,
subscriptionTerm: 6,
subscriptionStartDate: "2025-01-01",
subscriptionEndDate: "2025-07-01",
billingPeriod: "Quarterly",
billingTiming: "In Advance"
// Override: 6-month term, quarterly billing
}
]
}
]
};
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 billing: ${parent.billingPeriod}, ${parent.billingTiming}`);
parent.childrenLineItems.forEach(child => {
console.log(` └─ ${child.product.sku}: term=${child.subscriptionTerm}mo, billing=${child.billingPeriod} ${child.billingTiming}, $${child.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": "Per-AddOn Overrides",
"subscriptionStartDate": "2025-01-01",
"subscriptionEndDate": "2026-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"billingPeriod": "Annual",
"billingTiming": "In Advance",
"products": [
{
"productSku": "ENTERPRISE_SUITE",
"uom": "User/Month",
"quantity": 50,
"addOns": [
{
"productSku": "PREMIUM_SUPPORT",
"uom": "User/Month",
"quantity": 50,
"billingPeriod": "Monthly",
"billingTiming": "In Arrears"
},
{
"productSku": "DATA_EXPORT",
"uom": "User/Month",
"quantity": 50,
"subscriptionTerm": 6,
"subscriptionStartDate": "2025-01-01",
"subscriptionEndDate": "2025-07-01",
"billingPeriod": "Quarterly",
"billingTiming": "In Advance"
}
]
}
]
}'Result:
- ENTERPRISE_SUITE parent: 12-month term, Annual In Advance billing.
- PREMIUM_SUPPORT: inherits the 12-month term, but uses Monthly In Arrears billing.
- DATA_EXPORT: 6-month term override, Quarterly In Advance billing.
Use Case 3: Nested Bundles (Bundle Within a Bundle)
A bundle can contain another bundle as a child. The inner bundle's own children (auto-populated and explicit add-ons) are resolved recursively. In the response, the nested bundle appears inside the outer bundle's childrenLineItems, and the nested bundle has its own childrenLineItems for its children.
const quoteData = {
opportunityId: "006xx000001abc123",
name: "Nested Bundle Quote",
subscriptionStartDate: "2025-01-01",
subscriptionEndDate: "2026-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
products: [
{
productSku: "ENTERPRISE_SUITE",
uom: "User/Month",
quantity: 10,
addOns: [
{
productSku: "ANALYTICS_BUNDLE", // This is itself a bundle
uom: "User/Month",
quantity: 10,
addOns: [
{ productSku: "ADVANCED_DASHBOARDS", uom: "User/Month", quantity: 10 }
]
},
{ productSku: "PREMIUM_SUPPORT", 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 outer = result.quoteLineItems[0];
console.log(`Outer bundle: ${outer.product.sku}`);
outer.childrenLineItems.forEach(child => {
console.log(` └─ ${child.product.sku}: $${child.totalPrice}`);
if (child.childrenLineItems && child.childrenLineItems.length > 0) {
child.childrenLineItems.forEach(grandchild => {
console.log(` └─ ${grandchild.product.sku}: $${grandchild.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": "Nested Bundle Quote",
"subscriptionStartDate": "2025-01-01",
"subscriptionEndDate": "2026-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"products": [
{
"productSku": "ENTERPRISE_SUITE",
"uom": "User/Month",
"quantity": 10,
"addOns": [
{
"productSku": "ANALYTICS_BUNDLE",
"uom": "User/Month",
"quantity": 10,
"addOns": [
{ "productSku": "ADVANCED_DASHBOARDS", "uom": "User/Month", "quantity": 10 }
]
},
{ "productSku": "PREMIUM_SUPPORT", "uom": "User/Month", "quantity": 10 }
]
}
]
}'Response structure:
ENTERPRISE_SUITE (outer bundle)
└─ ANALYTICS_BUNDLE (inner bundle)
└─ [auto-populated bundled children of ANALYTICS_BUNDLE]
└─ ADVANCED_DASHBOARDS (explicit add-on of inner bundle)
└─ PREMIUM_SUPPORT (add-on of outer bundle)
└─ [auto-populated bundled children of ENTERPRISE_SUITE]The inner bundle's childrenLineItems array contains its own resolved children, creating a multi-level hierarchy.
Use Case 4: Multi-Level Explicit Add-On Configuration
Configure explicit add-ons at multiple levels of a nested bundle hierarchy. Each level's addOns array controls which optional products are included at that level.
const quoteData = {
opportunityId: "006xx000001abc123",
name: "Multi-Level Add-On Config",
subscriptionStartDate: "2025-01-01",
subscriptionEndDate: "2026-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
products: [
{
productSku: "ENTERPRISE_SUITE",
uom: "User/Month",
quantity: 20,
discount: 10,
addOns: [
{
productSku: "ANALYTICS_BUNDLE",
uom: "User/Month",
quantity: 20,
discount: 15,
addOns: [
{ productSku: "ADVANCED_DASHBOARDS", uom: "User/Month", quantity: 20 },
{ productSku: "REAL_TIME_STREAMING", uom: "User/Month", quantity: 20 }
]
},
{ productSku: "PREMIUM_SUPPORT", uom: "User/Month", quantity: 20 },
{ productSku: "DATA_EXPORT", uom: "User/Month", quantity: 20 }
]
}
]
};
fetch('https://api.nue.io/cpq/quotes:preview', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(quoteData)
})
.then(response => response.json())
.then(result => {
const outer = result.quoteLineItems[0];
console.log(`${outer.product.sku}: ${outer.discount}% off`);
outer.childrenLineItems.forEach(child => {
console.log(` └─ ${child.product.sku}: ${child.discount}% off, $${child.totalPrice}`);
if (child.childrenLineItems && child.childrenLineItems.length > 0) {
child.childrenLineItems.forEach(gc => {
console.log(` └─ ${gc.product.sku}: ${gc.discount}% off, $${gc.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-Level Add-On Config",
"subscriptionStartDate": "2025-01-01",
"subscriptionEndDate": "2026-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"products": [
{
"productSku": "ENTERPRISE_SUITE",
"uom": "User/Month",
"quantity": 20,
"discount": 10,
"addOns": [
{
"productSku": "ANALYTICS_BUNDLE",
"uom": "User/Month",
"quantity": 20,
"discount": 15,
"addOns": [
{ "productSku": "ADVANCED_DASHBOARDS", "uom": "User/Month", "quantity": 20 },
{ "productSku": "REAL_TIME_STREAMING", "uom": "User/Month", "quantity": 20 }
]
},
{ "productSku": "PREMIUM_SUPPORT", "uom": "User/Month", "quantity": 20 },
{ "productSku": "DATA_EXPORT", "uom": "User/Month", "quantity": 20 }
]
}
]
}'Discount propagation:
- ENTERPRISE_SUITE has 10% off. Its direct children (PREMIUM_SUPPORT, DATA_EXPORT) inherit 10%.
- ANALYTICS_BUNDLE has its own 15% off (overrides parent's 10%). Its children (ADVANCED_DASHBOARDS, REAL_TIME_STREAMING) inherit 15%.
Use Case 5: Large Bundle Quantity (LinkToBundleQuantity Scaling)
When the parent bundle quantity is large, child add-ons configured with LinkToBundleQuantity scale their quantities proportionally. This example uses a parent quantity of 5 to demonstrate the scaling behavior.
const quoteData = {
opportunityId: "006xx000001abc123",
name: "Large Bundle Quantity Scaling",
subscriptionStartDate: "2025-01-01",
subscriptionEndDate: "2026-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
products: [
{
productSku: "ENTERPRISE_SUITE",
uom: "User/Month",
quantity: 5,
addOns: [
{ productSku: "PREMIUM_SUPPORT", uom: "User/Month", quantity: 1 },
{ productSku: "DATA_EXPORT", uom: "User/Month", quantity: 2 }
]
}
]
};
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: ${parent.product.sku}, qty=${parent.quantity}`);
parent.childrenLineItems.forEach(child => {
console.log(` └─ ${child.product.sku}: qty=${child.quantity}, $${child.totalPrice}`);
// If LinkToBundleQuantity:
// PREMIUM_SUPPORT: 5 * 1 = 5
// DATA_EXPORT: 5 * 2 = 10
// If Independent:
// PREMIUM_SUPPORT: 1
// DATA_EXPORT: 2
});
})
.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": "Large Bundle Quantity Scaling",
"subscriptionStartDate": "2025-01-01",
"subscriptionEndDate": "2026-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"products": [
{
"productSku": "ENTERPRISE_SUITE",
"uom": "User/Month",
"quantity": 5,
"addOns": [
{ "productSku": "PREMIUM_SUPPORT", "uom": "User/Month", "quantity": 1 },
{ "productSku": "DATA_EXPORT", "uom": "User/Month", "quantity": 2 }
]
}
]
}'Result with LinkToBundleQuantity mode:
- PREMIUM_SUPPORT: resolved quantity = 5 * 1 = 5.
- DATA_EXPORT: resolved quantity = 5 * 2 = 10.
Result with Independent mode:
- PREMIUM_SUPPORT: resolved quantity = 1 (as specified).
- DATA_EXPORT: resolved quantity = 2 (as specified).
The actual scaling behavior depends on how the add-on relationship is configured in the product catalog. Check the response quantity field on each child line item to confirm the resolved quantity.
Use Case 6: Two Bundles and a Standalone in the Same Quote
A quote can contain multiple bundles and standalone products. Each bundle is resolved independently, and all products are priced together on the same quote.
const quoteData = {
opportunityId: "006xx000001abc123",
name: "Multi-Bundle + Standalone",
subscriptionStartDate: "2025-01-01",
subscriptionEndDate: "2026-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
discount: 5,
products: [
{
productSku: "ENTERPRISE_SUITE",
uom: "User/Month",
quantity: 25,
discount: 10,
addOns: [
{ productSku: "PREMIUM_SUPPORT", uom: "User/Month", quantity: 25 },
{ productSku: "DATA_EXPORT", uom: "User/Month", quantity: 25 }
]
},
{
productSku: "ANALYTICS_BUNDLE",
uom: "User/Month",
quantity: 15,
addOns: [
{ productSku: "ADVANCED_DASHBOARDS", uom: "User/Month", quantity: 15 }
]
},
{ 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 => {
console.log(`Total: $${result.quote.totalAmount}`);
console.log(`Top-level items: ${result.quoteLineItems.length}`); // 3
result.quoteLineItems.forEach(item => {
const children = item.childrenLineItems || [];
if (children.length > 0) {
console.log(`Bundle: ${item.product.sku} (${item.discount}% off, ${children.length} children)`);
children.forEach(child => {
console.log(` └─ ${child.product.sku}: qty=${child.quantity}, $${child.totalPrice}`);
});
} else {
console.log(`Standalone: ${item.product.sku}: ${item.discount}% off, $${item.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-Bundle + Standalone",
"subscriptionStartDate": "2025-01-01",
"subscriptionEndDate": "2026-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"discount": 5,
"products": [
{
"productSku": "ENTERPRISE_SUITE",
"uom": "User/Month",
"quantity": 25,
"discount": 10,
"addOns": [
{ "productSku": "PREMIUM_SUPPORT", "uom": "User/Month", "quantity": 25 },
{ "productSku": "DATA_EXPORT", "uom": "User/Month", "quantity": 25 }
]
},
{
"productSku": "ANALYTICS_BUNDLE",
"uom": "User/Month",
"quantity": 15,
"addOns": [
{ "productSku": "ADVANCED_DASHBOARDS", "uom": "User/Month", "quantity": 15 }
]
},
{ "productSku": "NUE_ON_SALESFORCE", "uom": "User/Month", "quantity": 10 }
]
}'Result:
- ENTERPRISE_SUITE bundle: 10% product-level discount (overrides the 5% header discount). Its children inherit 10%.
- ANALYTICS_BUNDLE bundle: no product-level discount, so it receives the 5% header discount. Its children inherit 5%.
- NUE_ON_SALESFORCE standalone: no product-level discount, so it receives the 5% header discount.
The quote totalAmount is the sum of all line items across both bundles and the standalone product.
Response Structure for Nested Bundles
Nested bundles produce a multi-level childrenLineItems hierarchy:
{
"quote": {
"totalAmount": 12345.00
},
"quoteLineItems": [
{
"product": { "sku": "ENTERPRISE_SUITE" },
"quantity": 10,
"totalPrice": 8000.00,
"childrenLineItems": [
{
"product": { "sku": "ANALYTICS_BUNDLE" },
"quantity": 10,
"totalPrice": 3000.00,
"childrenLineItems": [
{
"product": { "sku": "ADVANCED_DASHBOARDS" },
"quantity": 10,
"totalPrice": 1200.00,
"childrenLineItems": []
}
]
},
{
"product": { "sku": "PREMIUM_SUPPORT" },
"quantity": 10,
"totalPrice": 500.00,
"childrenLineItems": []
}
]
}
]
}Key points:
- Each level of nesting adds another childrenLineItems array.
- Leaf products (non-bundles) have an empty childrenLineItems array.
- The parent's totalPrice reflects the combined pricing of the entire bundle hierarchy as configured in the catalog.
Summary
Scenario | What to Specify | Key Behavior |
|---|---|---|
Accept/reject optional add-ons | List desired add-ons in addOns | Unlisted optional add-ons are excluded |
Per-addon billing overrides | Set billingPeriod, billingTiming, subscriptionTerm on individual add-ons | Overrides apply to that add-on only |
Nested bundles | Add a bundle SKU inside another bundle's addOns with its own addOns | Resolved recursively; multi-level childrenLineItems |
Multi-level discounts | Set discount on each bundle level | Each level's discount propagates to its own children |
LinkToBundleQuantity scaling | Set parent quantity; child quantities multiply | Depends on catalog configuration |
Multiple bundles in one quote | List each bundle in products array | Each resolved independently |
Next Steps
- BundlesBundles -- Basic bundle usage and fundamentals
- DiscountsDiscounts -- Apply header, parent, and line-level discretionary discounts
- Subscription TermsSubscription Terms -- Configure term lengths, billing periods, and renewal settings
- Create Quote OverviewCreate Quote Overview -- Return to the full API reference