Clone and Template Quotes
Clone and Template Quotes
Rebuilding a complicated quote line by line is slow and risks silently dropping a negotiated price tag. Three endpoints let you start from a quote that already exists instead: clone any quote, create a new quote from a curated template, or copy a template onto a quote you have already created.
All three carry over line items, applied line price tags and price tiers, and all three are available over REST, through the Nue Lifecycle Manager MCP, and as Apex global methods inside the managed package.
Which one to use
You have | You want | Use |
|---|---|---|
Any quote — a revision, last year's renewal | A new independent copy on the same account | Clone Quote |
A curated template quote and an opportunity | A new quote created and populated in one call | Create Quote From Template |
A curated template quote and a quote that already exists | The template's lines copied into that quote | Copy Quote From Template |
Endpoints
Endpoint | Method | Description |
|---|---|---|
/cpq/quotes/{quoteId}:clone | POST | Copy any quote into a new Draft quote |
/cpq/quotes:createFromTemplate | POST | Create a quote on an opportunity from a template quote |
/cpq/quotes/{targetQuoteId}:copyFromTemplate | POST | Copy a template quote onto an existing quote |
All three are available on Salesforce-integrated tenants only.
A template carries the shape of a deal, never a locked price
This is the single most important behaviour on this page, and the one most likely to surprise you.
A clone stays on its source's account. Its pricing, negotiated discounts and tax are copied across unchanged, because nothing about the customer changed.
A template is different. createFromTemplate and copyFromTemplate always reprice the result against the target quote's own account. A discount whose tier depends on the customer — headcount, volume, segment — re-resolves to the target's band, not the template's. Tax is likewise recalculated for the target's own shipping jurisdiction, so a template built for a customer in one city and applied to a customer in another is taxed for the second customer.
Expect the money to differ from the template whenever the two accounts resolve differently. That is the design.
Tax recalculation is best effort and is not reported back. A tenant with taxation switched off, or a tax engine that rejects the target's address, still returns 200. If the tax figure matters, read the quote back and check it.
Clone Quote
POST /cpq/quotes/{quoteId}:cloneNo request body.
{
"quote": { "id": "0Q0dh000002cp1xCAA", "name": "Acme Renewal Copy" },
"lineItemsCopied": 7,
"priceTagsCopied": 3
}Key behaviors
- The source does not have to be a template quote. Use this to duplicate any quote.
- The clone is always Draft and is never marked primary.
- It is named <source name/> Copy and stays on the source's opportunity. The endpoint accepts no overrides — to change the name, opportunity, start date or subscription term, follow with Update Quote.
- Price tags are always copied. There is no flag to suppress them.
- Cloning a quote with zero line items succeeds and returns an empty Draft quote. That is not an error.
- Not idempotent. Each call creates another quote, and a request that times out may still have created one — look for a new Draft quote on the source's opportunity before retrying.
Create Quote From Template
POST /cpq/quotes:createFromTemplate{
"templateQuoteId": "0Q0dh000002cpBdCAI",
"opportunityId": "006dh00000ABCDEAA3"
}{
"quote": { "id": "0Q0dh000002cq44CAA", "name": "Standard Platform Deal" },
"lineItemsCopied": 4,
"priceTagsCopied": 2
}Key behaviors
- The source must be flagged as a template quote (Ruby__Is_Template_Quote__c = true). A non-template source is refused with 400 and nothing is created.
- The new quote is Draft, never primary, and inherits the template's price book, subscription term and start date.
- There is no priceBookId parameter: the copy rejects any quote whose price book differs from the template's, so the new quote always takes the template's.
- The endpoint accepts no name, date or term overrides — follow with Update Quote.
- Header custom fields are not inherited. A template supplies line items, not header values, so set header custom fields yourself on the follow-up update. Line-level custom fields do copy.
- Not idempotent. Each call creates another quote.
Copy Quote From Template
POST /cpq/quotes/{targetQuoteId}:copyFromTemplate{ "templateQuoteId": "0Q0dh000002cpBdCAI" }{
"targetQuoteId": "0Q0dh000002cq44CAA",
"skipped": false,
"lineItemsCopied": 4,
"priceTagsCopied": 2
}Key behaviors
- The target must already exist. This endpoint never creates a quote.
- The template and target price books must match, and the template must have a price book.
- Header custom fields are not copied from the template; line-level custom fields are.
Always read skipped
A repeat call is not always a repeat copy, and the response is 200 either way.
When the target already records this same template as the one it was built from, the call is a deliberate no-op: skipped: true, lineItemsCopied: 0, and nothing is repriced or re-taxed.
That recording is what decides it — not the line items themselves — and only quotes created by createFromTemplate carry it. A quote you assembled any other way records no template, so a second copy onto it is refused with 409. Do not treat this endpoint as idempotent for a target you built yourself: read the target first.
Error responses
Status | When |
|---|---|
400 | Invalid request; the source is not a template quote; the template has no price book; the template and target price books differ |
404 | The source, template or target quote was not found |
409 | (copyFromTemplate only) The target already has lines and does not record this template. Not retryable without clearing the target first |
500 | Internal server error. The outcome is unknown — look for a newly created Draft quote before retrying, or you may create a second one |
Through the Nue Lifecycle Manager MCP
Tool | Endpoint it calls |
|---|---|
cloneQuote | /cpq/quotes/{quoteId}:clone, then :update for any override |
createQuoteFromTemplate | /cpq/quotes:createFromTemplate, then :update for any override |
copyQuoteFromTemplate | /cpq/quotes/{targetQuoteId}:copyFromTemplate |
The tools add conveniences the REST contract does not have. cloneQuote and createQuoteFromTemplate accept name, startDate and subscriptionTerm — and createQuoteFromTemplate also accepts customFields — and apply them as a follow-up repricing update. If the quote is created but that update fails, the tool returns PARTIAL_SUCCESS and names the quote that was created, so it can be corrected rather than orphaned silently.
Apex global methods
Available inside the managed package for Flows and Apex callers.
Quote clone = new Ruby.GlobalQuoteService().cloneQuote('0Q04w000002RzITCA0');
Quote fromTemplate = new Ruby.GlobalQuoteService()
.createQuoteFromTemplate('0Q04w000002RzITCA1', '006dh00000ABCDEAA3');
new Ruby.GlobalQuoteService()
.copyQuoteFromTemplate('0Q04w000002RzITCA1', '0Q04w000002RzITCA0');One deliberate difference between Apex and REST. copyQuoteFromTemplate is a released global method, so it still accepts an ordinary quote as its source. The REST and MCP surfaces refuse a non-template source, so that they agree with createFromTemplate. Existing Apex and Flow callers are unaffected.