Finalize Quotes to Orders
Finalize Quotes to Orders
Finalizing is the quote-to-revenue step: an accepted quote becomes an order, and an activated order provisions the subscriptions, assets and entitlements the customer actually bought. This page covers the three operations that carry a quote across that line and back again — finalize, activate, and cancel.
Endpoints
Endpoint | Method | Description |
|---|---|---|
/cpq/quotes:finalize | POST | Convert one or more quotes into orders, optionally activating them |
/orders/{orderId} | POST | Activate a draft order |
/cpq/orders/{orderId}:cancel | POST | Cancel an activated order |
/cpq/quotes:finalize and /cpq/orders/{orderId}:cancel are on the CPQ API and are available on Salesforce-integrated tenants only. Activation is served by the Nue order service.
Draft or activated
Finalizing has two modes, and the difference is what gets provisioned.
| activateOrders: false (default) | activateOrders: true |
|---|---|---|
Creates | A Draft order | A Draft order, then activates it |
Provisions | Nothing | Subscriptions, assets and entitlements, by product category |
Reversible | Yes — a Draft order can still be corrected | Not in practice. Undoing it means cancelling the order |
Leaving the order in Draft and activating it separately is the same shape the Salesforce UI uses, and it is the safer default when an agent or an integration is driving.
Finalize Quotes
POST /cpq/quotes:finalize{
"quoteIds": ["0Q0dh000002cp1xCAA"],
"activateOrders": false
}{
"orders": [
{
"orderId": "801dh00000zZQFNAA4",
"orderNumber": "00000142",
"status": "Draft",
"quoteId": "0Q0dh000002cp1xCAA"
}
],
"warnings": []
}Key behaviors
- At most 50 quote ids per call. A larger batch is rejected before any work is done.
- The batch is atomic: one unknown id finalizes nothing. A repeated id is finalized once.
- Activation is synchronous but provisioning is not. Subscriptions appear shortly afterwards, so read them back rather than reporting them from this response — it does not list them.
- On a quote-split tenant one quote can produce several orders. Do not assume one order per quote id.
- A tenant may additionally require the quote be primary, or its status be exactly Approved — note that is Approved, not Accepted.
Finalizing is not idempotent, and it is not blocked. Finalizing a quote that already has an order creates a second order on a normal tenant, and activates the existing draft on a quote-split tenant. Check whether the quote already has an order before finalizing it again. A 500 leaves the outcome unknown — read the quote before retrying.
Activate Order
POST /orders/{orderId}{ "options": { "generateInvoice": false, "activateInvoice": false } }{ "data": { "success": true } }Activation turns a Draft order — new or change — into an Active order, which is what generates subscriptions, assets and entitlements. On Salesforce the work is done by the Order trigger when Status becomes Activated, which is why the endpoint only has to stamp the field.
The response does not list what was provisioned. Read the subscriptions back separately.
Salesforce orgs also expose this as an Apex REST resource:
POST /services/apexrest/Ruby/activate-order
{ "id": "801dh00000zZQFNAA4" }which answers { "success": true }, or { "success": false, "errorCode": "...", "message": "..." } with a 400.
Cancel Order
POST /cpq/orders/{orderId}:cancelNo request body, and no response body on success.
Cancelling unwinds the order's subscriptions, assets and entitlements to their previous version and reverses their daily and monthly ARR records.
This is irreversible. A cancelled order cannot be reactivated.
Key behaviors
- Only Activated orders can be cancelled. A Draft order has provisioned nothing — delete it instead.
- The CancelOrder permission and the bundle-child dependency check are enforced server-side.
- An order with dependent orders, a split order, or one with superseding changes is refused.
- Salesforce-origin orders only.
- A 504 means the outcome could not be confirmed, not that the cancel failed. Read the order's status before retrying.
To check eligibility first, call POST /orders/cancelable with { "orderIds": [...] }. It reports only that no active invoice blocks the order — it does not check order status, activation state, or dependent orders, all of which are enforced when you cancel.
Error responses
Status | When |
|---|---|
400 | More than 50 quote ids, an empty list, an unresolved change-order line, or a tax-calculation error on the quote. For cancel: dependent orders, already cancelled, never activated, or the caller lacks CancelOrder |
404 | One or more quotes were not found. Nothing was finalized |
409 | Cancel via Apex is not enabled for this tenant |
500 | Internal server error. The outcome is unknown — read the quote before retrying, or it may be finalized twice |
504 | The cancel outcome could not be confirmed |
Through the Nue Lifecycle Manager MCP
Tool | Endpoint it calls |
|---|---|
finalizeQuoteToOrder | /cpq/quotes:finalize |
activateOrder | /orders/{orderId} |
cancelOrder | /cpq/orders/{orderId}:cancel, plus /orders/cancelable for the preview |
All three are gated, because all three are hard to undo. finalizeQuoteToOrder defaults to a Draft order and requires confirm: true before it will activate; asking to activate without confirming is not an error — it returns mode: "preview" describing the quotes involved and writes nothing. activateOrder and cancelOrder write nothing unless commit is true, and a cancel preview lists the subscriptions, assets and entitlements the cancel would unwind so the blast radius is visible before anyone agrees to it.
Apex global methods
List<Id> quoteIds = new List<Id>{ '0Q04w000002RzITCA0' };
List<Order> orders = new Ruby.GlobalQuoteService().generateOrders(quoteIds, true);
// Split quotes: finalize selected buckets into one order each, a batch at a time.
List<Order> bucketOrders = new Ruby.GlobalQuoteService()
.generateOrders(quoteId, lineBucketIds, true);
new Ruby.GlobalOrderService().cancelActiveOrders(new List<Id>{ '8014w000008LIDXAA4' });There is no global method for activating an order — activation happens when an order's Status becomes Activated, so a Flow or Apex caller can stamp the field directly, or call the /services/apexrest/Ruby/activate-order resource above. Cancelling is the opposite: a direct status write to Cancelled is refused, and the dedicated cancel path must be used.
Bucket activation is best effort. When generateOrders(quoteId, lineBucketIds, true) is used and an order's start date has to be recalculated, activation completes asynchronously — a returned order may still be Draft. Poll Order.Status to confirm.