ConnectWise CPQ Quotes
Overview
A CPQ quote is the proposal an MSP sends a client: a header (account, status, dates,
totals), one or more tabs that section the proposal, line items on those tabs,
customer records synced from the attached CRM/PSA, and optional payment/financing
terms. Quotes are versioned — a revision creates a new version under the same quote
number rather than mutating the old one.
Everything an MSP does after the quote is built — publishing to Order Porter, e-signature,
porting a won quote into the PSA as a sales order — happens in the CPQ web app. The API
covers building and reading quotes, not delivering them.
Anti-triggers
CPQ owns one stage of the quote funnel: composing and pricing the quote document.
It does not source the SKUs that go into it, it cannot deliver it, and it is not the
only tool in the stack that holds something called a quote.
- The priced SKU master list rather than this deal's document — catalog items,
costs, categories and manufacturers live in ConnectWise PSA; use
connectwise-psa-product-catalog.
- "Send the customer the quote", e-signature, or signed-document status — CPQ's API
has no publish, send or e-sign verb.
isSent and the orderPorter* fields are
state you can read (and patch), not actions you can trigger. Delivery and signature
tracking are pandadoc-documents.
- A quote that was built in a different quoting product — Kaseya Quote Manager and
SalesBuildr each keep their own quotes, numbering and APIs; use
kaseya-quote-manager-quotes or salesbuildr-quotes. Route on the product the quote
lives in, not on the word "quote".
- Adding, repricing or removing a line — the quote header and its line items are
separate tool sets; use
connectwise-cpq-quote-items.
Key Concepts
Two ways to address a quote
| Addressing |
Used by |
Notes |
GUID id (string) |
cpq_get_quote, cpq_update_quote, cpq_delete_quote, cpq_create_quote_from_template |
Unique to one version of one quote |
quoteNumber (int) + quoteVersion (int) |
cpq_get_quote_versions, cpq_delete_quote_version |
The human-facing number; identifies the quote across its versions |
Searches return both, so pull the id out of a search result before doing anything that
needs a GUID. A quote number alone is not enough to patch or delete a quote.
Versions
cpq_get_quote_versions takes a quoteNumber and behaves three ways:
- omit
version → list every version
version: "latest" → the current version
version: 3 → that specific version
Searches return latest versions only unless you pass showAllVersions: true. If a
search seems to be missing a quote a colleague is looking at, they are probably on a
superseded version.
Quote status and flags
Status lives across several fields rather than one enum. The ones that decide what a
quote means:
| Field |
Meaning |
quoteStatus |
Tenant-configurable status text (values vary per install — read them from live data, do not assume) |
isSent |
Delivered to the customer |
isAccepted |
Customer accepted |
isLost |
Marked lost |
isArchive |
Archived; excluded from most working views |
requiresApproval / approvalStatus / approvedByUser |
Internal approval workflow |
expirationDate / expectedCloseDate |
Proposal validity and forecast date |
orderPorter* |
Order Porter publishing state (passcode, template, signed date, upload state) |
See references/fields.md for the fuller QuoteView field reference.
Sub-resources
| Sub-resource |
Tools |
Notes |
| Tabs |
cpq_search_quote_tabs |
Read-only over the API. Sections of the proposal; every line item belongs to one. |
| Line items |
see the quote-items skill |
Full CRUD |
| Customers |
cpq_list_quote_customers, cpq_update_quote_customer, cpq_delete_quote_customer |
Exist only in the context of a quote — there is no global customer directory to search |
| Terms |
cpq_list_quote_terms, cpq_create_quote_term, cpq_update_quote_term, cpq_delete_quote_term |
Payment/financing options: periods, interestRate, downPayment, periodPaymentAmount, isSelected, leasing fields |
Common Workflows
Find a quote
cpq_search_quotes with conditions and a tight includeFields, e.g.
conditions: accountName contains "Acme" AND isArchive = False,
includeFields: id,name,quoteNumber,quoteVersion,quoteStatus,quoteTotal,createDate
- Page with
page/pageSize until a page returns fewer rows than pageSize.
cpq_get_quote with the GUID for the full record.
Called with no conditions the search asks for a created-since date, falling back to the
last 90 days and noting that in the result. Give it a condition when you know one — a
90-day window silently hides older quotes.
Create a quote
There is no create-from-scratch endpoint. Every new quote is a copy of a template or
of an existing quote, then patched into shape.
cpq_list_templates to see what is available (templates are themselves quotes).
cpq_create_quote_from_template with templateId, or with templateName to resolve
by name — an ambiguous name prompts a selection, and with no way to prompt you get an
error listing the candidates. Pass newName to rename the copy in the same call.
- The copy inherits the template's tabs, line items and terms. Patch the header with
cpq_update_quote (account, dates, custom fields).
- Add or adjust lines with the quote-items tools.
To revise an existing deal, copy the existing quote instead of a template —
cpq_create_quote_from_template accepts any quote GUID as its source.
Update a quote
cpq_update_quote takes either fields (a partial object, turned into replace ops) or
patch (raw RFC 6902 ops) — never both.
cpq_update_quote id=<guid> fields={ "name": "Acme — Managed Services FY27",
"expectedCloseDate": "2026-09-30T00:00:00Z" }
This tool is flagged high-impact for a reason: patching quoteStatus, isArchive,
isLost, expirationDate, orderPorter* or the approval fields changes workflow state
and, for the Order Porter fields, what the customer sees. Confirm intent before touching
those; renaming or setting a forecast date is routine.
Work the customers and terms on a quote
cpq_list_quote_customers with the quote GUID — the records are already there,
synced from the CRM/PSA. Correct them with cpq_update_quote_customer.
cpq_list_quote_terms for financing options; cpq_create_quote_term to add one.
isSelected marks the term the customer is being offered.
Delete
cpq_delete_quote removes the quote and every tab, line item and term on it.
Confirmation depends on how you are connected: an interactive client is prompted, while
a non-interactive one — the gateway included — is refused until the call is re-invoked
with confirm_destructive_action: true. cpq_delete_quote_version removes a single version by
quoteNumber + quoteVersion. Neither is recoverable — archive (isArchive: True) is
almost always the right move for a dead deal instead.
Gotchas
count in a tool result is the page length, not a total. CPQ returns bare arrays
with no collection count. Keep paging until a short page arrives.
- Searching by quote number needs a number, not a string:
quoteNumber = 1042, not
quoteNumber = "1042". Dates go in square brackets and are date-only:
createDate >= [2026-07-01].
- Every line item needs a tab. A freshly copied quote has the template's tabs; a
quote with no tabs cannot take line items, and tabs cannot be created over the API.
- Deleting a quote version is not the same as deleting a quote. The version delete
targets
quoteNumber + quoteVersion; the quote delete targets a GUID and takes
everything.
crmOpportunityId is a pointer, not a join. There are no opportunity endpoints in
CPQ — resolve it against the PSA/CRM (the connectwise-psa plugin) if you need the
opportunity itself.
- QuoteView is huge and mostly empty. 204 properties, 60+ of them unused
zCustom*
slots. Always send includeFields on searches or the results bury the useful fields.
Related Skills
1---2name: connectwise-cpq-quotes3description: The ConnectWise CPQ quote lifecycle over the real tool surface: searching quotes, the GUID-vs-quoteNumber dual addressing, versions, creating quotes by copying a template (the API's only create path), patching quote fields, the per-quote customer records and payment/financing terms, tabs as the section structure, and the deletes that cascade.4---56# ConnectWise CPQ Quotes78## Overview910A CPQ quote is the proposal an MSP sends a client: a header (account, status, dates,11totals), one or more **tabs** that section the proposal, **line items** on those tabs,12**customer records** synced from the attached CRM/PSA, and optional **payment/financing13terms**. Quotes are versioned — a revision creates a new version under the same quote14number rather than mutating the old one.1516Everything an MSP does after the quote is built — publishing to Order Porter, e-signature,17porting a won quote into the PSA as a sales order — happens in the CPQ web app. The API18covers building and reading quotes, not delivering them.1920## Anti-triggers2122CPQ owns one stage of the quote funnel: **composing and pricing the quote document**.23It does not source the SKUs that go into it, it cannot deliver it, and it is not the24only tool in the stack that holds something called a quote.2526- **The priced SKU master list rather than this deal's document** — catalog items,27 costs, categories and manufacturers live in ConnectWise PSA; use28 `connectwise-psa-product-catalog`.29- **"Send the customer the quote", e-signature, or signed-document status** — CPQ's API30 has no publish, send or e-sign verb. `isSent` and the `orderPorter*` fields are31 state you can read (and patch), not actions you can trigger. Delivery and signature32 tracking are `pandadoc-documents`.33- **A quote that was built in a different quoting product** — Kaseya Quote Manager and34 SalesBuildr each keep their own quotes, numbering and APIs; use35 `kaseya-quote-manager-quotes` or `salesbuildr-quotes`. Route on the product the quote36 lives in, not on the word "quote".37- **Adding, repricing or removing a line** — the quote header and its line items are38 separate tool sets; use `connectwise-cpq-quote-items`.3940## Key Concepts4142### Two ways to address a quote4344| Addressing | Used by | Notes |45|---|---|---|46| GUID `id` (string) | `cpq_get_quote`, `cpq_update_quote`, `cpq_delete_quote`, `cpq_create_quote_from_template` | Unique to one *version* of one quote |47| `quoteNumber` (int) + `quoteVersion` (int) | `cpq_get_quote_versions`, `cpq_delete_quote_version` | The human-facing number; identifies the quote across its versions |4849Searches return both, so pull the `id` out of a search result before doing anything that50needs a GUID. A quote number alone is not enough to patch or delete a quote.5152### Versions5354`cpq_get_quote_versions` takes a `quoteNumber` and behaves three ways:5556- omit `version` → list every version57- `version: "latest"` → the current version58- `version: 3` → that specific version5960Searches return **latest versions only** unless you pass `showAllVersions: true`. If a61search seems to be missing a quote a colleague is looking at, they are probably on a62superseded version.6364### Quote status and flags6566Status lives across several fields rather than one enum. The ones that decide what a67quote means:6869| Field | Meaning |70|---|---|71| `quoteStatus` | Tenant-configurable status text (values vary per install — read them from live data, do not assume) |72| `isSent` | Delivered to the customer |73| `isAccepted` | Customer accepted |74| `isLost` | Marked lost |75| `isArchive` | Archived; excluded from most working views |76| `requiresApproval` / `approvalStatus` / `approvedByUser` | Internal approval workflow |77| `expirationDate` / `expectedCloseDate` | Proposal validity and forecast date |78| `orderPorter*` | Order Porter publishing state (passcode, template, signed date, upload state) |7980See [references/fields.md](references/fields.md) for the fuller QuoteView field reference.8182### Sub-resources8384| Sub-resource | Tools | Notes |85|---|---|---|86| Tabs | `cpq_search_quote_tabs` | Read-only over the API. Sections of the proposal; every line item belongs to one. |87| Line items | see the [quote-items skill](../quote-items/SKILL.md) | Full CRUD |88| Customers | `cpq_list_quote_customers`, `cpq_update_quote_customer`, `cpq_delete_quote_customer` | Exist **only** in the context of a quote — there is no global customer directory to search |89| Terms | `cpq_list_quote_terms`, `cpq_create_quote_term`, `cpq_update_quote_term`, `cpq_delete_quote_term` | Payment/financing options: `periods`, `interestRate`, `downPayment`, `periodPaymentAmount`, `isSelected`, leasing fields |9091## Common Workflows9293### Find a quote94951. `cpq_search_quotes` with `conditions` and a tight `includeFields`, e.g.96 `conditions: accountName contains "Acme" AND isArchive = False`,97 `includeFields: id,name,quoteNumber,quoteVersion,quoteStatus,quoteTotal,createDate`982. Page with `page`/`pageSize` until a page returns fewer rows than `pageSize`.993. `cpq_get_quote` with the GUID for the full record.100101Called with no `conditions` the search asks for a created-since date, falling back to the102last 90 days and noting that in the result. Give it a condition when you know one — a10390-day window silently hides older quotes.104105### Create a quote106107There is no create-from-scratch endpoint. Every new quote is a **copy** of a template or108of an existing quote, then patched into shape.1091101. `cpq_list_templates` to see what is available (templates are themselves quotes).1112. `cpq_create_quote_from_template` with `templateId`, or with `templateName` to resolve112 by name — an ambiguous name prompts a selection, and with no way to prompt you get an113 error listing the candidates. Pass `newName` to rename the copy in the same call.1143. The copy inherits the template's tabs, line items and terms. Patch the header with115 `cpq_update_quote` (account, dates, custom fields).1164. Add or adjust lines with the quote-items tools.117118To revise an existing deal, copy the existing quote instead of a template —119`cpq_create_quote_from_template` accepts any quote GUID as its source.120121### Update a quote122123`cpq_update_quote` takes either `fields` (a partial object, turned into `replace` ops) or124`patch` (raw RFC 6902 ops) — never both.125126```127cpq_update_quote id=<guid> fields={ "name": "Acme — Managed Services FY27",128 "expectedCloseDate": "2026-09-30T00:00:00Z" }129```130131This tool is flagged high-impact for a reason: patching `quoteStatus`, `isArchive`,132`isLost`, `expirationDate`, `orderPorter*` or the approval fields changes workflow state133and, for the Order Porter fields, what the customer sees. Confirm intent before touching134those; renaming or setting a forecast date is routine.135136### Work the customers and terms on a quote1371381. `cpq_list_quote_customers` with the quote GUID — the records are already there,139 synced from the CRM/PSA. Correct them with `cpq_update_quote_customer`.1402. `cpq_list_quote_terms` for financing options; `cpq_create_quote_term` to add one.141 `isSelected` marks the term the customer is being offered.142143### Delete144145`cpq_delete_quote` removes the quote **and every tab, line item and term on it**.146Confirmation depends on how you are connected: an interactive client is prompted, while147a non-interactive one — the gateway included — is refused until the call is re-invoked148with `confirm_destructive_action: true`. `cpq_delete_quote_version` removes a single version by149`quoteNumber` + `quoteVersion`. Neither is recoverable — archive (`isArchive: True`) is150almost always the right move for a dead deal instead.151152## Gotchas153154- **`count` in a tool result is the page length, not a total.** CPQ returns bare arrays155 with no collection count. Keep paging until a short page arrives.156- **Searching by quote number needs a number, not a string:** `quoteNumber = 1042`, not157 `quoteNumber = "1042"`. Dates go in square brackets and are date-only:158 `createDate >= [2026-07-01]`.159- **Every line item needs a tab.** A freshly copied quote has the template's tabs; a160 quote with no tabs cannot take line items, and tabs cannot be created over the API.161- **Deleting a quote version is not the same as deleting a quote.** The version delete162 targets `quoteNumber` + `quoteVersion`; the quote delete targets a GUID and takes163 everything.164- **`crmOpportunityId` is a pointer, not a join.** There are no opportunity endpoints in165 CPQ — resolve it against the PSA/CRM (the `connectwise-psa` plugin) if you need the166 opportunity itself.167- **QuoteView is huge and mostly empty.** 204 properties, 60+ of them unused `zCustom*`168 slots. Always send `includeFields` on searches or the results bury the useful fields.169170## Related Skills171172- [ConnectWise CPQ API Patterns](../api-patterns/SKILL.md) — auth, conditions syntax, paging, JSON Patch173- [ConnectWise CPQ Quote Items](../quote-items/SKILL.md) — line items, tabs, pricing and margin fields