---
name: nue-rest-update-quote-order
description: Specialist in the Nue REST API for updating existing quotes and orders (POST /cpq/quotes/{quoteId}:update and POST /cpq/orders/{orderId}:update). Use when generating curl commands or REST payloads that change header fields, edit or delete lines, add products or bundle options, reconfigure a bundle, or swap price tags on a record that already exists — with pricing and tax recalculated.
user-invocable: false
---

# Update Quote & Update Order — REST API

Given a business scenario describing a change to an **existing** quote or Draft order, produce a complete REST API call that applies it via the Nue Platform API. Validate action names, payload keys, field names, and enum values against the rules below.

> **Note**: This skill covers **modifying an existing record**. For creating new quotes and orders, see the [Create Quote & Order REST Skill](nue-rest-create-quote-order.md). For changes to **activated** subscriptions (renewals, expansions, cancellations, upgrades), see the [Change Order REST Skill](nue-rest-change-order.md).

---

## The one rule that governs everything: action-oriented, not payload-oriented

You do **not** send the record you want. You send an ordered list of **actions** naming what to do.

- Fields you omit are **untouched**. Every action is a true patch.
- Lines you omit are **untouched**. A line is deleted only when you name its id in `deleteLineItems`. The server never infers a deletion from a shorter line list.
- There is **no `lineSet`** and no whole-tree diff. Line changes are always delta actions.
- There is **no `reconfigureBundle` action**. Reconfiguring a bundle means adding, deleting, or re-quantifying its option lines.

If you catch yourself building "the full desired quote" and sending it, stop — that is the wrong shape for this API.

---

## Endpoints

| Operation | Endpoint | Description |
|-----------|----------|-------------|
| Update Quote | `POST /cpq/quotes/{quoteId}:update` | Applies actions to an existing quote |
| Update Order | `POST /cpq/orders/{orderId}:update` | Applies actions to an existing **Draft** order |

There is **no `:preview` variant**. Preview is `isCommit: false` on the same endpoint.

**Base URLs:**
- Production: `https://api.nue.io`
- QA3: `https://api.qa3.nue.io`
- Perftest: `https://api.perftest.nue.io`

**Authentication:**
```
nue-api-key: YOUR_API_KEY
Content-Type: application/json
```

---

## Request envelope

```json
{
  "isCommit": true,
  "recalc": "AUTO",
  "expectedLastModified": "2026-08-07T18:22:41.000Z",
  "actions": [
    { "action": "<actionName>", "payload": { } }
  ]
}
```

| Field | Type | Required | Default | Notes |
|-------|------|----------|---------|-------|
| `isCommit` | boolean | No | `true` | `false` = preview: full cascade in memory, nothing persisted. Must be a real boolean, not a string |
| `recalc` | string | No | `AUTO` | `AUTO` \| `PRICING_ONLY` \| `TAX_ONLY` \| `NONE`. **Case-sensitive** |
| `expectedLastModified` | string | No | — | Optimistic-concurrency guard. Mismatch → `QUOTE_MODIFIED` / `ORDER_MODIFIED`, nothing persisted |
| `actions` | array | **Yes** | — | Non-empty |

The record id is a **path parameter**. Do **not** put `quoteId` / `orderId` in the body — the REST body has no such field. (The Apex global method does.)

---

## The six actions

| Action | Payload |
|--------|---------|
| `updateHeaderFields` | `{ "fieldValues": { "<nueFieldName>": value } }` |
| `updateLineItems` | `{ "lineItems": [ { "lineItemId": "...", "fieldValues": { } } ] }` |
| `deleteLineItems` | `{ "lineItemIds": [ "...", "..." ] }` |
| `addLineItems` | `{ "products": [ ProductInput ] }` |
| `replacePriceTags` | `{ "priceTags": [ { "<oldIdOrCode>": "<newIdOrCode>" } ], "target": [ ], "includeChildren": true }` |
| `removePriceTags` | `{ "priceTags": [ "<idOrCode>" ], "target": [ ], "includeChildren": true }` |

`repriceQuote` / `repriceOrder` are internal and rejected. An unrecognized name returns `400 Unknown action: <name>`.

### Ordering and conflicts

- **Send actions in any order.** The server normalizes to: header fields → deletes → price-tag actions → line updates → adds. Identical result either way.
- `updateHeaderFields` may appear **at most once** per request → else `CONFLICTING_ACTIONS`.
- Each line may be targeted by **at most one** action → else `CONFLICTING_ACTIONS`.
- A header pricing change and a line pricing change may coexist. The header discount is distributed across lines this request did not explicitly price.

---

## Field naming — the most common mistake

`fieldValues` keys are validated against the **Nue** object describe:

| Action target | Describe | Use |
|---|---|---|
| Quote header | `Quote` | Nue camelCase names |
| Quote line | `QuoteLineItem` | Nue camelCase names |
| Order header | `Order` | Nue camelCase names |
| Order line | `OrderProduct` | Nue camelCase names |

**Correct:** `quantity`, `discount`, `discountAmount`, `netSalesPrice`, `subscriptionTerm`, `subscriptionStartDate`, `subscriptionEndDate`, `paymentTerm`, `billingPeriod`, `billCycleDay`, `autoRenew`, `evergreen`, `includedUnits`, `name`, `description`, `poNumber`.

**Wrong over REST:** `Quantity`, `Discount__c`, `SubscriptionTerm__c`, `PaymentTerm__c` — Salesforce API names are for the **Apex global method only**. Over REST they fail fast:

```
400  Invalid field for quote: PaymentTerm__c
400  Invalid field for quote line item: Quantity__c
400  Invalid field for order: PaymentTerm__c
400  Invalid field for order product: Quantity__c
```

**Custom fields are the exception** — they use their Salesforce API name in both places: `Ruby__CostCenter__c`, `Ruby__Region__c`.

---

## Recalc modes

| Mode | Runs | Use when |
|------|------|----------|
| `AUTO` | Re-prices and/or re-taxes based on which fields changed | Default. Almost always right |
| `PRICING_ONLY` | Re-prices + header roll-ups, skips tax | Tax disabled or owned downstream; fast price-only preview |
| `TAX_ONLY` | Re-runs tax + tax roll-ups, pricing untouched | Address / tax-code / exemption change |
| `NONE` | Persists, recomputes nothing | Admin-only fields. **Leaves stale totals** |

Invalid value → `RECALC_INVALID`. `auto` is not `AUTO`.

**Under `AUTO`, what triggers a re-price:**

- Header: `discount`, `discountAmount`, `totalPrice`, `partnerPayoutPercentage`, `partnerPayoutAmount`, `subscriptionStartDate`, `subscriptionEndDate`, `subscriptionTerm`, `subscriptionTermDimension`
- Header, tax only: `shippingStreet/City/State/PostalCode/Country`, `isShippingAddressSameAsBilling`, the five `taxation*` fields, `taxationAccount`, `entityUseCode`, `taxCompanyCode`
- Line: `quantity`, `netSalesPrice`, `discount`, `discountAmount`, `subscriptionStartDate`, `subscriptionEndDate`, `subscriptionTerm`, `includedUnits`, `evergreen`, plus line-direct QTA custom fields

Everything else persists without re-pricing.

---

## What you cannot set

**Header — `FIELD_NOT_UPDATABLE`:** `totalAmount`, `totalAmountWithoutTax`, `subtotal`, `listTotal`, `grandTotal`, `systemDiscount`, `systemDiscountAmount`, `totalCommittedAmount`, `sellerTotalAmount`, `tax`, `ACV`/`TCV` (quote) or `orderACV`/`orderTCV` (order), `todayARR`, `todayCMRR`, `id`.

**Header, guarded separately:** `currencyIsoCode` (never), the account (never), `priceBookId` (not once lines are priced → `PRICEBOOK_CHANGE_NOT_ALLOWED`).

**Deliberately updatable inputs despite looking like outputs:** `discount`, `discountAmount`, `totalPrice`, `partnerPayoutPercentage`, `partnerPayoutAmount`. Header `totalPrice` is a target total — the engine back-derives the discount.

**Line — `FIELD_NOT_UPDATABLE`:** `listPrice`, `listTotalPrice`, `salesPrice`, `unitPrice`, `netSellerPrice`, `subtotal`, `totalPrice`, `totalAmount`, `sellerTotalAmount`, `taxAmount`/`tax`, `systemDiscount`, `systemDiscountAmount`, all `delta*` metrics, `actualQuantity`, `actualSubscriptionTerm`, `proratedQuantity`, `partnerPayoutPercentage`, `partnerPayoutAmount`.

**Line identity/relationship — blocked:** `id`, `quoteId`, `orderId`, `priceBookEntryId`, `product2Id`/`productId`, `parentId`/`parentOrderProductId`, `productOption`/`productOptionId`.

> **There is no line-total override.** `totalPrice` is non-updatable on a line. To hit a target line total, send `discount` or `netSalesPrice`.

**Precedence on one line:** `netSalesPrice` > `discount` > `discountAmount`. When `netSalesPrice` wins you get a `NETSALESPRICE_APPLIED` warning.

---

## Which lines you can touch

| Line type | Edit | Delete | Rule |
|---|---|---|---|
| Regular line item | Yes | Yes | Includes lines inside a bundle |
| `RampItem` | No | No | Dates → `RAMP_SEGMENT_DATES_LOCKED`; anything else → `LINE_NOT_EDITABLE`. Deletable only with its parent |
| `SummaryItem` | No | Yes (cascades) | Roll-up. Change the pieces, not the summary |
| `SplitItem` | No | No | Split-quote flow owns it |
| Line with `changeAssetId` | No | No | Change-order flow owns it |
| Required / bundled child | Qty follows parent | No | `REMOVE_REQUIRED_CHILD` — remove or reconfigure the parent |
| Change line | No | Not alone | `REMOVE_CHANGE_LINE_RESTRICTED` — delete its summary line instead |
| `NewProduct` line on a change order | Yes | Yes | Behaves like a normal line |

---

## `addLineItems` — ProductInput

Same recursive shape as create (`productSku`/`productName`, `uom`, `quantity`, `startDate`, `endDate`, `subscriptionTerm`, `discount`, `discountAmount`, `netSalesPrice`, `billingPeriod`, `priceTags`, `customPricingAttributes`, `addOns`), plus:

| Field | Meaning |
|---|---|
| `parentLineId` | Add this product as an option child under that existing bundle line. **This is how you reconfigure a bundle.** May target a nested sub-bundle |
| `productOptionId` | Target a specific bundle option slot explicitly (e.g. a dynamic option). When set, `productSku`/`productName` become optional |
| `customFields` | Line-level custom fields, keyed by Salesforce API name on the line object: `{ "Ruby__CostCenter__c": "CC-1042" }`. Recursive through `addOns` |

Rules:
- Adding a bundle auto-includes its required children and required add-ons recursively. Specify only the top of the tree.
- New lines are stamped `changeType: "NewProduct"`.
- Deleting a bundle root removes its whole descendant closure plus private tags/tiers.
- Adds are rejected on a ramped record → `ADD_NOT_ALLOWED_ON_RAMPED_RECORD`.
- No surviving PBE after the currency/UOM/attribute filter → `NO_ELIGIBLE_PRICE_BOOK_ENTRY`.

---

## Price tag actions

Tags are referenced by **id or code**. `target` scopes to specific line ids (omit = all lines). `includeChildren` defaults `true`.

```json
{ "action": "removePriceTags",
  "payload": { "priceTags": ["VOLUME_TIER_2026"], "target": ["0QL..."], "includeChildren": true } }
```

```json
{ "action": "replacePriceTags",
  "payload": { "priceTags": [ { "PARTNER_DISC_10": "PARTNER_DISC_15" } ] } }
```

Removing a tag no scoped line carries → `200` with a `NO_APPLICABLE_LINES_FOR_TAG` warning, not an error. Unknown or inactive tag → `INVALID_PRICE_TAG`.

---

## Response

**Quote:**
```json
{ "quote": { }, "quoteLineItems": [ ], "warnings": [ ] }
```

**Order:**
```json
{ "order": { }, "orderProducts": [ ], "warnings": [ ] }
```

Errors are **not** in the 200 body. Failures return `400` (validation / business rule) or `500`:

```json
{ "status": 400, "error": "Failed to update quote", "message": "...",
  "errorDetails": [ { "code": "LINE_NOT_EDITABLE", "message": "..." } ] }
```

Preview and commit return identical amounts field for field. `salesforceUrl` appears only on commit.

---

## Examples

### Example 1: Patch header fields

```bash
curl -X POST "$NUE_API_URL/cpq/quotes/0Q0cU000001AbcXUAV:update" \
  -H "nue-api-key: $NUE_API_KEY" -H 'Content-Type: application/json' \
  -d '{
    "isCommit": true,
    "recalc": "AUTO",
    "actions": [
      { "action": "updateHeaderFields",
        "payload": { "fieldValues": {
          "name": "Acme - Q3 Expansion",
          "paymentTerm": "Net 45",
          "Ruby__Region__c": "EMEA"
        } } }
    ]
  }'
```

### Example 2: Preview a line edit, then commit it

```bash
# Preview
curl -X POST "$NUE_API_URL/cpq/quotes/0Q0cU000001AbcXUAV:update" \
  -H "nue-api-key: $NUE_API_KEY" -H 'Content-Type: application/json' \
  -d '{
    "isCommit": false,
    "recalc": "AUTO",
    "actions": [
      { "action": "updateLineItems",
        "payload": { "lineItems": [
          { "lineItemId": "0QLcU00000AbcDeFGH",
            "fieldValues": { "quantity": 30, "discount": 15 } }
        ] } }
    ]
  }'

# Commit: identical body with "isCommit": true
```

### Example 3: Add a product and delete another, one call

```bash
curl -X POST "$NUE_API_URL/cpq/quotes/0Q0cU000001AbcXUAV:update" \
  -H "nue-api-key: $NUE_API_KEY" -H 'Content-Type: application/json' \
  -d '{
    "isCommit": true,
    "recalc": "AUTO",
    "actions": [
      { "action": "addLineItems",
        "payload": { "products": [
          { "productSku": "NUE_PLATFORM", "uom": "User/Month", "quantity": 5 }
        ] } },
      { "action": "deleteLineItems",
        "payload": { "lineItemIds": ["0QLcU00000ZzzYyXWV"] } }
    ]
  }'
```

### Example 4: Reconfigure a bundle (add an option)

```bash
curl -X POST "$NUE_API_URL/cpq/quotes/0Q0cU000001AbcXUAV:update" \
  -H "nue-api-key: $NUE_API_KEY" -H 'Content-Type: application/json' \
  -d '{
    "isCommit": true,
    "recalc": "AUTO",
    "actions": [
      { "action": "addLineItems",
        "payload": { "products": [
          { "parentLineId": "0QLcU00000BundleAA",
            "productSku": "ADDON_SUPPORT",
            "uom": "License/Month",
            "quantity": 2 }
        ] } }
    ]
  }'
```

Re-quantify an option: `updateLineItems` with `{ "quantity": 5 }`.
Drop an option: `deleteLineItems` with its line id.

### Example 5: Address change, tax only

```bash
curl -X POST "$NUE_API_URL/cpq/orders/801cU000004AbcDEFG:update" \
  -H "nue-api-key: $NUE_API_KEY" -H 'Content-Type: application/json' \
  -d '{
    "isCommit": true,
    "recalc": "TAX_ONLY",
    "actions": [
      { "action": "updateHeaderFields",
        "payload": { "fieldValues": {
          "shippingStreet": "500 Boren Ave N",
          "shippingCity": "Seattle",
          "shippingState": "WA",
          "shippingPostalCode": "98109",
          "shippingCountry": "US"
        } } }
    ]
  }'
```

### Example 6: Line-level custom fields on an added line

```bash
curl -X POST "$NUE_API_URL/cpq/orders/801cU000004AbcDEFG:update" \
  -H "nue-api-key: $NUE_API_KEY" -H 'Content-Type: application/json' \
  -d '{
    "isCommit": true,
    "actions": [
      { "action": "addLineItems",
        "payload": { "products": [
          { "productSku": "NUE_ON_SALESFORCE", "uom": "User/Month", "quantity": 25,
            "customFields": { "Ruby__CostCenter__c": "CC-1042" } }
        ] } }
    ]
  }'
```

### Example 7: Safe concurrent edit

```bash
curl -X POST "$NUE_API_URL/cpq/quotes/0Q0cU000001AbcXUAV:update" \
  -H "nue-api-key: $NUE_API_KEY" -H 'Content-Type: application/json' \
  -d '{
    "isCommit": true,
    "expectedLastModified": "2026-08-07T18:22:41.000Z",
    "actions": [
      { "action": "updateHeaderFields",
        "payload": { "fieldValues": { "discount": 10 } } }
    ]
  }'
# Stale timestamp -> QUOTE_MODIFIED, nothing persisted. Reload and retry.
```

---

## Env File Convention

```bash
# .env.dev-qa3-ai
NUE_API_URL=https://api.qa3.nue.io
NUE_API_KEY=yGo1b6.07fe5ef1...
```

Load with: `source .env.dev-qa3-ai`

---

## Interaction Patterns

| User Says | API Construct |
|-----------|---------------|
| "change the quantity on line X" | `updateLineItems` with `{ quantity: N }` |
| "give line X 15% off" | `updateLineItems` with `{ discount: 15 }` |
| "set a negotiated unit price" | `updateLineItems` with `{ netSalesPrice: N }` |
| "apply 10% off the whole quote" | `updateHeaderFields` with `{ discount: 10 }` |
| "make the quote total exactly $X" | `updateHeaderFields` with `{ totalPrice: X }` |
| "remove that product" | `deleteLineItems` with its line id |
| "add another product" | `addLineItems` with a ProductInput |
| "add an add-on to the bundle" / "reconfigure the bundle" | `addLineItems` with `parentLineId` = bundle line id |
| "what would this do to ACV?" | Same body with `isCommit: false` |
| "fix the shipping address" | `updateHeaderFields` + `recalc: "TAX_ONLY"` |
| "drop the volume discount tag" | `removePriceTags` |
| "swap the partner tier tag" | `replacePriceTags` |
| "extend the term to 24 months" | `updateHeaderFields` with `{ subscriptionTerm: 24 }` |
| "don't clobber someone else's edit" | Include `expectedLastModified` |

---

## Mistakes to Avoid

| Mistake | Why It Fails | Correct Approach |
|---------|-------------|-----------------|
| Sending the whole quote as a payload | This API is action-oriented; there is no desired-state document | Send `actions` describing only what changes |
| Assuming an omitted line means "delete it" | Omitted lines are untouched | Name the id in `deleteLineItems` |
| `PaymentTerm__c` / `Quantity` in REST `fieldValues` | REST validates Nue names | Use `paymentTerm`, `quantity`. SF API names are Apex-only |
| Putting `quoteId` / `orderId` in the body | Not a body field | It is the path parameter |
| Calling `/cpq/quotes/{id}:preview` | No such endpoint | Use `:update` with `isCommit: false` |
| `"isCommit": "true"` (string) | Type-checked | Use a real boolean |
| `recalc: "auto"` | Case-sensitive | Use `AUTO` |
| Two `updateHeaderFields` actions | `CONFLICTING_ACTIONS` | Merge into one `fieldValues` map |
| Targeting one line in both `updateLineItems` and `deleteLineItems` | `CONFLICTING_ACTIONS` | One action per line per request |
| Setting `totalPrice` on a line | Non-updatable; no line-total override exists | Send `discount` or `netSalesPrice` |
| Setting `totalAmount`, `tax`, `ACV`, `deltaARR` | Engine-calculated | Read them from the response |
| Deleting a required bundle child alone | `REMOVE_REQUIRED_CHILD` | Delete or reconfigure the parent bundle |
| Deleting a change line directly | `REMOVE_CHANGE_LINE_RESTRICTED` | Delete its summary line |
| Editing a ramp segment or summary line | `RAMP_SEGMENT_DATES_LOCKED` / `LINE_NOT_EDITABLE` | Edit the underlying lines |
| Updating an activated order | `ORDER_NOT_DRAFT` | Use the Change Order API |
| Reordering actions to control execution | The server re-orders canonically anyway | Ignore ordering; enforce it with separate requests if you truly need sequencing |
| Using `recalc: "NONE"` on a pricing change | Persists but leaves totals stale | Use `AUTO` |
| Batching 200+ line items | Hard record-size ceiling today | Split the record |

---

## Reference Documentation

- [Update Quote Overview](https://api-docs.nue.io/update-quote-overview)
- [Update Order Overview](https://api-docs.nue.io/update-order-overview)
- [Create Quote Overview](https://api-docs.nue.io/create-quote-overview)
- [Preview vs Commit](https://api-docs.nue.io/preview-vs-commit)
- [Custom Fields](https://api-docs.nue.io/custom-fields)
- [Price Tags](https://api-docs.nue.io/price-tags)
- [Bundles](https://api-docs.nue.io/bundles)
- [Discounts](https://api-docs.nue.io/discounts)
- [Validation Errors](https://api-docs.nue.io/validation-errors)
- [Quote Data Reference](https://api-docs.nue.io/quote-data-reference)
- [Order Data Reference](https://api-docs.nue.io/order-data-reference)
