---
title: Update Quote Overview
slug: update-quote-overview
createdAt: 2026-08-07T12:00:00.000Z
---

# Update Quote Overview

The Update Quote API changes an existing quote and re-runs the same pricing and tax cascade the Nue Line Editor runs internally. It is the write counterpart to [Create Quote](docId\:BC-IdxYnwAUS7O9EFYCwq): same pricing engine, same product shape, same preview semantics -- applied to a quote that already exists.

Use it to change header attributes, edit or delete lines, add products and bundles, reconfigure a bundle's options, swap or remove price tags, and correct addresses -- with totals, metrics, and tax recalculated in the same call.

Everything on this page has an exact Order equivalent. See [Update Order Overview](docId\:Gm0OXVHu351idX6gf9oQa).

## Endpoints

| Endpoint                       | Method | Description                                           |
| ------------------------------ | ------ | ----------------------------------------------------- |
| `/cpq/quotes/{quoteId}:update` | POST   | Apply one or more update actions to an existing quote |

There is no separate preview endpoint. Preview is `isCommit: false` on the same call.

## Action-oriented, not payload-oriented

This is the most important thing to understand about the API, and the way it differs from every other write surface in Nue.

You do **not** send the quote you want. You send **what you want done**.

|                            | Payload-oriented (what this API is *not*)                  | Action-oriented (what this API *is*)                                     |
| -------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------ |
| **You send**               | The full desired record and its lines                      | An ordered list of actions, each naming an operation and its own payload |
| **Server does**            | Diffs your document against stored state and infers intent | Executes exactly the operations you named                                |
| **Omitting a field means** | Ambiguous -- clear it, or leave it alone?                  | Unambiguous -- it is untouched                                           |
| **Omitting a line means**  | Ambiguous -- delete it, or leave it alone?                 | Untouched. Deletion only happens if you send `deleteLineItems`           |
| **Concurrency**            | Last writer clobbers the whole record                      | Only the fields and lines you named are written                          |

Concretely: a `PUT`-style API that received a quote with three lines when the stored quote has five would have to guess whether you meant to delete two. This API never guesses -- you delete a line by asking for it, in a `deleteLineItems` action, by id.

Three consequences worth internalizing:

- **Every action is a true patch.** `updateHeaderFields` changes only the keys you include in `fieldValues`. `updateLineItems` changes only the keys you include on the lines you name. Everything else on the quote is left exactly as it was.
- **There is no "desired line set."** A `lineSet`-style whole-tree diff was designed and deliberately not built. Line changes are always delta actions: `updateLineItems`, `deleteLineItems`, `addLineItems`.
- **Reconfiguring a bundle is not a verb.** There is no `reconfigureBundle` action. You add, delete, or re-quantify the bundle's option lines, and the server rewires the bundle tree. See [Reconfiguring a bundle](docId\:J9W7UfHJvphiEJ93BX5y9).

:::hint{type="info"}
**Contrast with `PATCH /api/objects/{objectName}/{id}`.** The generic Objects API writes the field you send and recalculates **nothing** (partner payout is an exception, below) -- a start date or address changed that way leaves the quote with stale totals and stale tax. The Update Quote API exists precisely to close that gap: it applies the change *and* re-runs pricing, roll-ups, and tax. Use the Objects API for administrative bookkeeping; use this API for anything that touches money, dates, quantity, or the line set.

The exception is partner payout. `PATCH /api/objects/Quote/{id}` with `partnerPayoutPercentage` or `partnerPayoutAmount` in the body re-prices the quote. That PATCH runs the same validation as Update Quote, so sending both partner fields returns `400` (`PARTNER_PAYOUT_MUTUALLY_EXCLUSIVE`). Partner fields are not supported on the list variants -- `PATCH /api/objects/QuoteList` and `POST /api/objects/composite/*List` -- which return `400` wherever your API role can reach them; use Update Quote for those.
:::

## Request envelope

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

| Field                  | Type    | Required | Default | Description                                                                                                                                                                     |
| ---------------------- | ------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `isCommit`             | boolean | No       | `true`  | `true` persists the result. `false` runs the full cascade in memory and persists nothing. Must be a real boolean -- a string is rejected with `400 isCommit must be a boolean.` |
| `recalc`               | string  | No       | `AUTO`  | `AUTO`, `PRICING_ONLY`, `TAX_ONLY`, or `NONE`. **Case-sensitive.** See [Recalculation modes](docId\:J9W7UfHJvphiEJ93BX5y9)                                                      |
| `expectedLastModified` | string  | No       | --      | Optimistic-concurrency guard. When set, the update fails with `QUOTE_MODIFIED` unless it matches the quote's current last-modified timestamp                                    |
| `actions`              | array   | **Yes**  | --      | Ordered list of actions. Must be non-empty                                                                                                                                      |

The quote is identified by the `{quoteId}` path parameter. Do not put `quoteId` in the body -- the REST body has no such field. (The Apex global method does; see [Global methods](docId\:J9W7UfHJvphiEJ93BX5y9).)

## The six actions

| Action               | Payload                                                                                               | What it does                                                                                                                           |
| -------------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `updateHeaderFields` | `{ "fieldValues": { ... } }`                                                                          | Patches header fields -- standard and custom                                                                                           |
| `updateLineItems`    | `{ "lineItems": [ { "lineItemId": "...", "fieldValues": { ... } } ] }`                                | Patches fields on existing lines, addressed by line id                                                                                 |
| `deleteLineItems`    | `{ "lineItemIds": [ "...", "..." ] }`                                                                 | Removes lines by id, cascading to their children and private tags                                                                      |
| `addLineItems`       | `{ "products": [ ProductInput ] }`                                                                    | Adds products and bundles using the same recursive shape as Create Quote. Set `parentLineId` to add an option under an existing bundle |
| `replacePriceTags`   | `{ "priceTags": [ { "<oldIdOrCode>": "<newIdOrCode>" } ], "target": [...], "includeChildren": true }` | Swaps a tag for another on every scoped line that carries the old tag                                                                  |
| `removePriceTags`    | `{ "priceTags": [ "<idOrCode>" ], "target": [...], "includeChildren": true }`                         | Removes tags from the scoped lines and reprices without them                                                                           |

A seventh action, `repriceQuote`, is internal. Sending it is rejected. An unrecognized action name returns `400 Unknown action: <name>`.

### Action ordering is not your problem

You may send actions in any order. The server normalizes them into a canonical sequence before executing:

**header fields → deletes → price-tag actions → line updates → adds**

This ordering exists so that header context (price book, currency, dates, discount) is established before adds resolve their Price Book Entry, and so deletes settle before edits and adds are targeted.

The re-ordering is a **stable sort by stage**: actions move into their stage, and within a stage they keep the order you sent them in. For most requests that makes the result fully order-independent, because the conflict rules force each line to be targeted at most once, so the actions inside a stage cannot overlap.

:::hint{type="warning"}
**The exception is the price-tag stage.** `removePriceTags` and `replacePriceTags` occupy the *same* stage, and the `CONFLICTING_ACTIONS` check does not inspect their `target` line ids — only `updateLineItems` and `deleteLineItems` targets are checked. So two price-tag actions may legitimately touch the same lines, and when they do they execute **in the order you sent them** and the outcome depends on that order. If a `removePriceTags` and a `replacePriceTags` in one request overlap on the same lines or the same tag, order them deliberately, or split them into two requests.
:::

### Two conflicts are rejected before anything is written

| Conflict                                                         | Result                                                         |
| ---------------------------------------------------------------- | -------------------------------------------------------------- |
| `updateHeaderFields` appears more than once in one request       | `CONFLICTING_ACTIONS` -- combine them into a single action     |
| The same line is targeted by more than one action in one request | `CONFLICTING_ACTIONS` -- each line may be touched at most once |

A header pricing change and a line pricing change *may* coexist. The header discount is distributed across the lines that this request did not explicitly price.

## Field naming: use Nue field names over REST

REST `fieldValues` keys are validated against the Nue object describe for `Quote` (header actions) and `QuoteLineItem` (line actions). Use the **Nue camelCase API names** -- `quantity`, `discount`, `subscriptionTerm`, `netSalesPrice`, `paymentTerm`, `includedUnits`, `evergreen`.

An unknown key fails fast at the proxy, before anything reaches Salesforce:

```javascript
400  Invalid field for quote: PaymentTerm__c
400  Invalid field for quote line item: Quantity__c
```

Custom fields are addressed by their Salesforce API name, exactly as on the Create Quote path -- `Region__c`, `CostCenter__c`.

:::hint{type="warning"}
**Salesforce field API names (`SubscriptionTerm__c`, `Discount__c`) are for the Apex global methods, not for REST.** The REST proxy rejects them. If you are porting Apex sample code to REST, translate every `fieldValues` key to its Nue name first.
:::

## Recalculation modes

`recalc` selects which callouts run. `AUTO` is the default and the right answer almost always.

| Mode           | What runs                                                                                                                              | Use it when                                                                                                |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `AUTO`         | Re-prices and/or re-taxes based on which fields you actually changed                                                                   | Default. You do not want to decide what to recompute                                                       |
| `PRICING_ONLY` | Re-prices and rolls up header totals; skips tax. On a change quote, the stored tax is kept and `totalAmount` = `totalPrice` + that tax | Tax is disabled or owned downstream; batch edits that defer tax to a final call; a fast price-only preview |
| `TAX_ONLY`     | Re-runs tax and tax roll-ups; leaves pricing untouched                                                                                 | An address, tax-code, or exemption change -- without disturbing negotiated prices                          |
| `NONE`         | Persists the change and recomputes nothing                                                                                             | Administrative, non-pricing fields only. **Can leave stale totals**                                        |

An invalid value returns `RECALC_INVALID`. The check is case-sensitive: `auto` is not `AUTO`.

Under `AUTO`, whether pricing runs is decided by which fields you touched:

**Header fields that drive a re-price:** `discount`, `discountAmount`, `totalPrice`, `partnerPayoutPercentage`, `partnerPayoutAmount`, `subscriptionStartDate`, `subscriptionEndDate`, `subscriptionTerm`, `subscriptionTermDimension`.

**Header fields that drive tax only:** `shippingStreet`, `shippingCity`, `shippingState`, `shippingPostalCode`, `shippingCountry`, `isShippingAddressSameAsBilling`, the five `taxation*` address fields, `taxationAccount`, `entityUseCode`, `taxCompanyCode`.

**Line fields that drive a re-price:** `quantity`, `netSalesPrice`, `discount`, `discountAmount`, `subscriptionStartDate`, `subscriptionEndDate`, `subscriptionTerm`, `includedUnits`, `evergreen` -- plus any custom field mapped as a line-direct Quantity Tier Attribute, since editing one can move the resolved tier.

Everything else is persisted without a re-price. If you change a field that is not on these lists and you *want* it to affect price, that is what `PRICING_ONLY` is for.

### Pricing engine plugins and `recalc`

Your active pricing engine plugins run as part of the update, exactly as they do on create. The reprice loads them and hands them to the engine on every pricing call, so a plugin that overrides a metric, adjusts a price, or emits a tag behaves the same whether the record was built by Create Quote or edited by Update Quote.

Which plugins run: every **active** plugin scoped `HeaderObject`, `LineItem`, or `Any`, on the `BeforeCalculation`, `AfterCalculation`, or `AfterRampGeneration` trigger events. `Any`-scoped plugins are included -- the update path deliberately uses the scope-inclusive lookup.

:::hint{type="warning"}
**`recalc` decides whether your plugins run at all.** Plugins execute only when the request actually triggers a pricing call. They do **not** run when `recalc` is `NONE` or `TAX_ONLY`, and they do not run under `AUTO` if you only touched fields that don't re-price (billing period, payment term, notes, a plain custom field). If you depend on a plugin to populate a custom metric or price, make sure the edit reaches the pricing pass -- send `PRICING_ONLY` if `AUTO` would skip it.
:::

Preview runs them too. `isCommit: false` skips only the persist phase, so plugins execute on a preview exactly as on a commit -- worth knowing if a plugin of yours has side effects.

Two behaviors specific to the update path, as opposed to create:

- Only price tags already persisted on the line reach the engine; the Price Book Entry auto-tag channel is suppressed on a reprice, so a tag removed earlier is never resurrected by a later update.
- Tags emitted by a plugin are persisted with the result, including plugin tags that carry no identity of their own.

## Preview vs commit

`isCommit: false` runs the identical code path -- load, apply actions, price, tax -- entirely in memory and stops before persistence.

|                                 | `isCommit: false`                                | `isCommit: true` (or omitted) |
| ------------------------------- | ------------------------------------------------ | ----------------------------- |
| Database writes                 | None                                             | One bulk persist              |
| Pricing + tax callouts          | Yes                                              | Yes                           |
| Response shape                  | Identical                                        | Identical                     |
| Side effects                    | None -- no change history, no result persistence | Full                          |
| `salesforceUrl` in the response | Absent                                           | Present                       |

Preview and commit return the same computed amounts field for field. Use preview to answer "what would +10 quantity do to ACV and tax?" without touching the record.

## Transaction semantics

The whole update is one synchronous transaction, executed in three phases:

1. **Validate and apply, in memory.** Every action is validated, then executed against an in-memory object graph. Header start/end dates are re-derived from line min/max, bundle trees are rewired, and the dirty-field analysis behind `recalc: AUTO` is computed. No callouts, no writes.
2. **Price and tax.** Pricing rolls line subtotals up to header ACV/TCV/ARR/MRR/CMRR, re-applies tags, and propagates header-to-line values (auto-renew, payment term, billing frequency). Tax runs when taxation is enabled and the change touched an address, amount, quantity, or the line set.
3. **Persist.** One bulk write of the fully computed result. Preview stops before this phase.

Because it is one transaction, a pricing failure rolls the entire update back -- nothing is written, not even the fields that would have succeeded.

## Examples

All examples use the `nue-api-key` header and `https://api.nue.io`.

### Patch header fields

Only the keys you send change. Everything else on the quote is untouched.

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

### Edit a line, and preview it first

```javascript
const myHeaders = new Headers();
myHeaders.append("nue-api-key", "YOUR_API_KEY_HERE");
myHeaders.append("Content-Type", "application/json");

const edit = {
  isCommit: false,                 // preview -- nothing is written
  recalc: "AUTO",
  actions: [
    {
      action: "updateLineItems",
      payload: {
        lineItems: [
          {
            lineItemId: "0QLcU00000AbcDeFGH",
            fieldValues: { quantity: 30, discount: 15 }
          }
        ]
      }
    }
  ]
};

const res = await fetch(
  'https://api.nue.io/cpq/quotes/0Q0cU000001AbcXUAV:update',
  { method: 'POST', headers: myHeaders, body: JSON.stringify(edit) }
);
const result = await res.json();

console.log(`Preview total: ${result.quote.totalAmount}`);
console.log(`Preview ACV:   ${result.quote.ACV}`);

// Happy with it? Send the identical body with isCommit: true.
```

### Add products and remove one, in a single call

The server orders the delete before the add, regardless of the order you send them in.

```bash
curl -X POST 'https://api.nue.io/cpq/quotes/0Q0cU000001AbcXUAV:update' \
  -H 'nue-api-key: YOUR_API_KEY_HERE' \
  -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"] }
      }
    ]
  }'
```

### Reconfiguring a bundle

There is no reconfigure action. You add an option under the bundle (`parentLineId`), re-quantify an existing option, or delete one.

```bash
# Add an optional add-on to a configured bundle
curl -X POST 'https://api.nue.io/cpq/quotes/0Q0cU000001AbcXUAV:update' \
  -H 'nue-api-key: YOUR_API_KEY_HERE' \
  -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
            }
          ]
        }
      }
    ]
  }'
```

```bash
# Change a bundle option's quantity
{ "action": "updateLineItems",
  "payload": { "lineItems": [ { "lineItemId": "0QLcU00000OptionAA",
                                "fieldValues": { "quantity": 5 } } ] } }

# Drop an optional add-on
{ "action": "deleteLineItems", "payload": { "lineItemIds": ["0QLcU00000OptionAA"] } }
```

The server compares the resulting selection against the bundle's current children, rewires parent/root/summary links, and re-applies price tags, pricing attributes, and price dimensions to the rebuilt lines. Quantity-linked options recompute against the bundle quantity.

### Remove and replace price tags

```json
{
  "isCommit": true,
  "actions": [
    {
      "action": "removePriceTags",
      "payload": {
        "priceTags": ["VOLUME_TIER_2026"],
        "target": ["0QLcU00000AbcDeFGH"],
        "includeChildren": true
      }
    }
  ]
}
```

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

Tags are referenced by id **or** code. `target` scopes the action to specific lines; omit it to apply to every line. `includeChildren` defaults to `true` and extends a targeted parent to its children. Removing a tag that no scoped line carries returns `200` with a `NO_APPLICABLE_LINES_FOR_TAG` warning, not an error.

### Correct an address and re-tax without re-pricing

```json
{
  "isCommit": true,
  "recalc": "TAX_ONLY",
  "actions": [
    {
      "action": "updateHeaderFields",
      "payload": {
        "fieldValues": {
          "shippingStreet": "500 Boren Ave N",
          "shippingCity": "Seattle",
          "shippingState": "WA",
          "shippingPostalCode": "98109",
          "shippingCountry": "US"
        }
      }
    }
  ]
}
```

`TAX_ONLY` re-runs tax and the tax roll-up and leaves every negotiated price alone. See [Addresses and taxation](docId\:J9W7UfHJvphiEJ93BX5y9).

## Response

`200 OK` returns the recalculated quote, its lines, and any warnings.

```json
{
  "quote": {
    "id": "0Q0cU000001AbcXUAV",
    "name": "Acme - Q3 Expansion",
    "totalAmount": 48250.00,
    "ACV": 41000.00,
    "TCV": 41000.00,
    "tax": 3862.50,
    "currencyIsoCode": "USD"
  },
  "quoteLineItems": [
    { "id": "0QLcU00000AbcDeFGH", "quantity": 30, "discount": 15, "netSalesPrice": 1254.60, "totalPrice": 37638.00 },
    { "id": "0QLcU00000NewLineA", "quantity": 5,  "netSalesPrice": 2270.00, "totalPrice": 11350.00, "changeType": "NewProduct" }
  ],
  "warnings": []
}
```

| Field            | Type   | Description                                                  |
| ---------------- | ------ | ------------------------------------------------------------ |
| `quote`          | object | The quote header with recalculated pricing, metrics, and tax |
| `quoteLineItems` | array  | Every line on the quote after the update, with hierarchy     |
| `warnings`       | array  | Non-blocking advisories -- `{ level, code, message }`        |

Errors do not come back in the `200` body. A failed update returns `400` (validation or business-rule failure) or `500`, with the standard error envelope:

```json
{
  "status": 400,
  "error": "Failed to update quote",
  "message": "Line '0QLcU00000AbcDeFGH' is a summary line and cannot be edited directly.",
  "errorDetails": [
    { "errorCode": "LINE_NOT_EDITABLE", "message": "..." }
  ]
}
```

Each `errorDetails` entry carries an `errorCode` and a `message`, plus a `path` when the error is about one place in the request. Branch on `errorCode`. `PATCH /api/objects/{Quote,Order}/{id}` returns the same envelope. The Apex global methods return a different shape (`errors[].errorType`, see [Apex global methods](docId\:J9W7UfHJvphiEJ93BX5y9)).

## What you can change

### Header fields

Header fields are patchable except for engine-owned outputs and identity.

**Never updatable -- calculated by the pricing engine.** Sending any of these returns `FIELD_NOT_UPDATABLE`: `totalAmount`, `subtotal`, `listTotal`, `grandTotal`, `systemDiscount`, `systemDiscountAmount`, `totalCommittedAmount`, `sellerTotalAmount`, `tax`, `ACV`, `TCV`, `todayARR`, `todayCMRR`, and `id`.

**Guarded separately:**

| Field                  | Rule                                                                                             |
| ---------------------- | ------------------------------------------------------------------------------------------------ |
| `currencyIsoCode`      | Cannot be changed. Over REST the proxy rejects it first with `currencyIsoCode is not updatable.` |
| `customerId` / account | Cannot be changed on an existing quote                                                           |
| `priceBookId`          | Cannot be changed once the quote has priced lines -- `PRICEBOOK_CHANGE_NOT_ALLOWED`              |

**Deliberately updatable inputs**, despite looking like outputs: `discount`, `discountAmount`, `totalPrice`, `partnerPayoutPercentage`, and `partnerPayoutAmount`. See [Partner payout](docId\:J9W7UfHJvphiEJ93BX5y9) for how the two partner fields behave. Header `totalPrice` behaves as a target total -- the engine back-derives the discount that hits it. Header `discountAmount` is a conserved target, allocated across lines.

**Cascades that fire on a header change**, exactly as in the Line Editor:

- Header start/end dates re-derive from line min/max.
- A header start-date change pushes down to line dates; end dates are recomputed from term.
- A header term change co-terminates lines and re-annualizes metrics, converting per line UOM dimension.
- `paymentTerm` and `billCycleDay` push down to lines.
- `autoRenew` propagates to Recurring and Usage lines only.
- A header discount is distributed across lines that this request did not explicitly price. Bundled child lines are excluded.
- When both `discount` and `discountAmount` are sent, the percentage wins.

### Partner payout

Send `partnerPayoutPercentage` **or** `partnerPayoutAmount` in `updateHeaderFields`; Nue derives the other. Sending both returns `400` (`PARTNER_PAYOUT_MUTUALLY_EXCLUSIVE`). Use the Nue names -- `Ruby__PartnerPayoutPercentage__c` and `Ruby__PartnerPayoutAmount__c` return `400 Invalid field`.

A partner field always needs a re-price, so it cannot be combined with `recalc: TAX_ONLY` or `recalc: NONE` (`RECALC_MODE_NOT_SUPPORTED`). `partnerPayoutPercentage` must be between 0 and 100 (`INVALID_PARTNER_PAYOUT_RANGE`). For an amount, the derivation uses the quote's total price: an amount that is negative, greater than the total price, or greater than zero when the total price is `0` returns `INVALID_PARTNER_PAYOUT_RANGE`. The amount is stored exactly as you sent it.

Resending the value the quote already holds is not an edit: it triggers no re-price and no `recalc` refusal.

**On a change quote**, the header percentage applies to every change line. An amount is converted to a percentage against the change total, with the summary line excluded. On a line with a negative total (a reduction or cancellation), the payout is the percentage x that line's total, so it is negative too. The summary line carries the asset's payout plus the change lines' new payout, and its percentage is that payout divided by its own total. Set the payout here after the change quote is created: `POST /cpq/change-order` and `POST /cpq/change-order:preview` ignore partner payout fields (the request succeeds and nothing is written).

### Line fields

Not every row in the Line Editor is editable, and the API follows the same rules.

| Line type                                                              | Editable                    | Deletable       | Rule                                                                                                                                                                   |
| ---------------------------------------------------------------------- | --------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Regular line item                                                      | Yes                         | Yes             | Fully editable, including lines inside a bundle                                                                                                                        |
| Ramp segment (`RampItem`)                                              | No                          | No              | System-generated from the ramp schedule. Sending a date returns `RAMP_SEGMENT_DATES_LOCKED`; anything else returns `LINE_NOT_EDITABLE`. Removable only with its parent |
| Summary line (`SummaryItem`)                                           | No                          | Yes -- cascades | A roll-up of the lines beneath it. Change the pieces, not the summary. Removing it removes the whole group                                                             |
| Split line (`SplitItem`)                                               | No                          | No              | Managed by the split-quote flow                                                                                                                                        |
| Subscription-context line (`changeAssetId` set)                        | No                          | No              | Belongs to the change-order flow                                                                                                                                       |
| Required / bundled child                                               | Quantity follows the parent | No              | Locked to the bundle. Remove or reconfigure the parent instead -- `REMOVE_REQUIRED_CHILD`                                                                              |
| Change line (cancel / upgrade / downgrade / swap / qty / term / renew) | No                          | Not on its own  | Remove its summary line to remove the whole change -- `REMOVE_CHANGE_LINE_RESTRICTED`                                                                                  |
| `NewProduct` line on a change order                                    | Yes                         | Yes             | A net-new line; behaves normally                                                                                                                                       |

**Line fields you may set:**

| Category                  | Fields                                                                                                                                            | Re-prices                                                               |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| Quantity and price levers | `quantity`, `discount`, `discountAmount`, `netSalesPrice`                                                                                         | Yes                                                                     |
| Term and dates            | `subscriptionTerm`, `subscriptionStartDate`, `subscriptionEndDate`, `evergreen`, `firstFullCreditPeriodStartDate`                                 | Yes                                                                     |
| Metering                  | `includedUnits`                                                                                                                                   | Yes                                                                     |
| Billing and renewal       | `billingPeriod`, `billingTiming`, `paymentTerm`, `billCycleDay`, `billCycleStartMonth`, `autoRenew`, `defaultRenewalTerm`, `renewalUpliftPercent` | No                                                                      |
| Structure and admin       | `description`, `milestones`, `lineBucketId`, `cancellationDate`                                                                                   | No                                                                      |
| Custom fields             | Any custom field by API name                                                                                                                      | Only if it feeds tiered pricing (a line-direct Quantity Tier Attribute) |

**Line fields you may not set -- calculated and overwritten on recalc.** Sending one returns `FIELD_NOT_UPDATABLE`: `listPrice`, `listTotalPrice`, `salesPrice`, `unitPrice`, `netSellerPrice`, `subtotal`, `totalPrice`, `totalAmount`, `sellerTotalAmount`, `taxAmount`, `systemDiscount`, `systemDiscountAmount`, `deltaACV`, `deltaARR`, `deltaCMRR`, `deltaTCV`, `deltaCommittedARR`, `deltaCommittedCMRR`, `actualQuantity`, `actualSubscriptionTerm`, `proratedQuantity`, `partnerPayoutPercentage`, `partnerPayoutAmount`.

**Relationship and identity fields are blocked too** -- `id`, `quoteId`, `orderId`, `priceBookEntryId`, `product2Id`, `parentId`, `productOption`. A legitimate edit changes pricing, quantity, or schedule; never a line's identity or its place in a bundle.

:::hint{type="warning"}
**A line-total override does not exist.** `totalPrice` is non-updatable on a line. To reach a target line total, send `discount` or `netSalesPrice` instead. The header-level `totalPrice` target *does* work -- that override lives only at the header.
:::

**Precedence when several price levers are sent on one line:** `netSalesPrice` beats `discount` beats `discountAmount`. When `netSalesPrice` wins, a `NETSALESPRICE_APPLIED` warning is returned.

### Adding and removing lines

`addLineItems` takes the same recursive `ProductInput` shape as Create Quote -- `productSku` or `productName`, `uom`, `quantity`, `startDate`, `endDate`, `subscriptionTerm`, `discount`, `priceTags`, `customPricingAttributes`, `customFields`, and nested `addOns`. See [Quote Data Reference](docId:4y_kdZRSB1DwjDFHvyTgO) for the full field list.

Two fields matter specifically on the update path:

| Field             | Meaning                                                                                                                                                                                                               |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `parentLineId`    | Adds this product as an option child under the existing bundle line with that id -- resolved against that bundle's live option config. May target the root bundle or a nested sub-bundle. Omit it for a top-level add |
| `productOptionId` | Targets a specific bundle option slot explicitly, for example a dynamic option. When set, `productSku`/`productName` become optional                                                                                  |

Rules:

- Adding a bundle automatically pulls in its required children and required add-ons recursively, all the way down. You specify only the top of the tree.
- New lines are stamped `changeType: NewProduct`.
- Deleting a bundle root removes its entire descendant closure, together with each line's private price tags and tiers. You do not list the children.
- If no Price Book Entry survives the currency, UOM, and pricing-attribute filter, the add is rejected with `NO_ELIGIBLE_PRICE_BOOK_ENTRY` rather than silently doing nothing.
- `sortOrder` is re-sequenced only on insert.

### Line-level custom fields

`customFields` on a `ProductInput` sets custom fields directly on the line being created, in the same call:

```json
{
  "action": "addLineItems",
  "payload": {
    "products": [
      {
        "productSku": "NUE_ON_SALESFORCE",
        "uom": "User/Month",
        "quantity": 25,
        "customFields": {
          "CostCenter__c": "CC-1042",
          "ContractRef__c": "MSA-2026-0417"
        }
      }
    ]
  }
}
```

To set a custom field on a line that already exists, put it in the line's `fieldValues` under `updateLineItems`. See [Custom Fields](docId\:BikyGXx0Nxr6hDnXuVITf).

## Addresses and taxation

An address or taxation-override change always triggers a tax recalculation when taxation is enabled, then rolls the header total up. `recalc: TAX_ONLY` is the cheap path for an address-only edit.

The header carries a taxation override with two independent parts:

| Override                   | Request fields                                                                             | Turns on when                 |
| -------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------- |
| Taxable address            | `taxationStreet`, `taxationCity`, `taxationState`, `taxationPostalCode`, `taxationCountry` | `taxationCountry` is non-null |
| Taxation customer identity | `taxationAccount`                                                                          | it is non-null                |

Either can be used alone or both together; clearing one does not affect the other. Each resolves through its own cascade, first match wins:

| Step | Taxable address                                         | Taxation customer identity          |
| ---- | ------------------------------------------------------- | ----------------------------------- |
| 1    | Quote taxation address -- if `taxationCountry` is set   | Quote `taxationAccount` -- if set   |
| 2    | Account taxation address -- if `taxationCountry` is set | Account taxation customer -- if set |
| 3    | Quote shipping address (default)                        | Account id and name (default)       |
| 4    | Account shipping address (fallback)                     | --                                  |

Each level is atomic: the chosen level's fields are used as-is and are never merged across levels.

The taxable address is honored by AvaTax, Stripe Tax, Sphere, and Anrok. The taxation customer identity is honored by **Anrok only**, where it matches the exemption certificate. On the other integrations it is saved but ignored -- a no-op, not an error.

Resolved taxation values freeze when the invoice activates. Change orders and renewals carry their own copy; cancellations, credit memos, refunds, voids, and write-offs reuse the originating order's frozen values.

## Editability gates

Before any action runs, the record itself must be editable.

| Gate                                                | Error                                                       |
| --------------------------------------------------- | ----------------------------------------------------------- |
| Quote is activated, ordered, or locked for approval | `QUOTE_NOT_EDITABLE`                                        |
| Order is not in `Draft` status                      | `ORDER_NOT_DRAFT`                                           |
| Record id does not exist                            | `QUOTE_NOT_FOUND` / `ORDER_NOT_FOUND`                       |
| `expectedLastModified` does not match               | `QUOTE_MODIFIED` / `ORDER_MODIFIED` -- nothing is persisted |
| Caller lacks edit permission                        | `INSUFFICIENT_ACCESS`                                       |

## Common error codes

| Code                                | Meaning                                                                                                                                                                           |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `UNKNOWN_ACTION`                    | The `action` name is not one of the six                                                                                                                                           |
| `ACTION_NOT_ALLOWED`                | An internal-only action was sent in a public request                                                                                                                              |
| `CONFLICTING_ACTIONS`               | Duplicate `updateHeaderFields`, one line targeted twice, or `partnerPayoutAmount` sent together with a price edit on an existing-subscription change line (apply them separately) |
| `RECALC_INVALID`                    | `recalc` is not `AUTO`, `PRICING_ONLY`, `TAX_ONLY`, or `NONE`                                                                                                                     |
| `PARTNER_PAYOUT_MUTUALLY_EXCLUSIVE` | Both `partnerPayoutPercentage` and `partnerPayoutAmount` were sent                                                                                                                |
| `INVALID_PARTNER_PAYOUT_RANGE`      | A `partnerPayoutPercentage` outside 0-100, or a payout amount that is negative, greater than the total price, or greater than zero on a record whose total price is `0`           |
| `RECALC_MODE_NOT_SUPPORTED`         | A partner field was sent with `recalc: TAX_ONLY` or `NONE`                                                                                                                        |
| `INVALID_FIELD`                     | The field does not exist on the target object                                                                                                                                     |
| `FIELD_NOT_UPDATABLE`               | The field is system-managed or engine-calculated                                                                                                                                  |
| `PRICEBOOK_CHANGE_NOT_ALLOWED`      | `priceBookId` changed on a quote that already has priced lines                                                                                                                    |
| `LINE_NOT_EDITABLE`                 | Summary, split, ramp, or subscription-context line                                                                                                                                |
| `QUANTITY_NOT_EDITABLE`             | The line's quantity is controlled by its bundle parent                                                                                                                            |
| `RAMP_SEGMENT_DATES_LOCKED`         | Ramp-segment dates are owned by the ramp schedule                                                                                                                                 |
| `REMOVE_REQUIRED_CHILD`             | A required or bundled child cannot be removed alone                                                                                                                               |
| `REMOVE_RAMP_SEGMENT`               | A ramp segment cannot be removed alone                                                                                                                                            |
| `REMOVE_CHANGE_LINE_RESTRICTED`     | Remove the summary line to remove the whole change                                                                                                                                |
| `NO_ELIGIBLE_PRICE_BOOK_ENTRY`      | No PBE survived the currency / UOM / attribute filter                                                                                                                             |
| `INVALID_PRICE_TAG`                 | The price tag was not found or is inactive                                                                                                                                        |
| `INVALID_LINEITEM_REFERENCE`        | The targeted line does not belong to this record                                                                                                                                  |
| `PRICING_ENGINE_ERROR`              | Pricing failed; nothing was committed                                                                                                                                             |

Common warnings: `NETSALESPRICE_APPLIED`, `HEADER_DISCOUNT_APPLIED`, `PRODUCT_DISCOUNT_OVERRIDES_HEADER`, `NO_APPLICABLE_LINES_FOR_TAG`, `TAX_CALCULATION_FAILED`, `QUANTITY_NOT_EDITABLE`.

## Scope and limits

**This API is for quotes and for orders that have not been activated yet.** Changes to live, activated subscriptions -- renewals, quantity changes, cancellations, and bundle reconfigurations on an activated order -- go through the [Change Order](docId\:USsHEavDTd5EFewFaUYlQ) process instead.

| Limit                                       | Detail                                                                                                                                                                               |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Record size                                 | Large records are supported. A single request is bounded by Apex governor limits -- pricing and tax run as synchronous callouts -- rather than by a fixed line-item count            |
| Callouts                                    | Pricing and tax are synchronous HTTP callouts. Budget at least one of each per recalc. A header carrying `partnerPayoutAmount` without a percentage triggers a two-pass pricing call |
| Tax failures                                | Surface as warnings, not hard errors, and leave amounts without tax                                                                                                                  |
| Change quotes under `PRICING_ONLY`          | The stored tax is kept as is, and `totalAmount` = `totalPrice` + that stored tax                                                                                                     |
| Bundle reconfigure on a change quote        | Not yet emitted as change lines under a Reconfigure parent. Pre-order, net-new bundles work today                                                                                    |
| Editing the change carried by a change line | Not supported                                                                                                                                                                        |

## Apex global methods

In-org and managed-package callers can use the Apex global methods directly, with no HTTP:

```apex
GlobalAPITypes.UpdateQuoteResponse GlobalQuoteServiceAPI.updateQuote(GlobalAPITypes.UpdateQuoteRequest req)
GlobalAPITypes.UpdateOrderResponse GlobalOrderServiceAPI.updateOrder(GlobalAPITypes.UpdateOrderRequest req)
```

Two differences from REST:

- The record id is a field on the request (`req.quoteId` / `req.orderId`), not a path parameter.
- `fieldValues` keys are **Salesforce field API names** (`SubscriptionTerm__c`, `Discount__c`), case-insensitive, with the namespace optional -- not the Nue camelCase names REST uses.

The response is `{ status, data, warnings, errors }`, where `status` is `SUCCESS` or `FAILURE` and errors are returned in the payload rather than as an HTTP status.

There is also a Salesforce apexrest surface for in-org HTTP callers: `POST /services/apexrest/Ruby/updateQuote` and `/services/apexrest/Ruby/updateOrder`, taking the same JSON body as the global method.

Full Apex documentation, including request construction and worked examples, lives in the Nue Knowledge Center under **Global Methods**.

## Related pages

- [Create Quote Overview](docId\:BC-IdxYnwAUS7O9EFYCwq) -- the create counterpart
- [Update Order Overview](docId\:Gm0OXVHu351idX6gf9oQa) -- the exact Order equivalent
- [Preview vs Commit](docId:0JQ3SD3mI6ejHJc69oVik)
- [Custom Fields](docId\:BikyGXx0Nxr6hDnXuVITf)
- [Price Tags](docId\:Z5zOtt7ESzhMHAXIFqMKr)
- [Quote Data Reference](docId:4y_kdZRSB1DwjDFHvyTgO)
- [Change Quotes and Orders](docId\:USsHEavDTd5EFewFaUYlQ) -- for activated subscriptions
