Quotes
Priced offers built from lines — from your product catalogue or free text — with VAT on each line, an optional quote-level discount and an optional ROT/RUT estimate. A sent quote gets its own link (share_url) where the buyer can accept or reject it without a deal room; a quote placed in a deal room can also be answered by the room's stakeholders.
Totals are always computed by Closepact, in öre, by the same rules as an invoice. Every amount is rounded half up exactly once per line, and totals are sums of the rounded lines:
- a line's
line_total_centsisquantity × unit_price_centsless the line'sdiscount_percent;quantitymay have up to three decimals (2.5 hours); - its
net_centsadditionally carries the quote'sdiscount_percent(10 % on a line already 25 % off is 32.5 % off), and itsvat_centsisvat_rateof that net — 25, 12, 6 or 0, inherited from the product unless you set it; subtotal_centsis the sum of line totals,discount_centswhat the quote discount took off them,tax_centsthe sum of line VAT, andtotal_centsnet plus VAT;vat_breakdowngives net and VAT per rate.
Only lines that count are summed: every fixed line, and an optional line (is_optional) only while is_selected. The buyer ticks optional lines in or out when accepting.
rot_rut estimates a ROT (30 %) or RUT (50 %) deduction on the labour lines including VAT — materials never qualify — and what the buyer pays after it. It is preliminary: the yearly ceiling per person is not applied.
Creating and changing quotes is included from the Väx tier of proposals (proposals.cpq); on Start those operations answer 402 and existing quotes stay readable.
An accepted quote is handed over — to a work order waiting to be planned when you have the scheduling module, otherwise to an invoice draft when you have invoicing — automatically on acceptance or with Hand over an accepted quote, as each quote's handover says. That is included from the Väx tier of proposals (proposals.convert).
List quotes
/quotesReturns the organization's quotes, newest first, each with its lines, as a cursor-paginated list. Page through with cursor until meta.next_cursor is null; links.next keeps your filters.
curl 'https://api.closepact.com/v1/api/quotes' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY"{
"data": [
{
"id": "01a15c50-8d9e-7f0a-b1c2-d3e4f5a6b7c8",
"deal_id": "01a15c2e-4b7a-7f3e-9c1d-2e8f6a0b4c71",
"deal_room_id": "01a15c40-1f2e-7d9a-8b3c-4e5f6a7b8c9d",
"quote_number": "Q-00042",
"title": "Offert hemstädning – familjen Ny",
"status": "sent",
"subtotal_cents": 320000,
"discount_cents": 0,
"discount_percent": 0,
"tax_cents": 80000,
"tax_percent": 25,
"total_cents": 400000,
"vat_breakdown": [
{
"rate": 25,
"base_cents": 320000,
"vat_cents": 80000
}
],
"rot_rut": {
"type": "rut",
"rate_percent": 50,
"labour_cents": 400000,
"deduction_cents": 200000,
"customer_pays_cents": 200000,
"preliminary": true,
"annual_ceiling_applied": false
},
"currency": "SEK",
"expires_at": "2026-11-17",
"accepted_at": null,
"accepted_by_stakeholder_id": null,
"acceptance": null,
"rejection_reason": null,
"share_url": "https://app.closepact.com/q/k3Jq8vR2mXw9TzL5pN7cH4bY6sD1fG0aE8uQ3iO5rV2tW9xZ",
"handover": {
"available": true,
"target": "work_order",
"mode": "automatic",
"work_order": null,
"invoice": null,
"blocked_reason": null
},
"lines": [
{
"id": "01a15c52-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"product_id": "01a15b70-6c7d-7e8f-9a0b-1c2d3e4f5a6b",
"name": "Hemstäd varannan vecka",
"description": "Två timmar per tillfälle, RUT-avdrag tillämpas på fakturan.",
"quantity": 2,
"unit_price_cents": 160000,
"discount_percent": 0,
"vat_rate": 25,
"line_total_cents": 320000,
"net_cents": 320000,
"vat_cents": 80000,
"line_type": "labour",
"is_optional": false,
"is_selected": true,
"billing_cycle": "monthly",
"position": 0
}
],
"created_at": "2026-10-13T08:30:00+00:00",
"updated_at": "2026-10-13T09:05:12+00:00"
}
],
"links": {
"first": null,
"last": null,
"prev": null,
"next": null
},
"meta": {
"path": "https://api.closepact.com/v1/api/quotes",
"per_page": 25,
"next_cursor": null,
"prev_cursor": null
}
}Query parameters
deal_idstring(uuid)Only quotes for this deal.
deal_room_idstring(uuid)Only quotes placed in this room.
statusstringOnly quotes with this status.
One of
draftsentacceptedrejectedexpiredlimitintegerItems per page. Values outside 1–100 are clamped.
Default
251–100cursorstringOpaque cursor from
meta.next_cursorormeta.prev_cursorof the previous page.
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataarray of objectsrequiredidstring(uuid)requireddeal_idstring(uuid)nullablerequireddeal_room_idstring(uuid)nullablerequiredThe room whose stakeholders can accept the quote.
quote_numberstringrequiredSequential within the organization.
Example
"Q-00042"titlestringrequiredstatusstringrequireddraftuntil sent, and the only state whose lines and terms can change;sentonce in front of the buyer, who can accept or reject it until the end of itsexpires_atday;acceptedorrejectedby the buyer, after which it never changes;expiredwhen a sent quote's last day passed unanswered (set by an hourly sweep). A sent or expired quote goes back todraftonly through Revert to draft.One of
draftsentacceptedrejectedexpiredsubtotal_centsintegerrequiredSum of the totals of the lines that count, in öre, before the quote discount and VAT.
discount_centsintegerrequiredWhat the quote's
discount_percenttook off those lines, in öre.discount_percentintegerrequired0–100
tax_centsintegerrequiredThe VAT of the lines that count, summed — each line's rounded once. Per rate in
vat_breakdown.tax_percentintegerrequireddeprecatedDeprecated: VAT is per line (
lines[].vat_rate,vat_breakdown). Kept for older integrations: it reads as the rate every counted line shares, and is left as it was when the rates are mixed. It never changes an amount.0–100
total_centsintegerrequiredWhat the quote comes to, VAT included, in öre: 400000 is 4 000 kr. Before any ROT/RUT deduction — see
rot_rut.customer_pays_cents.vat_breakdownarray of objectsrequiredNet and VAT per rate over the lines that count, highest rate first — as an invoice has to state them.
rot_rutobjectrequiredA preliminary ROT/RUT estimate, kept apart from the total. The yearly ceiling per person is not applied — it depends on what other companies have already claimed — and Skatteverket decides after the invoice.
currencystringrequiredexactly 3 characters
expires_atstring(date)nullablerequiredLast day the quote can be accepted (inclusive).
accepted_atstring(date-time)nullablerequiredaccepted_by_stakeholder_idstring(uuid)nullablerequiredThe room stakeholder who accepted it, when it was accepted in a room.
acceptanceobjectnullablerequiredWhat the buyer agreed to, recorded once at the moment of acceptance. Null until accepted, and on quotes accepted before this was recorded.
rejection_reasonstringnullablerequiredWhat the buyer wrote when rejecting it.
share_urlstring(uri)nullablerequiredThe quote's own page, where the buyer can read it, tick options and accept or reject it without a deal room. Issued when the quote is sent, revoked when it is reverted to draft (a new one comes with the next send). Null on a draft. Anyone holding it can answer the quote, so share it only with the buyer.
handoverobjectrequiredWhat an accepted quote becomes in your organization, and what it became. With the scheduling module it becomes a work order waiting to be planned; with invoicing and no scheduling, an invoice draft.
available,targetandmodedescribe your organization and are the same on every quote;work_order,invoiceandblocked_reasondescribe this one. See Hand over an accepted quote.linesarray of objectsrequiredcreated_atstring(date-time)requiredupdated_atstring(date-time)required
linksobjectrequiredmetaobjectrequired
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 429The key has made more requests this minute than its limit allows.Errors
Create a quote
/quotesCreates an empty draft quote with the next number in the organization's series (Q-00001, Q-00002, …), then add lines with Add a line. Place it in a room with deal_room_id so the room's stakeholders can accept it once it is sent.
Not idempotent: each call creates another quote and uses up a number.
curl -X POST 'https://api.closepact.com/v1/api/quotes' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Offert hemstädning – familjen Ny",
"deal_id": "01a15c2e-4b7a-7f3e-9c1d-2e8f6a0b4c71",
"deal_room_id": "01a15c40-1f2e-7d9a-8b3c-4e5f6a7b8c9d",
"rot_rut": "rut",
"expires_at": "2026-11-17"
}'{
"data": {
"id": "01a15c50-8d9e-7f0a-b1c2-d3e4f5a6b7c8",
"deal_id": "01a15c2e-4b7a-7f3e-9c1d-2e8f6a0b4c71",
"deal_room_id": "01a15c40-1f2e-7d9a-8b3c-4e5f6a7b8c9d",
"quote_number": "Q-00042",
"title": "Offert hemstädning – familjen Ny",
"status": "draft",
"subtotal_cents": 0,
"discount_cents": 0,
"discount_percent": 0,
"tax_cents": 0,
"tax_percent": 0,
"total_cents": 0,
"vat_breakdown": [],
"rot_rut": {
"type": "rut",
"rate_percent": 50,
"labour_cents": 0,
"deduction_cents": 0,
"customer_pays_cents": 0,
"preliminary": true,
"annual_ceiling_applied": false
},
"currency": "SEK",
"expires_at": "2026-11-17",
"accepted_at": null,
"accepted_by_stakeholder_id": null,
"acceptance": null,
"rejection_reason": null,
"share_url": null,
"handover": {
"available": true,
"target": "work_order",
"mode": "automatic",
"work_order": null,
"invoice": null,
"blocked_reason": null
},
"lines": [],
"created_at": "2026-10-13T08:30:00+00:00",
"updated_at": "2026-10-13T08:30:00+00:00"
}
}Request body
application/json · required
titlestringrequiredat most 255 characters
deal_idstring(uuid)nullableA deal in this organization.
deal_room_idstring(uuid)nullableA room in this organization, whose stakeholders will see the quote once it is sent.
currencystringnullableISO 4217 code, upper case. Stored as sent.
Default
"SEK"exactly 3 charactersdiscount_percentintegernullableDiscount on the whole quote, in whole percent. Applied to each line before its VAT.
Default
00–100rot_rutstringnullableThe tax deduction to estimate on the quote's
labourlines:rot(30 %),rut(50 %) ornone.One of
nonerotrutDefault
"none"tax_percentintegernullabledeprecatedDeprecated: VAT is set per line (
vat_rateon each line). Still accepted so older integrations keep working, but it changes no amount.0–100
expires_atstring(date)nullableLast day the quote can be accepted. Must be after today.
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredidstring(uuid)requireddeal_idstring(uuid)nullablerequireddeal_room_idstring(uuid)nullablerequiredThe room whose stakeholders can accept the quote.
quote_numberstringrequiredSequential within the organization.
Example
"Q-00042"titlestringrequiredstatusstringrequireddraftuntil sent, and the only state whose lines and terms can change;sentonce in front of the buyer, who can accept or reject it until the end of itsexpires_atday;acceptedorrejectedby the buyer, after which it never changes;expiredwhen a sent quote's last day passed unanswered (set by an hourly sweep). A sent or expired quote goes back todraftonly through Revert to draft.One of
draftsentacceptedrejectedexpiredsubtotal_centsintegerrequiredSum of the totals of the lines that count, in öre, before the quote discount and VAT.
discount_centsintegerrequiredWhat the quote's
discount_percenttook off those lines, in öre.discount_percentintegerrequired0–100
tax_centsintegerrequiredThe VAT of the lines that count, summed — each line's rounded once. Per rate in
vat_breakdown.tax_percentintegerrequireddeprecatedDeprecated: VAT is per line (
lines[].vat_rate,vat_breakdown). Kept for older integrations: it reads as the rate every counted line shares, and is left as it was when the rates are mixed. It never changes an amount.0–100
total_centsintegerrequiredWhat the quote comes to, VAT included, in öre: 400000 is 4 000 kr. Before any ROT/RUT deduction — see
rot_rut.customer_pays_cents.vat_breakdownarray of objectsrequiredNet and VAT per rate over the lines that count, highest rate first — as an invoice has to state them.
rot_rutobjectrequiredA preliminary ROT/RUT estimate, kept apart from the total. The yearly ceiling per person is not applied — it depends on what other companies have already claimed — and Skatteverket decides after the invoice.
currencystringrequiredexactly 3 characters
expires_atstring(date)nullablerequiredLast day the quote can be accepted (inclusive).
accepted_atstring(date-time)nullablerequiredaccepted_by_stakeholder_idstring(uuid)nullablerequiredThe room stakeholder who accepted it, when it was accepted in a room.
acceptanceobjectnullablerequiredWhat the buyer agreed to, recorded once at the moment of acceptance. Null until accepted, and on quotes accepted before this was recorded.
rejection_reasonstringnullablerequiredWhat the buyer wrote when rejecting it.
share_urlstring(uri)nullablerequiredThe quote's own page, where the buyer can read it, tick options and accept or reject it without a deal room. Issued when the quote is sent, revoked when it is reverted to draft (a new one comes with the next send). Null on a draft. Anyone holding it can answer the quote, so share it only with the buyer.
handoverobjectrequiredWhat an accepted quote becomes in your organization, and what it became. With the scheduling module it becomes a work order waiting to be planned; with invoicing and no scheduling, an invoice draft.
available,targetandmodedescribe your organization and are the same on every quote;work_order,invoiceandblocked_reasondescribe this one. See Hand over an accepted quote.linesarray of objectsrequiredcreated_atstring(date-time)requiredupdated_atstring(date-time)required
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 422The request body or query failed validation.Errors
- 429The key has made more requests this minute than its limit allows.Errors
Retrieve a quote
/quotes/{quote}Returns one quote with its lines in order. Poll this (or listen for the quote.accepted webhook) to learn whether the buyer accepted or rejected it: status, accepted_at, acceptance and rejection_reason say so. Once accepted, acceptance records who accepted, through which door, the optional lines they ticked and the totals at that moment; the lines' is_selected and the quote's totals reflect the same choice.
curl 'https://api.closepact.com/v1/api/quotes/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY"{
"data": {
"id": "01a15c50-8d9e-7f0a-b1c2-d3e4f5a6b7c8",
"deal_id": "01a15c2e-4b7a-7f3e-9c1d-2e8f6a0b4c71",
"deal_room_id": "01a15c40-1f2e-7d9a-8b3c-4e5f6a7b8c9d",
"quote_number": "Q-00042",
"title": "Offert hemstädning – familjen Ny",
"status": "accepted",
"subtotal_cents": 387500,
"discount_cents": 0,
"discount_percent": 0,
"tax_cents": 96875,
"tax_percent": 25,
"total_cents": 484375,
"vat_breakdown": [
{
"rate": 25,
"base_cents": 387500,
"vat_cents": 96875
}
],
"rot_rut": {
"type": "rut",
"rate_percent": 50,
"labour_cents": 484375,
"deduction_cents": 242188,
"customer_pays_cents": 242187,
"preliminary": true,
"annual_ceiling_applied": false
},
"currency": "SEK",
"expires_at": "2026-11-17",
"accepted_at": "2026-10-15T19:44:02+00:00",
"accepted_by_stakeholder_id": "01a15c47-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
"acceptance": {
"via": "room",
"name": "Anna Ny",
"email": "anna.ny@example.se",
"selected_optional_line_ids": [
"01a15c53-2b3c-7d4e-8f5a-6b7c8d9e0f1b"
],
"total_cents": 484375,
"tax_cents": 96875,
"customer_pays_cents": 242187
},
"rejection_reason": null,
"share_url": "https://app.closepact.com/q/k3Jq8vR2mXw9TzL5pN7cH4bY6sD1fG0aE8uQ3iO5rV2tW9xZ",
"handover": {
"available": true,
"target": "work_order",
"mode": "automatic",
"work_order": {
"id": "01a1c0b2-7d4e-7a19-8c35-2f9e6b1d4a07",
"order_number": "AO-10483",
"status": "booked"
},
"invoice": null,
"blocked_reason": null
},
"lines": [
{
"id": "01a15c52-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"product_id": "01a15b70-6c7d-7e8f-9a0b-1c2d3e4f5a6b",
"name": "Hemstäd varannan vecka",
"description": "Två timmar per tillfälle, RUT-avdrag tillämpas på fakturan.",
"quantity": 2,
"unit_price_cents": 160000,
"discount_percent": 0,
"vat_rate": 25,
"line_total_cents": 320000,
"net_cents": 320000,
"vat_cents": 80000,
"line_type": "labour",
"is_optional": false,
"is_selected": true,
"billing_cycle": "monthly",
"position": 0
},
{
"id": "01a15c53-2b3c-7d4e-8f5a-6b7c8d9e0f1b",
"product_id": null,
"name": "Fönsterputs",
"description": "Tillval: alla fönster, in- och utsida.",
"quantity": 1.5,
"unit_price_cents": 45000,
"discount_percent": 0,
"vat_rate": 25,
"line_total_cents": 67500,
"net_cents": 67500,
"vat_cents": 16875,
"line_type": "labour",
"is_optional": true,
"is_selected": true,
"billing_cycle": null,
"position": 1
}
],
"created_at": "2026-10-13T08:30:00+00:00",
"updated_at": "2026-10-15T19:44:02+00:00"
}
}Path parameters
quotestring(uuid)requiredThe quote's id.
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredidstring(uuid)requireddeal_idstring(uuid)nullablerequireddeal_room_idstring(uuid)nullablerequiredThe room whose stakeholders can accept the quote.
quote_numberstringrequiredSequential within the organization.
Example
"Q-00042"titlestringrequiredstatusstringrequireddraftuntil sent, and the only state whose lines and terms can change;sentonce in front of the buyer, who can accept or reject it until the end of itsexpires_atday;acceptedorrejectedby the buyer, after which it never changes;expiredwhen a sent quote's last day passed unanswered (set by an hourly sweep). A sent or expired quote goes back todraftonly through Revert to draft.One of
draftsentacceptedrejectedexpiredsubtotal_centsintegerrequiredSum of the totals of the lines that count, in öre, before the quote discount and VAT.
discount_centsintegerrequiredWhat the quote's
discount_percenttook off those lines, in öre.discount_percentintegerrequired0–100
tax_centsintegerrequiredThe VAT of the lines that count, summed — each line's rounded once. Per rate in
vat_breakdown.tax_percentintegerrequireddeprecatedDeprecated: VAT is per line (
lines[].vat_rate,vat_breakdown). Kept for older integrations: it reads as the rate every counted line shares, and is left as it was when the rates are mixed. It never changes an amount.0–100
total_centsintegerrequiredWhat the quote comes to, VAT included, in öre: 400000 is 4 000 kr. Before any ROT/RUT deduction — see
rot_rut.customer_pays_cents.vat_breakdownarray of objectsrequiredNet and VAT per rate over the lines that count, highest rate first — as an invoice has to state them.
rot_rutobjectrequiredA preliminary ROT/RUT estimate, kept apart from the total. The yearly ceiling per person is not applied — it depends on what other companies have already claimed — and Skatteverket decides after the invoice.
currencystringrequiredexactly 3 characters
expires_atstring(date)nullablerequiredLast day the quote can be accepted (inclusive).
accepted_atstring(date-time)nullablerequiredaccepted_by_stakeholder_idstring(uuid)nullablerequiredThe room stakeholder who accepted it, when it was accepted in a room.
acceptanceobjectnullablerequiredWhat the buyer agreed to, recorded once at the moment of acceptance. Null until accepted, and on quotes accepted before this was recorded.
rejection_reasonstringnullablerequiredWhat the buyer wrote when rejecting it.
share_urlstring(uri)nullablerequiredThe quote's own page, where the buyer can read it, tick options and accept or reject it without a deal room. Issued when the quote is sent, revoked when it is reverted to draft (a new one comes with the next send). Null on a draft. Anyone holding it can answer the quote, so share it only with the buyer.
handoverobjectrequiredWhat an accepted quote becomes in your organization, and what it became. With the scheduling module it becomes a work order waiting to be planned; with invoicing and no scheduling, an invoice draft.
available,targetandmodedescribe your organization and are the same on every quote;work_order,invoiceandblocked_reasondescribe this one. See Hand over an accepted quote.linesarray of objectsrequiredcreated_atstring(date-time)requiredupdated_atstring(date-time)required
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 404No such record in this organization.Errors
- 429The key has made more requests this minute than its limit allows.Errors
Update a quote
/quotes/{quote}Changes the title, quote-level discount, ROT/RUT estimate or expiry, and recomputes the totals. Fields you leave out are kept. VAT is set on each line, not here.
Only a draft can change. On a sent or expired quote only expires_at may be sent — extending an offer does not change what the buyer would accept — and anything else answers 409 quote_not_draft: revert it to draft first, which withdraws the offer until you send it again. An accepted or rejected quote cannot change at all (409 quote_locked); create a new one instead.
curl -X PATCH 'https://api.closepact.com/v1/api/quotes/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"discount_percent": 10
}'{
"data": {
"id": "01a15c50-8d9e-7f0a-b1c2-d3e4f5a6b7c8",
"deal_id": "01a15c2e-4b7a-7f3e-9c1d-2e8f6a0b4c71",
"deal_room_id": "01a15c40-1f2e-7d9a-8b3c-4e5f6a7b8c9d",
"quote_number": "Q-00042",
"title": "Offert hemstädning – familjen Ny",
"status": "draft",
"subtotal_cents": 320000,
"discount_cents": 32000,
"discount_percent": 10,
"tax_cents": 72000,
"tax_percent": 25,
"total_cents": 360000,
"vat_breakdown": [
{
"rate": 25,
"base_cents": 288000,
"vat_cents": 72000
}
],
"rot_rut": {
"type": "rut",
"rate_percent": 50,
"labour_cents": 360000,
"deduction_cents": 180000,
"customer_pays_cents": 180000,
"preliminary": true,
"annual_ceiling_applied": false
},
"currency": "SEK",
"expires_at": "2026-11-17",
"accepted_at": null,
"accepted_by_stakeholder_id": null,
"acceptance": null,
"rejection_reason": null,
"share_url": null,
"handover": {
"available": true,
"target": "work_order",
"mode": "automatic",
"work_order": null,
"invoice": null,
"blocked_reason": null
},
"lines": [
{
"id": "01a15c52-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"product_id": "01a15b70-6c7d-7e8f-9a0b-1c2d3e4f5a6b",
"name": "Hemstäd varannan vecka",
"description": null,
"quantity": 2,
"unit_price_cents": 160000,
"discount_percent": 0,
"vat_rate": 25,
"line_total_cents": 320000,
"net_cents": 288000,
"vat_cents": 72000,
"line_type": "labour",
"is_optional": false,
"is_selected": true,
"billing_cycle": "monthly",
"position": 0
}
],
"created_at": "2026-10-13T08:30:00+00:00",
"updated_at": "2026-10-13T08:52:19+00:00"
}
}Path parameters
quotestring(uuid)requiredThe quote's id.
Request body
application/json · required
titlestringat most 255 characters
discount_percentinteger0–100
rot_rutstringThe deduction to estimate:
rot(30 %),rut(50 %) ornone.One of
nonerotruttax_percentintegerdeprecatedDeprecated: VAT is set per line. Accepted for older integrations, but it changes no amount.
0–100
expires_atstring(date)nullableLast day the quote can be accepted, or null for no limit. Moving it does not change the status of a quote that has already expired.
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredidstring(uuid)requireddeal_idstring(uuid)nullablerequireddeal_room_idstring(uuid)nullablerequiredThe room whose stakeholders can accept the quote.
quote_numberstringrequiredSequential within the organization.
Example
"Q-00042"titlestringrequiredstatusstringrequireddraftuntil sent, and the only state whose lines and terms can change;sentonce in front of the buyer, who can accept or reject it until the end of itsexpires_atday;acceptedorrejectedby the buyer, after which it never changes;expiredwhen a sent quote's last day passed unanswered (set by an hourly sweep). A sent or expired quote goes back todraftonly through Revert to draft.One of
draftsentacceptedrejectedexpiredsubtotal_centsintegerrequiredSum of the totals of the lines that count, in öre, before the quote discount and VAT.
discount_centsintegerrequiredWhat the quote's
discount_percenttook off those lines, in öre.discount_percentintegerrequired0–100
tax_centsintegerrequiredThe VAT of the lines that count, summed — each line's rounded once. Per rate in
vat_breakdown.tax_percentintegerrequireddeprecatedDeprecated: VAT is per line (
lines[].vat_rate,vat_breakdown). Kept for older integrations: it reads as the rate every counted line shares, and is left as it was when the rates are mixed. It never changes an amount.0–100
total_centsintegerrequiredWhat the quote comes to, VAT included, in öre: 400000 is 4 000 kr. Before any ROT/RUT deduction — see
rot_rut.customer_pays_cents.vat_breakdownarray of objectsrequiredNet and VAT per rate over the lines that count, highest rate first — as an invoice has to state them.
rot_rutobjectrequiredA preliminary ROT/RUT estimate, kept apart from the total. The yearly ceiling per person is not applied — it depends on what other companies have already claimed — and Skatteverket decides after the invoice.
currencystringrequiredexactly 3 characters
expires_atstring(date)nullablerequiredLast day the quote can be accepted (inclusive).
accepted_atstring(date-time)nullablerequiredaccepted_by_stakeholder_idstring(uuid)nullablerequiredThe room stakeholder who accepted it, when it was accepted in a room.
acceptanceobjectnullablerequiredWhat the buyer agreed to, recorded once at the moment of acceptance. Null until accepted, and on quotes accepted before this was recorded.
rejection_reasonstringnullablerequiredWhat the buyer wrote when rejecting it.
share_urlstring(uri)nullablerequiredThe quote's own page, where the buyer can read it, tick options and accept or reject it without a deal room. Issued when the quote is sent, revoked when it is reverted to draft (a new one comes with the next send). Null on a draft. Anyone holding it can answer the quote, so share it only with the buyer.
handoverobjectrequiredWhat an accepted quote becomes in your organization, and what it became. With the scheduling module it becomes a work order waiting to be planned; with invoicing and no scheduling, an invoice draft.
available,targetandmodedescribe your organization and are the same on every quote;work_order,invoiceandblocked_reasondescribe this one. See Hand over an accepted quote.linesarray of objectsrequiredcreated_atstring(date-time)requiredupdated_atstring(date-time)required
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 404No such record in this organization.Errors
- 409The quote's state does not allow this.Errors
errorsays which:quote_not_draft— it has been sent; revert it to draft before changing it;quote_locked— it has been accepted or rejected and can no longer change;quote_expired— its last valid day has passed;quote_not_sent,quote_already_accepted,quote_rejected— it is not open for the buyer's answer;quote_not_accepted— only an accepted quote can be handed over;handover_unavailable— the organization has neither the scheduling nor the invoicing module, so an accepted quote has nothing to become.messagesays the same for a person. - 422The request body or query failed validation.Errors
- 429The key has made more requests this minute than its limit allows.Errors
Delete a quote
/quotes/{quote}Deletes the quote: it leaves lists and lookups at once and a later request for it is a 404. Its number is not reused. No webhook fires.
An accepted quote cannot be deleted (409 quote_locked): it is the record of what the buyer agreed to.
curl -X DELETE 'https://api.closepact.com/v1/api/quotes/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY"204 — no response bodyPath parameters
quotestring(uuid)requiredThe quote's id.
Responses
- 204The quote was deleted. No body.
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 404No such record in this organization.Errors
- 409The quote's state does not allow this.Errors
errorsays which:quote_not_draft— it has been sent; revert it to draft before changing it;quote_locked— it has been accepted or rejected and can no longer change;quote_expired— its last valid day has passed;quote_not_sent,quote_already_accepted,quote_rejected— it is not open for the buyer's answer;quote_not_accepted— only an accepted quote can be handed over;handover_unavailable— the organization has neither the scheduling nor the invoicing module, so an accepted quote has nothing to become.messagesays the same for a person. - 429The key has made more requests this minute than its limit allows.Errors
Send a quote
/quotes/{quote}/sendMarks a draft quote sent and gives it its own link, share_url: the buyer opens the quote there, ticks the options they want and accepts or rejects it — no deal room needed. A sent quote in a room also appears on the room's buyer page. Only a sent quote can be accepted or rejected, and only until the end of its expires_at day; acceptance fires the quote.accepted webhook.
With email, the link is e-mailed to that address (with name and message in the letter). Without it nothing is mailed — copy share_url into your own message, or rely on the room. Calling this again on a sent quote keeps its link and can mail it to someone else.
A draft whose expires_at has already passed answers 409 quote_expired: move the date first. Asking to e-mail a quote that can no longer be answered (accepted, rejected or past its expiry) answers 409 too. Otherwise idempotent: a quote that is not a draft is returned unchanged. The body is optional.
curl -X POST 'https://api.closepact.com/v1/api/quotes/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40/send' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "anna.ny@example.se",
"name": "Anna Ny",
"message": "Hej Anna! Här är offerten vi pratade om."
}'{
"data": {
"id": "01a15c50-8d9e-7f0a-b1c2-d3e4f5a6b7c8",
"deal_id": "01a15c2e-4b7a-7f3e-9c1d-2e8f6a0b4c71",
"deal_room_id": "01a15c40-1f2e-7d9a-8b3c-4e5f6a7b8c9d",
"quote_number": "Q-00042",
"title": "Offert hemstädning – familjen Ny",
"status": "sent",
"subtotal_cents": 320000,
"discount_cents": 0,
"discount_percent": 0,
"tax_cents": 80000,
"tax_percent": 25,
"total_cents": 400000,
"vat_breakdown": [
{
"rate": 25,
"base_cents": 320000,
"vat_cents": 80000
}
],
"rot_rut": {
"type": "rut",
"rate_percent": 50,
"labour_cents": 400000,
"deduction_cents": 200000,
"customer_pays_cents": 200000,
"preliminary": true,
"annual_ceiling_applied": false
},
"currency": "SEK",
"expires_at": "2026-11-17",
"accepted_at": null,
"accepted_by_stakeholder_id": null,
"acceptance": null,
"rejection_reason": null,
"share_url": "https://app.closepact.com/q/k3Jq8vR2mXw9TzL5pN7cH4bY6sD1fG0aE8uQ3iO5rV2tW9xZ",
"handover": {
"available": true,
"target": "work_order",
"mode": "automatic",
"work_order": null,
"invoice": null,
"blocked_reason": null
},
"lines": [
{
"id": "01a15c52-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"product_id": "01a15b70-6c7d-7e8f-9a0b-1c2d3e4f5a6b",
"name": "Hemstäd varannan vecka",
"description": null,
"quantity": 2,
"unit_price_cents": 160000,
"discount_percent": 0,
"vat_rate": 25,
"line_total_cents": 320000,
"net_cents": 320000,
"vat_cents": 80000,
"line_type": "labour",
"is_optional": false,
"is_selected": true,
"billing_cycle": "monthly",
"position": 0
}
],
"created_at": "2026-10-13T08:30:00+00:00",
"updated_at": "2026-10-13T09:05:12+00:00"
}
}Path parameters
quotestring(uuid)requiredThe quote's id.
Request body
application/json
emailstring(email)nullableMail the quote's link to this address.
at most 255 characters
namestringnullableThe recipient's name, for the greeting.
at most 120 characters
messagestringnullableA note quoted in the letter.
at most 2,000 characters
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredidstring(uuid)requireddeal_idstring(uuid)nullablerequireddeal_room_idstring(uuid)nullablerequiredThe room whose stakeholders can accept the quote.
quote_numberstringrequiredSequential within the organization.
Example
"Q-00042"titlestringrequiredstatusstringrequireddraftuntil sent, and the only state whose lines and terms can change;sentonce in front of the buyer, who can accept or reject it until the end of itsexpires_atday;acceptedorrejectedby the buyer, after which it never changes;expiredwhen a sent quote's last day passed unanswered (set by an hourly sweep). A sent or expired quote goes back todraftonly through Revert to draft.One of
draftsentacceptedrejectedexpiredsubtotal_centsintegerrequiredSum of the totals of the lines that count, in öre, before the quote discount and VAT.
discount_centsintegerrequiredWhat the quote's
discount_percenttook off those lines, in öre.discount_percentintegerrequired0–100
tax_centsintegerrequiredThe VAT of the lines that count, summed — each line's rounded once. Per rate in
vat_breakdown.tax_percentintegerrequireddeprecatedDeprecated: VAT is per line (
lines[].vat_rate,vat_breakdown). Kept for older integrations: it reads as the rate every counted line shares, and is left as it was when the rates are mixed. It never changes an amount.0–100
total_centsintegerrequiredWhat the quote comes to, VAT included, in öre: 400000 is 4 000 kr. Before any ROT/RUT deduction — see
rot_rut.customer_pays_cents.vat_breakdownarray of objectsrequiredNet and VAT per rate over the lines that count, highest rate first — as an invoice has to state them.
rot_rutobjectrequiredA preliminary ROT/RUT estimate, kept apart from the total. The yearly ceiling per person is not applied — it depends on what other companies have already claimed — and Skatteverket decides after the invoice.
currencystringrequiredexactly 3 characters
expires_atstring(date)nullablerequiredLast day the quote can be accepted (inclusive).
accepted_atstring(date-time)nullablerequiredaccepted_by_stakeholder_idstring(uuid)nullablerequiredThe room stakeholder who accepted it, when it was accepted in a room.
acceptanceobjectnullablerequiredWhat the buyer agreed to, recorded once at the moment of acceptance. Null until accepted, and on quotes accepted before this was recorded.
rejection_reasonstringnullablerequiredWhat the buyer wrote when rejecting it.
share_urlstring(uri)nullablerequiredThe quote's own page, where the buyer can read it, tick options and accept or reject it without a deal room. Issued when the quote is sent, revoked when it is reverted to draft (a new one comes with the next send). Null on a draft. Anyone holding it can answer the quote, so share it only with the buyer.
handoverobjectrequiredWhat an accepted quote becomes in your organization, and what it became. With the scheduling module it becomes a work order waiting to be planned; with invoicing and no scheduling, an invoice draft.
available,targetandmodedescribe your organization and are the same on every quote;work_order,invoiceandblocked_reasondescribe this one. See Hand over an accepted quote.linesarray of objectsrequiredcreated_atstring(date-time)requiredupdated_atstring(date-time)required
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 404No such record in this organization.Errors
- 409The quote's state does not allow this.Errors
errorsays which:quote_not_draft— it has been sent; revert it to draft before changing it;quote_locked— it has been accepted or rejected and can no longer change;quote_expired— its last valid day has passed;quote_not_sent,quote_already_accepted,quote_rejected— it is not open for the buyer's answer;quote_not_accepted— only an accepted quote can be handed over;handover_unavailable— the organization has neither the scheduling nor the invoicing module, so an accepted quote has nothing to become.messagesays the same for a person. - 422The request body or query failed validation.Errors
- 429The key has made more requests this minute than its limit allows.Errors
Revert a quote to draft
/quotes/{quote}/revert-to-draftTakes a sent or expired quote back to draft so its lines and terms can be changed. This withdraws the offer: a draft cannot be accepted, so it leaves the room's buyer page and its share_url stops working, until you send the revised quote again — with a new link, so nobody can accept the revision from a link to the old offer.
An accepted or rejected quote cannot be reverted (409 quote_locked). Idempotent on a draft. No request body.
curl -X POST 'https://api.closepact.com/v1/api/quotes/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40/revert-to-draft' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY"{
"data": {
"id": "01a15c50-8d9e-7f0a-b1c2-d3e4f5a6b7c8",
"deal_id": "01a15c2e-4b7a-7f3e-9c1d-2e8f6a0b4c71",
"deal_room_id": "01a15c40-1f2e-7d9a-8b3c-4e5f6a7b8c9d",
"quote_number": "Q-00042",
"title": "Offert hemstädning – familjen Ny",
"status": "draft",
"subtotal_cents": 320000,
"discount_cents": 0,
"discount_percent": 0,
"tax_cents": 80000,
"tax_percent": 25,
"total_cents": 400000,
"vat_breakdown": [
{
"rate": 25,
"base_cents": 320000,
"vat_cents": 80000
}
],
"rot_rut": {
"type": "rut",
"rate_percent": 50,
"labour_cents": 400000,
"deduction_cents": 200000,
"customer_pays_cents": 200000,
"preliminary": true,
"annual_ceiling_applied": false
},
"currency": "SEK",
"expires_at": "2026-11-17",
"accepted_at": null,
"accepted_by_stakeholder_id": null,
"acceptance": null,
"rejection_reason": null,
"share_url": null,
"handover": {
"available": true,
"target": "work_order",
"mode": "automatic",
"work_order": null,
"invoice": null,
"blocked_reason": null
},
"lines": [
{
"id": "01a15c52-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"product_id": "01a15b70-6c7d-7e8f-9a0b-1c2d3e4f5a6b",
"name": "Hemstäd varannan vecka",
"description": null,
"quantity": 2,
"unit_price_cents": 160000,
"discount_percent": 0,
"vat_rate": 25,
"line_total_cents": 320000,
"net_cents": 320000,
"vat_cents": 80000,
"line_type": "labour",
"is_optional": false,
"is_selected": true,
"billing_cycle": "monthly",
"position": 0
}
],
"created_at": "2026-10-13T08:30:00+00:00",
"updated_at": "2026-10-14T10:12:40+00:00"
}
}Path parameters
quotestring(uuid)requiredThe quote's id.
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredidstring(uuid)requireddeal_idstring(uuid)nullablerequireddeal_room_idstring(uuid)nullablerequiredThe room whose stakeholders can accept the quote.
quote_numberstringrequiredSequential within the organization.
Example
"Q-00042"titlestringrequiredstatusstringrequireddraftuntil sent, and the only state whose lines and terms can change;sentonce in front of the buyer, who can accept or reject it until the end of itsexpires_atday;acceptedorrejectedby the buyer, after which it never changes;expiredwhen a sent quote's last day passed unanswered (set by an hourly sweep). A sent or expired quote goes back todraftonly through Revert to draft.One of
draftsentacceptedrejectedexpiredsubtotal_centsintegerrequiredSum of the totals of the lines that count, in öre, before the quote discount and VAT.
discount_centsintegerrequiredWhat the quote's
discount_percenttook off those lines, in öre.discount_percentintegerrequired0–100
tax_centsintegerrequiredThe VAT of the lines that count, summed — each line's rounded once. Per rate in
vat_breakdown.tax_percentintegerrequireddeprecatedDeprecated: VAT is per line (
lines[].vat_rate,vat_breakdown). Kept for older integrations: it reads as the rate every counted line shares, and is left as it was when the rates are mixed. It never changes an amount.0–100
total_centsintegerrequiredWhat the quote comes to, VAT included, in öre: 400000 is 4 000 kr. Before any ROT/RUT deduction — see
rot_rut.customer_pays_cents.vat_breakdownarray of objectsrequiredNet and VAT per rate over the lines that count, highest rate first — as an invoice has to state them.
rot_rutobjectrequiredA preliminary ROT/RUT estimate, kept apart from the total. The yearly ceiling per person is not applied — it depends on what other companies have already claimed — and Skatteverket decides after the invoice.
currencystringrequiredexactly 3 characters
expires_atstring(date)nullablerequiredLast day the quote can be accepted (inclusive).
accepted_atstring(date-time)nullablerequiredaccepted_by_stakeholder_idstring(uuid)nullablerequiredThe room stakeholder who accepted it, when it was accepted in a room.
acceptanceobjectnullablerequiredWhat the buyer agreed to, recorded once at the moment of acceptance. Null until accepted, and on quotes accepted before this was recorded.
rejection_reasonstringnullablerequiredWhat the buyer wrote when rejecting it.
share_urlstring(uri)nullablerequiredThe quote's own page, where the buyer can read it, tick options and accept or reject it without a deal room. Issued when the quote is sent, revoked when it is reverted to draft (a new one comes with the next send). Null on a draft. Anyone holding it can answer the quote, so share it only with the buyer.
handoverobjectrequiredWhat an accepted quote becomes in your organization, and what it became. With the scheduling module it becomes a work order waiting to be planned; with invoicing and no scheduling, an invoice draft.
available,targetandmodedescribe your organization and are the same on every quote;work_order,invoiceandblocked_reasondescribe this one. See Hand over an accepted quote.linesarray of objectsrequiredcreated_atstring(date-time)requiredupdated_atstring(date-time)required
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 404No such record in this organization.Errors
- 409The quote's state does not allow this.Errors
errorsays which:quote_not_draft— it has been sent; revert it to draft before changing it;quote_locked— it has been accepted or rejected and can no longer change;quote_expired— its last valid day has passed;quote_not_sent,quote_already_accepted,quote_rejected— it is not open for the buyer's answer;quote_not_accepted— only an accepted quote can be handed over;handover_unavailable— the organization has neither the scheduling nor the invoicing module, so an accepted quote has nothing to become.messagesays the same for a person. - 422The request body or query failed validation.Errors
- 429The key has made more requests this minute than its limit allows.Errors
Hand over an accepted quote
/quotes/{quote}/convertwrite Module Proposals & e-signing Any permission of booking.work_orders.manage,invoicing.invoices.manageTurns an accepted quote into what your organization does with one: a work order waiting to be planned when you have the scheduling module, otherwise an invoice draft when you have the invoicing module — the panel's Create job or Create invoice draft button. Usually it happens by itself the moment the buyer accepts; call this when your organization hands quotes over manually (handover.mode is manual), or when acceptance could not do it (handover.blocked_reason).
What the buyer agreed to is carried over as agreed: the customer, the contact who accepted, the title, the lines they accepted (optional lines only if they ticked them) at the quoted quantities, prices, discounts and VAT rates, and the ROT/RUT choice. A line made from a product linked to a service (service_id on the product) becomes that service on the work order; other work becomes a service line of free text and materials become material lines. The work order is booked with no time on it, at the customer's default service address. An invoice draft has no number and is never sent by this call. What could not be filled in is listed in the work order's or draft's handover.warnings.
A quote is handed over once. If it already was — by acceptance, or an earlier call — the answer is 200 with the same quote and nothing new is made; a work order deleted afterwards can be made again with this call. handover in the response says what the quote became.
Needs the quote-to-job capability (proposals.convert, proposals Väx and Pro), and the member the key acts as must be allowed to create work orders (booking.work_orders.manage) or, where quotes become invoices, invoice drafts (invoicing.invoices.manage). Refused with 409 quote_not_accepted for a quote that is not accepted and handover_unavailable without either module; with 422 on account_id when the quote is tied to no customer and none was given.
curl -X POST 'https://api.closepact.com/v1/api/quotes/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40/convert' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"account_id": "01a15a77-0d3b-7e21-a6f9-3c8e2b1d4a05"
}'{
"data": {
"id": "01a1bf90-2c6a-7e45-9d18-4b7f3a0e6c21",
"deal_id": "01a15c2e-4b7a-7f3e-9c1d-2e8f6a0b4c71",
"deal_room_id": null,
"quote_number": "Q-2026-0142",
"title": "Fönsterbyte Solna – offert",
"status": "accepted",
"subtotal_cents": 1200000,
"discount_cents": 0,
"discount_percent": 0,
"tax_cents": 300000,
"tax_percent": 25,
"total_cents": 1500000,
"vat_breakdown": [
{
"rate": 25,
"base_cents": 1200000,
"vat_cents": 300000
}
],
"rot_rut": {
"type": "none",
"rate_percent": null,
"labour_cents": 0,
"deduction_cents": 0,
"customer_pays_cents": 1500000,
"preliminary": true,
"annual_ceiling_applied": false
},
"currency": "SEK",
"expires_at": "2026-11-15",
"accepted_at": "2026-10-18T08:41:12+00:00",
"accepted_by_stakeholder_id": null,
"acceptance": {
"via": "link",
"name": "Eva Ek",
"email": "eva.ek@example.se",
"selected_optional_line_ids": [],
"total_cents": 1500000,
"tax_cents": 300000,
"customer_pays_cents": 1500000
},
"rejection_reason": null,
"share_url": "https://app.closepact.com/q/k3Jq8vR2mXw9TzL5pN7cH4bY6sD1fG0aE8uQ3iO5rV2tW9xZ",
"handover": {
"available": true,
"target": "invoice",
"mode": "automatic",
"work_order": null,
"invoice": {
"id": "01a1c1e8-5a3d-7b62-9f40-8c2e1d7b0a95",
"status": "draft",
"reference": "Utkast"
},
"blocked_reason": null
},
"lines": [],
"created_at": "2026-10-13T08:30:00+00:00",
"updated_at": "2026-10-18T08:41:12+00:00"
}
}Path parameters
quotestring(uuid)requiredThe quote's id.
Request body
application/json
account_idstring(uuid)nullableThe customer the work order or draft is for. Defaults to the quote's: the customer of its deal (or its room's deal), else the customer of the contact who accepted. Required when the quote has none.
service_address_idstring(uuid)nullableOne of the customer's addresses, where the work happens. Defaults to the customer's default service address, or its only one.
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredidstring(uuid)requireddeal_idstring(uuid)nullablerequireddeal_room_idstring(uuid)nullablerequiredThe room whose stakeholders can accept the quote.
quote_numberstringrequiredSequential within the organization.
Example
"Q-00042"titlestringrequiredstatusstringrequireddraftuntil sent, and the only state whose lines and terms can change;sentonce in front of the buyer, who can accept or reject it until the end of itsexpires_atday;acceptedorrejectedby the buyer, after which it never changes;expiredwhen a sent quote's last day passed unanswered (set by an hourly sweep). A sent or expired quote goes back todraftonly through Revert to draft.One of
draftsentacceptedrejectedexpiredsubtotal_centsintegerrequiredSum of the totals of the lines that count, in öre, before the quote discount and VAT.
discount_centsintegerrequiredWhat the quote's
discount_percenttook off those lines, in öre.discount_percentintegerrequired0–100
tax_centsintegerrequiredThe VAT of the lines that count, summed — each line's rounded once. Per rate in
vat_breakdown.tax_percentintegerrequireddeprecatedDeprecated: VAT is per line (
lines[].vat_rate,vat_breakdown). Kept for older integrations: it reads as the rate every counted line shares, and is left as it was when the rates are mixed. It never changes an amount.0–100
total_centsintegerrequiredWhat the quote comes to, VAT included, in öre: 400000 is 4 000 kr. Before any ROT/RUT deduction — see
rot_rut.customer_pays_cents.vat_breakdownarray of objectsrequiredNet and VAT per rate over the lines that count, highest rate first — as an invoice has to state them.
rot_rutobjectrequiredA preliminary ROT/RUT estimate, kept apart from the total. The yearly ceiling per person is not applied — it depends on what other companies have already claimed — and Skatteverket decides after the invoice.
currencystringrequiredexactly 3 characters
expires_atstring(date)nullablerequiredLast day the quote can be accepted (inclusive).
accepted_atstring(date-time)nullablerequiredaccepted_by_stakeholder_idstring(uuid)nullablerequiredThe room stakeholder who accepted it, when it was accepted in a room.
acceptanceobjectnullablerequiredWhat the buyer agreed to, recorded once at the moment of acceptance. Null until accepted, and on quotes accepted before this was recorded.
rejection_reasonstringnullablerequiredWhat the buyer wrote when rejecting it.
share_urlstring(uri)nullablerequiredThe quote's own page, where the buyer can read it, tick options and accept or reject it without a deal room. Issued when the quote is sent, revoked when it is reverted to draft (a new one comes with the next send). Null on a draft. Anyone holding it can answer the quote, so share it only with the buyer.
handoverobjectrequiredWhat an accepted quote becomes in your organization, and what it became. With the scheduling module it becomes a work order waiting to be planned; with invoicing and no scheduling, an invoice draft.
available,targetandmodedescribe your organization and are the same on every quote;work_order,invoiceandblocked_reasondescribe this one. See Hand over an accepted quote.linesarray of objectsrequiredcreated_atstring(date-time)requiredupdated_atstring(date-time)required
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 404No such record in this organization.Errors
- 409The quote's state does not allow this.Errors
errorsays which:quote_not_draft— it has been sent; revert it to draft before changing it;quote_locked— it has been accepted or rejected and can no longer change;quote_expired— its last valid day has passed;quote_not_sent,quote_already_accepted,quote_rejected— it is not open for the buyer's answer;quote_not_accepted— only an accepted quote can be handed over;handover_unavailable— the organization has neither the scheduling nor the invoicing module, so an accepted quote has nothing to become.messagesays the same for a person. - 422The request body or query failed validation.Errors
- 429The key has made more requests this minute than its limit allows.Errors
Add a line
/quotes/{quote}/linesAppends a line to the quote and recomputes its totals. Answers with the whole quote.
With product_id, the line starts from that catalogue product: its name, description, unit price, VAT rate and billing cycle are copied onto the line (a snapshot — later changes to the product do not reach the quote), and anything you send overrides them. Without it, name is required, the price defaults to 0 and the VAT rate to 25.
An optional line (is_optional: true) is priced like any other but counts in the totals only while is_selected; it starts unticked unless you send is_selected: true to suggest it. The buyer makes the final choice when accepting.
Only a draft's lines change: on a sent or expired quote this answers 409 quote_not_draft (revert it to draft first), on an accepted or rejected one 409 quote_locked.
Not idempotent: each call adds another line.
curl -X POST 'https://api.closepact.com/v1/api/quotes/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40/lines' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"product_id": "01a15b70-6c7d-7e8f-9a0b-1c2d3e4f5a6b",
"quantity": 2,
"line_type": "labour"
}'{
"data": {
"id": "01a15c50-8d9e-7f0a-b1c2-d3e4f5a6b7c8",
"deal_id": "01a15c2e-4b7a-7f3e-9c1d-2e8f6a0b4c71",
"deal_room_id": "01a15c40-1f2e-7d9a-8b3c-4e5f6a7b8c9d",
"quote_number": "Q-00042",
"title": "Offert hemstädning – familjen Ny",
"status": "draft",
"subtotal_cents": 320000,
"discount_cents": 0,
"discount_percent": 0,
"tax_cents": 80000,
"tax_percent": 25,
"total_cents": 400000,
"vat_breakdown": [
{
"rate": 25,
"base_cents": 320000,
"vat_cents": 80000
}
],
"rot_rut": {
"type": "rut",
"rate_percent": 50,
"labour_cents": 400000,
"deduction_cents": 200000,
"customer_pays_cents": 200000,
"preliminary": true,
"annual_ceiling_applied": false
},
"currency": "SEK",
"expires_at": "2026-11-17",
"accepted_at": null,
"accepted_by_stakeholder_id": null,
"acceptance": null,
"rejection_reason": null,
"share_url": null,
"handover": {
"available": true,
"target": "work_order",
"mode": "automatic",
"work_order": null,
"invoice": null,
"blocked_reason": null
},
"lines": [
{
"id": "01a15c52-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"product_id": "01a15b70-6c7d-7e8f-9a0b-1c2d3e4f5a6b",
"name": "Hemstäd varannan vecka",
"description": null,
"quantity": 2,
"unit_price_cents": 160000,
"discount_percent": 0,
"vat_rate": 25,
"line_total_cents": 320000,
"net_cents": 320000,
"vat_cents": 80000,
"line_type": "labour",
"is_optional": false,
"is_selected": true,
"billing_cycle": "monthly",
"position": 0
}
],
"created_at": "2026-10-13T08:30:00+00:00",
"updated_at": "2026-10-13T08:41:57+00:00"
}
}Path parameters
quotestring(uuid)requiredThe quote's id.
Request body
application/json · required
product_idstring(uuid)nullableA product in this organization's catalogue.
namestringnullableRequired without
product_id.at most 255 characters
descriptionstringnullableat most 2,000 characters
quantitynumberrequiredUp to three decimals: 2.5 hours, 13.75 m².
0.001–100,000
unit_price_centsintegernullablePrice per unit in öre, excluding VAT. Defaults to the product's price, or 0.
≥ 0
discount_percentintegernullableDefault
00–100vat_ratenumbernullableMoms in percent. Defaults to the product's rate, or 25.
One of
251260line_typestringnullableOnly
labourcounts towards a ROT/RUT deduction.One of
labourmaterialotherDefault
"other"is_optionalbooleannullableAn option the buyer may tick in or out.
Default
falseis_selectedbooleannullableFor an optional line: counted in the totals until the buyer decides. Ignored for a fixed line, which always counts.
Default
falsebilling_cyclestringnullableFor recurring lines. Defaults to the product's.
One of
monthlyyearly
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredidstring(uuid)requireddeal_idstring(uuid)nullablerequireddeal_room_idstring(uuid)nullablerequiredThe room whose stakeholders can accept the quote.
quote_numberstringrequiredSequential within the organization.
Example
"Q-00042"titlestringrequiredstatusstringrequireddraftuntil sent, and the only state whose lines and terms can change;sentonce in front of the buyer, who can accept or reject it until the end of itsexpires_atday;acceptedorrejectedby the buyer, after which it never changes;expiredwhen a sent quote's last day passed unanswered (set by an hourly sweep). A sent or expired quote goes back todraftonly through Revert to draft.One of
draftsentacceptedrejectedexpiredsubtotal_centsintegerrequiredSum of the totals of the lines that count, in öre, before the quote discount and VAT.
discount_centsintegerrequiredWhat the quote's
discount_percenttook off those lines, in öre.discount_percentintegerrequired0–100
tax_centsintegerrequiredThe VAT of the lines that count, summed — each line's rounded once. Per rate in
vat_breakdown.tax_percentintegerrequireddeprecatedDeprecated: VAT is per line (
lines[].vat_rate,vat_breakdown). Kept for older integrations: it reads as the rate every counted line shares, and is left as it was when the rates are mixed. It never changes an amount.0–100
total_centsintegerrequiredWhat the quote comes to, VAT included, in öre: 400000 is 4 000 kr. Before any ROT/RUT deduction — see
rot_rut.customer_pays_cents.vat_breakdownarray of objectsrequiredNet and VAT per rate over the lines that count, highest rate first — as an invoice has to state them.
rot_rutobjectrequiredA preliminary ROT/RUT estimate, kept apart from the total. The yearly ceiling per person is not applied — it depends on what other companies have already claimed — and Skatteverket decides after the invoice.
currencystringrequiredexactly 3 characters
expires_atstring(date)nullablerequiredLast day the quote can be accepted (inclusive).
accepted_atstring(date-time)nullablerequiredaccepted_by_stakeholder_idstring(uuid)nullablerequiredThe room stakeholder who accepted it, when it was accepted in a room.
acceptanceobjectnullablerequiredWhat the buyer agreed to, recorded once at the moment of acceptance. Null until accepted, and on quotes accepted before this was recorded.
rejection_reasonstringnullablerequiredWhat the buyer wrote when rejecting it.
share_urlstring(uri)nullablerequiredThe quote's own page, where the buyer can read it, tick options and accept or reject it without a deal room. Issued when the quote is sent, revoked when it is reverted to draft (a new one comes with the next send). Null on a draft. Anyone holding it can answer the quote, so share it only with the buyer.
handoverobjectrequiredWhat an accepted quote becomes in your organization, and what it became. With the scheduling module it becomes a work order waiting to be planned; with invoicing and no scheduling, an invoice draft.
available,targetandmodedescribe your organization and are the same on every quote;work_order,invoiceandblocked_reasondescribe this one. See Hand over an accepted quote.linesarray of objectsrequiredcreated_atstring(date-time)requiredupdated_atstring(date-time)required
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 404No such record in this organization.Errors
- 409The quote's state does not allow this.Errors
errorsays which:quote_not_draft— it has been sent; revert it to draft before changing it;quote_locked— it has been accepted or rejected and can no longer change;quote_expired— its last valid day has passed;quote_not_sent,quote_already_accepted,quote_rejected— it is not open for the buyer's answer;quote_not_accepted— only an accepted quote can be handed over;handover_unavailable— the organization has neither the scheduling nor the invoicing module, so an accepted quote has nothing to become.messagesays the same for a person. - 422The request body or query failed validation.Errors
- 429The key has made more requests this minute than its limit allows.Errors
Update a line
/quotes/{quote}/lines/{line}Changes the fields you send and recomputes the quote's totals. Answers with the whole quote. A field sent as null is left as it is, so a line's description or billing cycle cannot be cleared here — replace the line instead. The product a line came from cannot be changed. Only a draft's lines change (409 otherwise, as for adding one).
curl -X PATCH 'https://api.closepact.com/v1/api/quotes/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40/lines/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"quantity": 4,
"discount_percent": 5
}'{
"data": {
"id": "01a15c50-8d9e-7f0a-b1c2-d3e4f5a6b7c8",
"deal_id": "01a15c2e-4b7a-7f3e-9c1d-2e8f6a0b4c71",
"deal_room_id": "01a15c40-1f2e-7d9a-8b3c-4e5f6a7b8c9d",
"quote_number": "Q-00042",
"title": "Offert hemstädning – familjen Ny",
"status": "draft",
"subtotal_cents": 608000,
"discount_cents": 0,
"discount_percent": 0,
"tax_cents": 152000,
"tax_percent": 25,
"total_cents": 760000,
"vat_breakdown": [
{
"rate": 25,
"base_cents": 608000,
"vat_cents": 152000
}
],
"rot_rut": {
"type": "rut",
"rate_percent": 50,
"labour_cents": 760000,
"deduction_cents": 380000,
"customer_pays_cents": 380000,
"preliminary": true,
"annual_ceiling_applied": false
},
"currency": "SEK",
"expires_at": "2026-11-17",
"accepted_at": null,
"accepted_by_stakeholder_id": null,
"acceptance": null,
"rejection_reason": null,
"share_url": null,
"handover": {
"available": true,
"target": "work_order",
"mode": "automatic",
"work_order": null,
"invoice": null,
"blocked_reason": null
},
"lines": [
{
"id": "01a15c52-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"product_id": "01a15b70-6c7d-7e8f-9a0b-1c2d3e4f5a6b",
"name": "Hemstäd varannan vecka",
"description": null,
"quantity": 4,
"unit_price_cents": 160000,
"discount_percent": 5,
"vat_rate": 25,
"line_total_cents": 608000,
"net_cents": 608000,
"vat_cents": 152000,
"line_type": "labour",
"is_optional": false,
"is_selected": true,
"billing_cycle": "monthly",
"position": 0
}
],
"created_at": "2026-10-13T08:30:00+00:00",
"updated_at": "2026-10-13T08:47:03+00:00"
}
}Path parameters
quotestring(uuid)requiredThe quote's id.
linestring(uuid)requiredThe line's id. It must belong to this quote.
Request body
application/json · required
namestringnullableat most 255 characters
descriptionstringnullableat most 2,000 characters
quantitynumbernullableUp to three decimals.
0.001–100,000
unit_price_centsintegernullable≥ 0
discount_percentintegernullable0–100
vat_ratenumbernullableOne of
251260line_typestringnullableOne of
labourmaterialotheris_optionalbooleannullableMaking a line fixed counts it again; making it optional leaves it unticked unless
is_selectedsays otherwise.is_selectedbooleannullableFor an optional line only.
billing_cyclestringnullableOne of
monthlyyearly
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredidstring(uuid)requireddeal_idstring(uuid)nullablerequireddeal_room_idstring(uuid)nullablerequiredThe room whose stakeholders can accept the quote.
quote_numberstringrequiredSequential within the organization.
Example
"Q-00042"titlestringrequiredstatusstringrequireddraftuntil sent, and the only state whose lines and terms can change;sentonce in front of the buyer, who can accept or reject it until the end of itsexpires_atday;acceptedorrejectedby the buyer, after which it never changes;expiredwhen a sent quote's last day passed unanswered (set by an hourly sweep). A sent or expired quote goes back todraftonly through Revert to draft.One of
draftsentacceptedrejectedexpiredsubtotal_centsintegerrequiredSum of the totals of the lines that count, in öre, before the quote discount and VAT.
discount_centsintegerrequiredWhat the quote's
discount_percenttook off those lines, in öre.discount_percentintegerrequired0–100
tax_centsintegerrequiredThe VAT of the lines that count, summed — each line's rounded once. Per rate in
vat_breakdown.tax_percentintegerrequireddeprecatedDeprecated: VAT is per line (
lines[].vat_rate,vat_breakdown). Kept for older integrations: it reads as the rate every counted line shares, and is left as it was when the rates are mixed. It never changes an amount.0–100
total_centsintegerrequiredWhat the quote comes to, VAT included, in öre: 400000 is 4 000 kr. Before any ROT/RUT deduction — see
rot_rut.customer_pays_cents.vat_breakdownarray of objectsrequiredNet and VAT per rate over the lines that count, highest rate first — as an invoice has to state them.
rot_rutobjectrequiredA preliminary ROT/RUT estimate, kept apart from the total. The yearly ceiling per person is not applied — it depends on what other companies have already claimed — and Skatteverket decides after the invoice.
currencystringrequiredexactly 3 characters
expires_atstring(date)nullablerequiredLast day the quote can be accepted (inclusive).
accepted_atstring(date-time)nullablerequiredaccepted_by_stakeholder_idstring(uuid)nullablerequiredThe room stakeholder who accepted it, when it was accepted in a room.
acceptanceobjectnullablerequiredWhat the buyer agreed to, recorded once at the moment of acceptance. Null until accepted, and on quotes accepted before this was recorded.
rejection_reasonstringnullablerequiredWhat the buyer wrote when rejecting it.
share_urlstring(uri)nullablerequiredThe quote's own page, where the buyer can read it, tick options and accept or reject it without a deal room. Issued when the quote is sent, revoked when it is reverted to draft (a new one comes with the next send). Null on a draft. Anyone holding it can answer the quote, so share it only with the buyer.
handoverobjectrequiredWhat an accepted quote becomes in your organization, and what it became. With the scheduling module it becomes a work order waiting to be planned; with invoicing and no scheduling, an invoice draft.
available,targetandmodedescribe your organization and are the same on every quote;work_order,invoiceandblocked_reasondescribe this one. See Hand over an accepted quote.linesarray of objectsrequiredcreated_atstring(date-time)requiredupdated_atstring(date-time)required
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 404No such record in this organization.Errors
- 409The quote's state does not allow this.Errors
errorsays which:quote_not_draft— it has been sent; revert it to draft before changing it;quote_locked— it has been accepted or rejected and can no longer change;quote_expired— its last valid day has passed;quote_not_sent,quote_already_accepted,quote_rejected— it is not open for the buyer's answer;quote_not_accepted— only an accepted quote can be handed over;handover_unavailable— the organization has neither the scheduling nor the invoicing module, so an accepted quote has nothing to become.messagesays the same for a person. - 422The request body or query failed validation.Errors
- 429The key has made more requests this minute than its limit allows.Errors
Delete a line
/quotes/{quote}/lines/{line}Removes the line and recomputes the quote's totals. The other lines keep their positions. Only a draft's lines change (409 otherwise, as for adding one).
curl -X DELETE 'https://api.closepact.com/v1/api/quotes/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40/lines/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY"204 — no response bodyPath parameters
quotestring(uuid)requiredThe quote's id.
linestring(uuid)requiredThe line's id. It must belong to this quote.
Responses
- 204The line was deleted. No body.
- 401No key, or the key is invalid, revoked or expired, or its creator no longer has access.Errors
- 402The organization has not bought the module this endpoint belongs to, or it has lapsed to read-only, or its tier does not include what the operation needs:Errors
featurenames the capability (the operation'sx-feature) andrequired_tierthe cheapest tier that grants it. Every operation answers this with"feature": "core.api"when no module the organization holds is on a tier that includes the API. - 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (Errors
module_seat_required— an administrator assigns seats under Settings → Users), or the organization is suspended. - 404No such record in this organization.Errors
- 409The quote's state does not allow this.Errors
errorsays which:quote_not_draft— it has been sent; revert it to draft before changing it;quote_locked— it has been accepted or rejected and can no longer change;quote_expired— its last valid day has passed;quote_not_sent,quote_already_accepted,quote_rejected— it is not open for the buyer's answer;quote_not_accepted— only an accepted quote can be handed over;handover_unavailable— the organization has neither the scheduling nor the invoicing module, so an accepted quote has nothing to become.messagesays the same for a person. - 429The key has made more requests this minute than its limit allows.Errors