Create Quote Overview
Create Quote Overview
The Nue CPQ Quote API provides REST endpoints for programmatically creating fully-priced quotes with the same pricing fidelity as the Nue UI. Use the CPQ REST API when you need to build quotes outside of the Nue application -- for example, from a custom storefront, a partner portal, an AI agent, or any backend integration that requires real-time pricing.
If you are working with post-quote lifecycle operations such as order activation, subscription management, or asset tracking, use the Lifecycle Manager API instead. The CPQ REST API is focused on quote creation and pricing.
Endpoints
Endpoint | Method | Description |
|---|---|---|
/cpq/quotes | POST | Create and persist a quote (commit mode) |
/cpq/quotes:preview | POST | Preview a quote without persisting (dry-run mode) |
/cpq/quotes/{quoteId} | GET | Retrieve a quote's JSON representation |
How It Works
When you submit a Create Quote request, the pricing engine performs the following steps:
- Product Resolution -- Finds products by SKU or name, resolves the correct Price Book Entry based on UOM, currency, and pricing attributes
- Bundle Expansion -- Automatically includes bundled items, auto-populates required add-ons, resolves dynamic product options
- Pricing Engine Execution -- Calculates the full pricing formula chain: ListPrice x Quantity x Term = ListTotal, then applies system discounts and discretionary discounts to arrive at the final total price
- Discount Propagation -- Applies header-level discounts to all undiscounted lines, propagates parent-level discounts to children, respects explicit overrides
- Price Tag Evaluation -- Evaluates tiered, volume, and ramp price/discount tags; applies auto-attached tags from product configuration
- Validation -- Enforces business rules and returns structured error messages or warnings
Key Concepts
Preview vs Commit
The CPQ API supports two modes with identical request bodies:
Mode | Endpoint | Persists to Database | quote.id in Response |
|---|---|---|---|
Preview (dry-run) | POST /cpq/quotes:preview | No | null |
Commit | POST /cpq/quotes | Yes | Returns a UUID |
Preview mode runs the full pricing engine but writes nothing to the database. Use it for pricing previews, what-if scenarios, and validation. Commit mode runs the same engine and persists the result. See Preview vs Commit for detailed examples.
Product Identification
Products are identified by SKU and Unit of Measure (UOM). The SKU resolves the product, and the UOM resolves the specific Price Book Entry that determines the list price.
- SKU + UOM (recommended): { "productSku": "NUE_PLATFORM", "uom": "User/Month" }
- Name + UOM: { "productName": "Nue Platform", "uom": "User/Month" }
- Price Book Entry ID: { "priceBookEntryId": "PBE-001" }
When both productSku and productName are provided, the SKU is used for product resolution.
Pricing Formula
The pricing engine applies the following formula for recurring products:
ListTotal = ListPrice x Quantity x Term
SubTotal = ListTotal - SystemDiscount
Total = SubTotal - DiscretionaryDiscountOne-time products use ListTotal = ListPrice x Quantity (no term multiplier).
Prerequisites
Before creating quotes, ensure you have:
- A valid Nue API key with quote creation permissions
- An Opportunity ID to associate with the quote
- Products published to your Price Book with Price Book Entries
- Knowledge of product SKUs and their Units of Measure (UOM)
Authentication
All CPQ operations 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");Minimal Example -- Preview a Quote
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": "Q1 2026 Platform License",
"subscriptionStartDate": "2026-01-01",
"subscriptionEndDate": "2027-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"products": [
{ "productSku": "NUE_PLATFORM", "uom": "User/Month", "quantity": 10 }
]
}'const myHeaders = new Headers();
myHeaders.append("nue-api-key", "YOUR_API_KEY_HERE");
myHeaders.append("Content-Type", "application/json");
const quoteData = {
opportunityId: "006xx000001abc123",
name: "Q1 2026 Platform License",
subscriptionStartDate: "2026-01-01",
subscriptionEndDate: "2027-01-01",
subscriptionTermDimension: "Month",
subscriptionTerm: 12,
products: [
{ productSku: "NUE_PLATFORM", 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('Quote preview:', result);
console.log(`Total Amount: $${result.quote.totalAmount}`);
console.log(`Line items: ${result.quoteLineItems.length}`);
// the quote object has no id in preview mode
})
.catch(error => console.log('Error:', error));Minimal Example -- Commit a Quote
To persist the quote, use the /cpq/quotes endpoint (without :preview). The request body is identical.
curl -X POST 'https://api.nue.io/cpq/quotes' \
-H 'nue-api-key: YOUR_API_KEY_HERE' \
-H 'Content-Type: application/json' \
-d '{
"opportunityId": "006xx000001abc123",
"name": "Q1 2026 Platform License",
"subscriptionStartDate": "2026-01-01",
"subscriptionEndDate": "2027-01-01",
"subscriptionTermDimension": "Month",
"subscriptionTerm": 12,
"products": [
{ "productSku": "NUE_PLATFORM", "uom": "User/Month", "quantity": 10 }
]
}'fetch('https://api.nue.io/cpq/quotes', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(quoteData)
})
.then(response => response.json())
.then(result => {
console.log(`Quote ID: ${result.quote.id}`); // present -- quote is persisted
console.log(`Total Amount: $${result.quote.totalAmount}`);
})
.catch(error => console.log('Error:', error));Request Structure
The Create Quote request body has three main sections:
Quote Header Fields
Field | Type | Required | Description |
|---|---|---|---|
opportunityId | string | Yes | The Opportunity ID to associate with the quote |
name | string | Yes | The quote name |
subscriptionStartDate | date | Yes | Start date (YYYY-MM-DD) |
subscriptionEndDate | date | No | End date; calculated from term if not provided |
subscriptionTerm | number | Yes | Term length (e.g., 12 for 12 months) |
subscriptionTermDimension | string | No | "Month" (default) or "Year" |
priceBookId | string | No | Price Book ID; uses system default if omitted |
discount | number | No | Header-level discount percentage (0-100) |
discountAmount | number | No | Header-level discount as fixed amount |
billingPeriod | string | No | "Monthly", "Quarterly", "Semi-Annually", "Annually" |
billingTiming | string | No | "In Advance" or "In Arrears" |
autoRenew | boolean | No | Auto-renew setting for all products |
currencyIsoCode | string | No | ISO currency code for multi-currency orgs |
priceTags | array | No | Quote-level price/discount tags |
Product Input Fields
Field | Type | Required | Description |
|---|---|---|---|
productSku | string | Yes* | Product SKU (either SKU or name required) |
productName | string | Yes* | Product name (alternative to SKU) |
uom | string | Conditional | Unit of Measure; required if priceBookEntryId is not provided |
quantity | number | No | Product quantity (defaults vary by product) |
startDate | date | No | Product start date; defaults to quote start date |
endDate | date | No | Product end date |
subscriptionTerm | number | No | Per-product term override |
discount | number | No | Line-level discount percentage (0-100) |
discountAmount | number | No | Line-level discount amount |
billingPeriod | string | No | Per-product billing period override |
billingTiming | string | No | Per-product billing timing override |
autoRenew | boolean | No | Per-product auto-renew override |
evergreen | boolean | No | Whether the product is evergreen |
renewalTerm | number | No | Renewal term override |
priceTags | array | No | Line-level price/discount tags |
addOns | array | No | Add-on products for bundles (recursive ProductInput) |
customPricingAttributes | array | No | Custom attributes for multi-attribute PBE resolution |
customFields | object | No | Custom field values applied to the line item, keyed by Salesforce API name (for example a Line Attribute's mapped field) |
rampValues | array | No | Per-ramp Line Attribute values for a ramped line. Each entry targets one ramp line by rampLineIndex and sets custom fields on that ramp line. See the Per-ramp Line Attribute values section below |
Price Tag Input
Field | Type | Required | Description |
|---|---|---|---|
code | string | One of code/id | The price tag code |
id | string | One of code/id | The price tag ID |
Per-ramp Line Attribute values
On a ramped (multi-year) line, a custom field mapped as a Line Attribute can carry a different value for each ramp line. Pass these per-ramp values with the rampValues array on the product. Omit it and every ramp line inherits the value on the line, then the Pricebook Entry default.
Field | Type | Required | Description |
|---|---|---|---|
rampLineIndex | number | Yes | The ramp line to target, 1-based in schedule order (the first ramp line is 1) |
startDate | date | No | The ramp line's start date, accepted as a readable alias for rampLineIndex. Ramp line dates are derived by the pricing engine, so this is not the addressing key; when both are given they must agree |
fieldValues | object | Yes | Field API name to value, applied to that ramp line only (for example { "Overage_Rate__c": 3.00 }) |
Each ramp line resolves its value from the first available of: an explicit rampValues entry, the value set on the line, the product's Pricebook Entry default, then empty. A rampLineIndex that is out of range, not a positive integer, or duplicated across entries is rejected with INVALID_RAMP_LINE.
Example -- different overage rate each year
{
"opportunityId": "006xx000001abc123",
"name": "3-Year Usage Ramp",
"subscriptionStartDate": "2026-01-01",
"subscriptionTermDimension": "Year",
"subscriptionTerm": 3,
"products": [
{
"productSku": "API_USAGE",
"uom": "Call/Month",
"quantity": 100,
"priceTags": [ { "code": "3-YEAR-RAMP" } ],
"rampValues": [
{ "rampLineIndex": 1, "fieldValues": { "Overage_Rate__c": 3.00 } },
{ "rampLineIndex": 2, "fieldValues": { "Overage_Rate__c": 3.50 } },
{ "rampLineIndex": 3, "fieldValues": { "Overage_Rate__c": 4.00 } }
]
}
]
}Response Structure
The quote and its line items are returned at the top level of the response body:
{
"quote": { ... },
"quoteLineItems": [ ... ]
}- quote -- The quote header object with all calculated pricing fields
- quoteLineItems -- Array of root line items; bundle children are nested in childrenLineItems on their parent line, not flattened into this array
In preview mode the quote object has no id, and each line item carries a temporary id of the form temp_<uuid>. In commit mode quote.id holds the persisted quote ID and line items carry their real IDs.
On failure, the response is an HTTP error status with this body:
{
"status": 400,
"error": "Failed to create quote",
"message": "The field 'accountId' is required.",
"errorDetails": [
{
"errorCode": "QUOTE_ACCOUNTID_REQUIRED",
"message": "The field 'accountId' is required."
}
]
}Here status is the numeric HTTP status and the per-error list is errorDetails. Basic request validation failures, such as a missing name or priceBookId, return "error": "Bad Request" with no errorDetails array.
Guide Articles
Article | Description |
|---|---|
Understand the difference between dry-run and commit modes | |
Create quotes with simple standalone products | |
Work with bundle products and add-ons | |
Apply header, parent, and line-level discounts | |
Configure quantity-based pricing tiers |