Create Draft Change Orders
This guide provides comprehensive instructions for creating draft change orders using the Nue Lifecycle Management API. Learn how to modify existing subscriptions through quantity updates, renewals, upgrades, downgrades, and other subscription lifecycle changes.
Prerequisites
Before you begin, ensure you have:
- A valid Nue API key with change order permissions
- Asset numbers (SUB-XXXXXX format) for existing subscriptions to modify
- Understanding of subscription change types and their implications
- Basic knowledge of REST APIs and JSON
- Customer timezone information for accurate date calculations
Authentication
All change order 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");Change Order Overview
Change orders modify existing subscriptions through the /change-orders endpoint. Unlike regular orders that create new subscriptions, change orders reference existing assets and apply specific change types.
Try it now: Create Change Order →
Important Limitations
⚠️ Draft change orders have specific limitations compared to regular draft orders:
- No description fields: Change orders do not support description fields at any level
- No price tag support: Change orders do not accept priceTagIds or priceTagCodes fields
- Asset-based structure: Changes are applied to existing assets, not new product configurations
- Limited customization: Only change-type specific parameters are supported
These limitations ensure change orders maintain data integrity when modifying existing subscriptions.
Change Types Reference
The Nue API supports 10 different change types, plus a products array for adding new products:
Change Type | Purpose | Required Fields | Use Case |
|---|---|---|---|
UpdateQuantity | Change subscription quantity | quantity, startDate | Scale subscription up/down |
UpdateTerm | Modify subscription term | term | Extend/shorten contract |
Renew | Renew expiring subscription | renewalTerm | Continue subscription |
CoTerm | Align with another subscription | coTermDate | Synchronize end dates |
Cancel | Terminate subscription | cancellationDate | End subscription early |
ConvertFreeTrial | Convert trial to paid | startDate, term | Monetize trial users |
Upgrade | Move to higher-tier product | targetPriceBookEntryId, startDate | Increase service level |
Downgrade | Move to lower-tier product | targetPriceBookEntryId, startDate | Reduce service level |
Swap | Switch to different product | targetPriceBookEntryId, startDate | Change product type |
Reconfigure | Add bundle add-ons | startDate, addOns | Self-service bundle management |
Additionally, the products array allows adding completely new products (cross-sell) alongside asset changes. See the New Product (Cross-Sell) section below.
Basic Change Order Structure
All change orders follow this base structure:
const changeOrderRequest = {
// Optional: Configuration options
options: {
previewInvoice: true, // Generate billing preview (default: true)
calculateTax: false // Calculate tax amounts (default: false)
},
// Optional: Timezone for date calculations
timeZone: "America/New_York",
// Optional: Custom fields at order level
"Ruby__Custom__c": "Value1",
"Department__c": "Engineering",
// Required: Array of asset changes
assetChanges: [
{
changeType: "UpdateQuantity", // Required: Change type
assetNumber: "SUB-000001", // Required: Asset to modify
// Additional fields based on change type...
}
],
// Optional: Array of new products to add (cross-sell)
products: [
{
priceBookEntryId: "01uQL000009Tsn7YAC", // Required: Product to add
quantity: 3, // Required: Quantity
subscriptionStartDate: "2025-02-15", // Required: Start date
subscriptionTerm: 12 // Required*: Term in months
// *Either subscriptionTerm or coTermAsset is required
}
]
};Custom Fields on Change Orders
Setting Custom Fields at Order Level
Custom fields can be set at the order level for change orders. These fields must be synced via the business objects page in Nue settings before they can be used.
Important Limitation: Custom fields are only supported at the order level for change orders. Unlike draft orders, change orders do not support custom fields on individual asset changes.
const changeOrderWithCustomFields = {
// Order-level custom fields
"Ruby__Custom__c": "Value1",
"Department__c": "Engineering",
"ProjectCode__c": "CHANGE-2025-001",
"ChangeReason__c": "Customer Request",
options: {
previewInvoice: true,
calculateTax: false
},
timeZone: "America/New_York",
assetChanges: [
{
changeType: "Swap",
assetNumber: "SUB-00001098",
targetPriceBookEntryId: "01uEc000009NkzmIAC",
startDate: "2025-08-18"
},
{
changeType: "Swap",
assetNumber: "SUB-00001099",
targetPriceBookEntryId: "01uEc000009NkzuIAC",
startDate: "2025-08-18"
}
]
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(changeOrderWithCustomFields)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('✅ Change order created with custom fields');
console.log(`Change Order ID: ${result.data.order.id}`);
}
})
.catch(error => console.log('Error:', error));Request Body Schema
Required Fields
Field | Type | Description | Example |
|---|---|---|---|
assetChanges | Array | List of asset changes to apply | See asset change schema below |
Optional Fields
Field | Type | Description | Example |
|---|---|---|---|
options.previewInvoice | Boolean | Generate billing preview (default: true) | true |
options.calculateTax | Boolean | Calculate tax amounts (default: false) | false |
timeZone | String | Customer timezone for calculations | "America/New_York" |
Asset Change Schema
Field | Type | Required | Description |
|---|---|---|---|
changeType | String | Yes | Type of change (see enum above) |
assetNumber | String | Yes | Asset identifier (SUB-XXXXXX format) |
Change Type Specific Fields
UpdateQuantity:
- quantity (Number, required) - Quantity delta (positive to add, negative to reduce)
- startDate (String, required) - Change start date
UpdateTerm:
- term (Number, required) - New term in months
Renew:
- renewalTerm (Number, required) - Renewal term in months
CoTerm:
- coTermDate (String, required) - Target coterm date
Cancel:
- cancellationDate (String, required) - Cancellation date
ConvertFreeTrial:
- startDate (String, required) - Conversion start date
- term (Number, required) - Subscription term in months
- overrideTrialEnd (Boolean, optional) - Override trial end behavior
Upgrade/Downgrade/Swap:
- targetPriceBookEntryId (String, required) - Target product ID
- startDate (String, required) - Change start date
Product Relationship Requirements
Upgrade, downgrade, and swap operations require a valid Product Relationship to be configured in the catalog between the source and target products.
How It Works
- Relationship Lookup - When processing a change order, the system validates that a ProductRelationship exists:
- Upgrades/Swaps: Looks for a relationship from source product → target product
- Downgrades: Looks for an "Upgrade" relationship from target product → source product (reversed direction)
- UOM Validation - The system validates Unit of Measure compatibility based on the relationship's baseUomRange and targetUomRange settings (e.g., "Any", "SameUOMOnly", or specific UOM lists).
- Same Price Swap - If samePriceSwap: true is configured on the ProductRelationship, the system automatically applies same-price swap behavior. This is derived from the catalog configuration.
- Price Tags - Any price tags (price dimensions) configured on the ProductRelationship are automatically applied during pricing.
Error Response
If no valid product relationship exists between the source and target products:
{
"status": 400,
"error": "Bad Request",
"code": "INVALID_PARAMETER",
"message": "No valid product relationship found for Upgrade from product SKU 'BASIC-PLAN' (UOM: User/Month) to product SKU 'PRO-PLAN' (UOM: User/Month)"
}Catalog Configuration Required
Before using upgrade, downgrade, or swap change types, ensure that:
- Product Relationships are defined between the source and target products
- The relationship type matches the intended change type (Upgrade, Downgrade, or Swap)
- UOM ranges are configured appropriately if products have multiple UOMs
- Price tags are attached to the relationship if special pricing should apply
Reconfigure:
- startDate (String, required) - Change start date
- addOns (Array, required) - Array of add-on configurations
- productOptionId (String, required) - Product option ID for bundle add-on
- reconfigureEffectiveDate (String, required) - When add-on change takes effect
- productOptionQuantity (Number, required) - Quantity of the add-on
⚠️ Important Restriction: Reconfigure change orders cannot be combined with other change types in a single transaction.
Products Array (New Product / Cross-Sell)
The products array is a separate top-level field alongside assetChanges used for adding new products to the account.
Field | Type | Required | Description |
|---|---|---|---|
priceBookEntryId | String | Yes | Price book entry ID identifying the product to add |
quantity | Number | Yes | Quantity to add (must be positive) |
subscriptionStartDate | String | Yes | Effective start date (YYYY-MM-DD) |
subscriptionTerm | Number | One of term/coTermAsset | Subscription term in months. Mutually exclusive with coTermAsset |
coTermAsset | String | One of term/coTermAsset | Existing subscription number (e.g. SUB-000200) to co-term with. Mutually exclusive with subscriptionTerm |
autoRenew | Boolean | No | Whether the subscription should auto-renew |
defaultRenewalTerm | Number | No | Renewal term in months when auto-renew is enabled |
billingTiming | String | No | Billing timing, e.g. In Advance, In Arrears |
billingPeriod | String | No | Billing frequency, e.g. Month, Year |
billCycleDay | String | No | Day of the billing cycle |
billCycleStartMonth | String | No | Starting month for annual billing cycles |
description | String | No | Custom description |
priceTagIds | Array | No | Price tag IDs to apply discounts |
priceTagCodes | Array | No | Price tag codes (alternative to IDs) |
customFields | Object | No | Custom field values to populate on the order product |
addOns | Array | No | Bundle add-ons to include (see add-on structure below) |
New Product Add-On Structure:
Field | Type | Required | Description |
|---|---|---|---|
productOptionId | String | Yes | Product option ID. For dynamic options, use the composite ID {productOptionId}_{priceBookEntryId} |
productOptionQuantity | Number | Yes | Quantity of the add-on |
subscriptionTerm | Number | No | Term in months if different from parent |
coTermAsset | String | No | Co-term with an existing subscription |
subscriptionStartDate | String | No | Start date if different from parent |
billingTiming | String | No | Billing timing override |
billingPeriod | String | No | Billing period override |
billCycleDay | String | No | Bill cycle day override |
billCycleStartMonth | String | No | Bill cycle start month override |
priceTagIds | Array | No | Price tag IDs |
priceTagCodes | Array | No | Price tag codes |
addOns | Array | No | Nested add-ons (recursive) |
customFields | Object | No | Custom field values |
Pricing fields are calculated automatically. Fields like netSalesPrice, salesPrice, subtotal, listTotal, totalPrice, totalAmount, tax, and discountAmount are calculated by the pricing engine and cannot be set on the request. The API will reject requests that include these fields.
Advanced Change Order Scenarios
Update Subscription Quantity
Scale an existing subscription up or down:
const myHeaders = new Headers();
myHeaders.append("nue-api-key", "YOUR_API_KEY_HERE");
myHeaders.append("Content-Type", "application/json");
const quantityChangeOrder = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "UpdateQuantity",
assetNumber: "SUB-000293",
quantity: 150,
startDate: "2025-02-01"
}
],
options: {
previewInvoice: true,
calculateTax: true
}
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(quantityChangeOrder)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('✅ Quantity change order created successfully');
console.log('Change Order ID:', result.data.order?.id);
}
})
.catch(error => console.log('Quantity change failed:', error));Update Subscription Term
Modify the term length of an existing subscription:
const myHeaders = new Headers();
myHeaders.append("nue-api-key", "YOUR_API_KEY_HERE");
myHeaders.append("Content-Type", "application/json");
const termUpdateOrder = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "UpdateTerm",
assetNumber: "SUB-000293",
term: 24
}
],
options: {
previewInvoice: true,
calculateTax: true
}
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(termUpdateOrder)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('✅ Term update order created successfully');
console.log('Change Order ID:', result.data.order?.id);
}
})
.catch(error => console.log('Term update failed:', error));Renew Subscription
Extend an expiring subscription:
const renewalOrder = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "Renew",
assetNumber: "SUB-000293",
renewalTerm: 12
}
],
options: {
previewInvoice: true,
calculateTax: true
}
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(renewalOrder)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('✅ Renewal order created successfully');
}
})
.catch(error => console.log('Renewal failed:', error));Convert Free Trial to Paid Subscription
Monetize trial users by converting to paid plans:
const trialConversionOrder = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "ConvertFreeTrial",
assetNumber: "SUB-000293",
startDate: "2025-03-21",
term: 12,
overrideTrialEnd: false
}
],
options: {
previewInvoice: true,
calculateTax: true
}
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(trialConversionOrder)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('✅ Trial conversion successful');
}
})
.catch(error => console.log('Trial conversion failed:', error));Upgrade Subscription
Move customer to a higher-tier product:
const upgradeOrder = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "Upgrade",
assetNumber: "SUB-000293",
targetPriceBookEntryId: "01u4x000005Yw0BAAS",
startDate: "2025-03-21"
}
],
options: {
previewInvoice: true,
calculateTax: true
}
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(upgradeOrder)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('✅ Upgrade order created successfully');
}
})
.catch(error => console.log('Upgrade failed:', error));Downgrade Subscription
Move customer to a lower-tier product:
const downgradeOrder = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "Downgrade",
assetNumber: "SUB-000293",
targetPriceBookEntryId: "01u4x000005Yw0CAAS",
startDate: "2025-03-21"
}
],
options: {
previewInvoice: true,
calculateTax: false
}
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(downgradeOrder)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('✅ Downgrade order created successfully');
}
})
.catch(error => console.log('Downgrade failed:', error));Co-Terminate Multiple Subscriptions
Align subscription end dates for easier management:
const coTermOrder = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "CoTerm",
assetNumber: "SUB-000293",
coTermDate: "2025-07-20"
}
],
options: {
previewInvoice: true,
calculateTax: true
}
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(coTermOrder)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('✅ Co-termination order created');
}
})
.catch(error => console.log('Co-termination failed:', error));Cancel Subscription
Terminate a subscription with proper notice:
const cancellationOrder = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "Cancel",
assetNumber: "SUB-000293",
cancellationDate: "2025-03-21"
}
],
options: {
previewInvoice: true,
calculateTax: true
}
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(cancellationOrder)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('✅ Cancellation order created');
}
})
.catch(error => console.log('Cancellation failed:', error));Reconfigure Bundle Subscription
Add product options (add-ons) from existing bundle subscriptions:
const myHeaders = new Headers();
myHeaders.append("nue-api-key", "YOUR_API_KEY_HERE");
myHeaders.append("Content-Type", "application/json");
const reconfigureOrder = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "Reconfigure",
assetNumber: "SUB-000050",
startDate: "2025-08-26",
addOns: [
{
productOptionId: "a0g7z000007gUXuAAM",
reconfigureEffectiveDate: "2025-08-26",
productOptionQuantity: 2
}
]
}
],
options: {
previewInvoice: true,
calculateTax: true
}
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(reconfigureOrder)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('✅ Bundle reconfiguration successful');
console.log('Change Order ID:', result.data.order?.id);
}
})
.catch(error => console.log('Bundle reconfiguration failed:', error));Product Swap
Switch to a different product at the same level:
const swapOrder = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "Swap",
assetNumber: "SUB-000293",
targetPriceBookEntryId: "01u4x000005Yw0DAAS",
startDate: "2025-03-21"
}
],
options: {
previewInvoice: true,
calculateTax: true
}
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(swapOrder)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('✅ Product swap order created');
}
})
.catch(error => console.log('Product swap failed:', error));New Product (Cross-Sell)
Add a completely new product as part of a change order. New products are specified in a separate products array alongside the assetChanges array. Supports standalone products and full bundle hierarchies with configured and dynamic add-ons.
Important: The products array must be accompanied by at least one entry in the assetChanges array (e.g., Cancel, UpdateQuantity, Renew). You cannot submit a change order with only new products.
Basic New Product
Cancel an existing subscription and add a new product with a 12-month term:
const newProductOrder = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "Cancel",
assetNumber: "SUB-000192",
cancellationDate: "2025-02-15"
}
],
products: [
{
priceBookEntryId: "01uQL000009Tsn7YAC",
quantity: 3,
subscriptionStartDate: "2025-02-15",
subscriptionTerm: 12
}
],
options: {
previewInvoice: true,
calculateTax: true
}
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(newProductOrder)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('New product change order created successfully');
console.log('Change Order ID:', result.data.order?.id);
}
})
.catch(error => console.log('New product change order failed:', error));New Product with Bundle Add-Ons
Add a new bundle product with configured and dynamic add-ons:
const bundleNewProductOrder = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "Cancel",
assetNumber: "SUB-000192",
cancellationDate: "2025-02-15"
}
],
products: [
{
priceBookEntryId: "01uQL000009Tsn7YAC",
quantity: 3,
subscriptionStartDate: "2025-02-15",
subscriptionTerm: 12,
addOns: [
{
// Configured add-on
productOptionId: "a0jQL00000JxnAVYAZ",
productOptionQuantity: 3
},
{
// Dynamic add-on: use composite ID {productOptionId}_{priceBookEntryId}
productOptionId: "a0jQL00000JxnAVYAZ_01uQL00000CXUGcYAP",
productOptionQuantity: 10
}
]
}
],
options: {
previewInvoice: true,
calculateTax: true
}
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(bundleNewProductOrder)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('Bundle new product change order created');
}
})
.catch(error => console.log('Bundle new product failed:', error));New Product Co-Termed with Existing Subscription
Align the new product's end date to an existing subscription instead of specifying a term:
const coTermNewProductOrder = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "Cancel",
assetNumber: "SUB-000192",
cancellationDate: "2025-02-15"
}
],
products: [
{
priceBookEntryId: "01uQL000009Tsn7YAC",
quantity: 5,
subscriptionStartDate: "2025-02-15",
// Co-terminate with existing subscription instead of fixed term
coTermAsset: "SUB-000200"
}
],
options: {
previewInvoice: true,
calculateTax: true
}
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(coTermNewProductOrder)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('Co-termed new product change order created');
}
})
.catch(error => console.log('Co-termed new product failed:', error));Multiple Changes in Single Order
Apply multiple changes across different subscriptions:
const multiChangeOrder = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "UpdateQuantity",
assetNumber: "SUB-000001",
quantity: 200,
startDate: "2025-07-01"
},
{
changeType: "Upgrade",
assetNumber: "SUB-000002",
targetPriceBookEntryId: "01uQL000009Tsn5YAC",
startDate: "2025-07-01"
},
{
changeType: "Cancel",
assetNumber: "SUB-000003",
cancellationDate: "2025-07-31"
}
],
options: {
previewInvoice: true,
calculateTax: true
}
};
fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(multiChangeOrder)
})
.then(response => response.json())
.then(result => {
if (result.status === 'SUCCESS') {
console.log('✅ Multiple changes applied successfully');
}
})
.catch(error => console.log('Multiple changes failed:', error));Change Order Service Class
Implement a comprehensive service for change order management:
class ChangeOrderService {
constructor(apiKey) {
this.apiKey = apiKey;
this.headers = new Headers();
this.headers.append("nue-api-key", apiKey);
this.headers.append("Content-Type", "application/json");
}
async createChangeOrder(changeOrderInput) {
try {
// Step 1: Validate change order data
const validatedOrder = this.validateChangeOrderData(changeOrderInput);
// Step 2: Apply business rules and defaults
const enrichedOrder = this.enrichChangeOrderData(validatedOrder);
// Step 3: Submit change order
const changeOrder = await this.submitChangeOrder(enrichedOrder);
// Step 4: Process results
const processedResult = this.processChangeOrderResult(changeOrder);
return {
success: true,
order: processedResult.order,
assets: processedResult.assets,
previews: processedResult.previews,
warnings: processedResult.warnings,
summary: this.generateChangeOrderSummary(processedResult)
};
} catch (error) {
console.error('Change order creation failed:', error);
return {
success: false,
error: error.message,
orderInput: changeOrderInput
};
}
}
validateChangeOrderData(orderInput) {
const errors = [];
// Required field validation
if (!orderInput.timeZone) {
errors.push('Time zone is required for change orders');
}
if (!orderInput.assetChanges || orderInput.assetChanges.length === 0) {
errors.push('At least one asset change is required');
}
// Asset change validation
orderInput.assetChanges?.forEach((change, index) => {
const changePath = `Asset Change ${index + 1}`;
if (!change.changeType) {
errors.push(`${changePath}: Change type is required`);
}
if (!change.assetNumber) {
errors.push(`${changePath}: Asset number is required`);
}
// Validate asset number format
if (change.assetNumber && !change.assetNumber.match(/^SUB-\d{6}$/)) {
errors.push(`${changePath}: Asset number must be in format SUB-XXXXXX`);
}
// Change type specific validation
this.validateChangeTypeRequirements(change, changePath, errors);
});
if (errors.length > 0) {
throw new Error(`Change order validation failed:\n${errors.join('\n')}`);
}
return orderInput;
}
validateChangeTypeRequirements(change, changePath, errors) {
const changeType = change.changeType;
switch (changeType) {
case 'UpdateQuantity':
if (change.quantity === undefined || change.quantity === null || change.quantity === 0) {
errors.push(`${changePath}: Quantity (delta) is required and cannot be 0 for UpdateQuantity`);
}
if (!change.startDate) {
errors.push(`${changePath}: Start date is required for UpdateQuantity`);
}
break;
case 'UpdateTerm':
if (!change.term || change.term <= 0) {
errors.push(`${changePath}: Term must be greater than 0 for UpdateTerm`);
}
break;
case 'Renew':
if (!change.renewalTerm || change.renewalTerm <= 0) {
errors.push(`${changePath}: Renewal term must be greater than 0 for Renew`);
}
break;
case 'CoTerm':
if (!change.coTermDate) {
errors.push(`${changePath}: Co-term date is required for CoTerm`);
}
break;
case 'Cancel':
if (!change.cancellationDate) {
errors.push(`${changePath}: Cancellation date is required for Cancel`);
}
break;
case 'ConvertFreeTrial':
if (!change.startDate) {
errors.push(`${changePath}: Start date is required for ConvertFreeTrial`);
}
if (!change.term || change.term <= 0) {
errors.push(`${changePath}: Term must be greater than 0 for ConvertFreeTrial`);
}
break;
case 'Upgrade':
case 'Downgrade':
case 'Swap':
if (!change.targetPriceBookEntryId) {
errors.push(`${changePath}: Target price book entry ID is required for ${changeType}`);
}
if (!change.startDate) {
errors.push(`${changePath}: Start date is required for ${changeType}`);
}
break;
case 'Reconfigure':
if (!change.startDate) {
errors.push(`${changePath}: Start date is required for Reconfigure`);
}
if (!change.addOns || !Array.isArray(change.addOns) || change.addOns.length === 0) {
errors.push(`${changePath}: At least one add-on is required for Reconfigure`);
} else {
change.addOns.forEach((addOn, addOnIndex) => {
const addOnPath = `${changePath} Add-on ${addOnIndex + 1}`;
if (!addOn.productOptionId) {
errors.push(`${addOnPath}: Product option ID is required`);
}
if (!addOn.reconfigureEffectiveDate) {
errors.push(`${addOnPath}: Reconfigure effective date is required`);
}
if (!addOn.productOptionQuantity || addOn.productOptionQuantity <= 0) {
errors.push(`${addOnPath}: Product option quantity must be greater than 0`);
}
});
}
break;
default:
errors.push(`${changePath}: Invalid change type: ${changeType}`);
}
}
enrichChangeOrderData(orderInput) {
return {
...orderInput,
// Set default options if not provided
options: {
previewInvoice: true,
calculateTax: false,
...orderInput.options
}
};
}
async submitChangeOrder(orderData) {
const response = await fetch('https://api.nue.io/change-orders', {
method: 'POST',
headers: this.headers,
body: JSON.stringify(orderData)
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`API Error ${response.status}: ${errorText}`);
}
const result = await response.json();
if (result.status !== 'SUCCESS') {
throw new Error(`Change order creation failed: ${result.message || 'Unknown error'}`);
}
return result;
}
processChangeOrderResult(apiResult) {
return {
order: apiResult.data.order,
assets: apiResult.data.assets || [],
previews: apiResult.data.previews || [],
warnings: apiResult.data.warnings || []
};
}
generateChangeOrderSummary(result) {
const order = result.order;
const assets = result.assets;
// Group changes by type
const changeTypes = assets.reduce((acc, asset) => {
const type = asset.changeType || 'Unknown';
acc[type] = (acc[type] || 0) + 1;
return acc;
}, {});
// Sum the prorated invoice amounts due now (before tax)
const totalBillingImpact = result.previews.reduce((sum, preview) => {
return sum + (preview.priceSummary?.todayChargePrice || 0);
}, 0);
return {
orderId: order.id,
effectiveDate: order.effectiveDate,
totalChanges: assets.length,
changeTypes: changeTypes,
billingImpact: totalBillingImpact,
hasWarnings: result.warnings.length > 0,
warningCount: result.warnings.length
};
}
}
// Usage example
const changeOrderService = new ChangeOrderService("YOUR_API_KEY_HERE");
const subscriptionUpgrade = {
timeZone: "America/New_York",
assetChanges: [
{
changeType: "Upgrade",
assetNumber: "SUB-000001",
targetPriceBookEntryId: "01uQL000009Tsn6YAC",
startDate: "2025-08-01"
}
]
};
changeOrderService.createChangeOrder(subscriptionUpgrade)
.then(result => {
if (result.success) {
console.log('\n🎉 Change order created successfully!');
console.log(`Order ID: ${result.summary.orderId}`);
console.log(`Changes Applied: ${result.summary.totalChanges}`);
console.log(`Next invoice (before tax): $${result.summary.billingImpact}`);
if (result.summary.hasWarnings) {
console.log(`\n⚠️ ${result.summary.warningCount} warnings to review`);
}
} else {
console.error('❌ Change order failed:', result.error);
}
});Request Body Schema
Required Fields
Field | Type | Description | Example |
|---|---|---|---|
assetChanges | Array | List of asset changes to apply | See asset change schema below |
Asset Change Schema
Field | Type | Required | Description |
|---|---|---|---|
changeType | String | Yes | Type of change (see enum above) |
assetNumber | String | Yes | Asset identifier (SUB-XXXXXX format) |
Change Type Specific Fields
UpdateQuantity:
- quantity (Number, required) - Quantity delta (positive to add, negative to reduce)
- startDate (String, required) - Change start date
UpdateTerm:
- term (Number, required) - New term in months
Renew:
- renewalTerm (Number, required) - Renewal term in months
Upgrade/Downgrade/Swap:
- targetPriceBookEntryId (String, required) - Target product ID
- startDate (String, required) - Change start date
⚠️ Important: Upgrade, downgrade, and swap operations do not support ramp tags on price book entries. Use products with standard pricing for these change types.
Reconfigure:
- startDate (String, required) - Change start date
- addOns (Array, required) - Array of add-on configurations
- productOptionId (String, required) - Product option ID for bundle add-on
- reconfigureEffectiveDate (String, required) - When add-on change takes effect
- productOptionQuantity (Number, required) - Quantity of the add-on
Reading the Billing Preview
When previewInvoice is true (the default), each entry in data.previews includes a priceSummary with the billing impact of that change:
Field | What it means |
|---|---|
totalPrice | Value of the change over the rest of the subscription term, before tax. |
totalAmount | Same as totalPrice at the preview level. Order-level tax, when calculated, is on data.order.tax and data.order.totalAmount. |
todayChargePrice | Amount of the invoice generated for the change today, before tax. This is the prorated amount from the change date to the next bill date. |
todayChargeAmount | The same invoice including tax. Invoice previews do not call the tax engine, so this currently equals todayChargePrice. |
nextBillingDate | Date of the next regular invoice for the subscription. |
nextBillingPeriodPrice / nextBillingPeriodAmount | Amount of the following billing period. This can currently be returned as 0; do not rely on it for display. |
The size of todayChargePrice depends on the subscription's billing settings, not on the size of the change. The same change produces a small prorated amount on a monthly plan and a much larger one on an annual plan. On a plan billed in arrears there may be no invoice today, so the value is 0.
Example: adding one seat mid-month on a monthly plan
A subscription at $30 per seat per month, billed monthly in advance, halfway through its current billing period with 11.5 months left in the term. Adding one seat with calculateTax: true (8% tax) returns:
- data.order.totalPrice: 345.00 (the extra seat for the rest of the term)
- data.order.tax: 27.60
- data.order.totalAmount: 372.60
- priceSummary.todayChargePrice: 15.00 (the prorated invoice for the rest of the current month, before tax)
- priceSummary.nextBillingDate: the next bill date
The buyer's card is charged the prorated invoice plus tax (16.20 here), not 372.60 and not 15.00.
Response Structure
Success Response (201 Created)
{
"status": "SUCCESS",
"data": {
"order": {
"id": "3f6c2a1e-8b4d-4e2a-9c1f-5d7e8a9b0c12",
"orderNumber": "O-00001234",
"status": "Draft",
"orderStartDate": "2026-09-15",
"totalPrice": 345.00,
"discountAmount": 0,
"tax": 27.60,
"totalAmountWithoutTax": 345.00,
"totalAmount": 372.60
},
"orderProducts": [],
"previews": [
{
"assetNumber": "SUB-000001",
"previewResult": {
"assetNumber": "SUB-000001",
"startDate": "2026-09-15",
"billingPrice": {
"todayChargePrice": 15.00,
"todayChargeAmount": 15.00,
"nextBillingDate": "2026-10-01"
}
},
"priceSummary": {
"totalPrice": 345.00,
"totalAmount": 345.00,
"todayChargePrice": 15.00,
"todayChargeAmount": 15.00,
"nextBillingDate": "2026-10-01"
}
}
],
"subscriptions": [],
"assets": [],
"entitlements": []
}
}orderProducts and previewResult are trimmed for readability. The full response includes the order product records and additional previewResult fields (changes, endDate, orderPrice, quantity, term). subscriptions, assets and entitlements are empty on a draft change order; they are provisioned when the order is activated.
Error Handling
Common Change Order Errors
Error Code | Description | Resolution |
|---|---|---|
ASSET_NOT_FOUND | Asset number doesn't exist | Verify asset number format and existence |
INVALID_CHANGE_TYPE | Change type not supported for asset | Check asset type compatibility |
CHANGE_NOT_ALLOWED | Change violates business rules | Review asset status and constraints |
INSUFFICIENT_PERMISSIONS | User lacks change permissions | Verify API key permissions |
INVALID_TARGET_PRODUCT | Target product not available | Check product catalog availability |
Handle Change-Specific Errors
try {
const result = await changeOrderService.createChangeOrder(changeOrder);
} catch (error) {
if (error.message.includes('ASSET_NOT_FOUND')) {
console.error('Asset Error: Subscription not found - check asset number');
} else if (error.message.includes('CHANGE_NOT_ALLOWED')) {
console.error('Business Rule Error: Change not permitted for this subscription');
} else if (error.message.includes('INVALID_TARGET_PRODUCT')) {
console.error('Product Error: Target product not available for this customer');
}
}Best Practices
Change Order Design
- Validate asset existence before creating change orders
- Use appropriate change types based on business intent
- Consider billing impact with preview invoices
Timing and Scheduling
- Plan effective dates considering billing cycles
- Coordinate multiple changes for optimal customer experience
- Respect minimum notice periods for cancellations
- Align with customer timezone for accurate calculations
Business Operations
- Implement approval workflows for significant changes
- Monitor change order patterns for customer health insights
- Automate common changes like renewals and quantity adjustments
- Maintain change history for customer relationship management
This comprehensive guide enables you to efficiently manage subscription lifecycles through the Nue Change Orders API, supporting all types of subscription modifications from simple quantity updates to complex product transitions.