Products
The product catalogue quotes are built from: what you sell, its SKU, unit price in öre, whether it recurs, and any custom fields (properties) your organization defined for products.
Changing the catalogue is included from the Väx tier of proposals (proposals.cpq); on Start it is read-only.
List products
/productsReturns the catalogue as a cursor-paginated list, by name unless you ask for another order, with each product's property values. Page through with cursor until meta.next_cursor is null; links.next keeps your search and sort.
curl 'https://api.closepact.com/v1/api/products' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY"{
"data": [
{
"id": "01a15b70-6c7d-7e8f-9a0b-1c2d3e4f5a6b",
"sku": "HEM-VV",
"name": "Hemstäd varannan vecka",
"description": "Två timmar per tillfälle.",
"type": "recurring",
"unit_price_cents": 160000,
"vat_rate": 25,
"currency": "SEK",
"billing_cycle": "monthly",
"is_active": true,
"service_id": null,
"metadata": [],
"properties": {
"01a15b60-1d2e-7f3a-8b4c-5d6e7f8a9b0c": 85,
"01a15b61-2e3f-7a4b-9c5d-6e7f8a9b0c1d": true
},
"created_by_name": "Anna Lindqvist",
"updated_by_name": "Anna Lindqvist",
"created_at": "2026-09-01T10:00:00+00:00",
"updated_at": "2026-10-01T08:15:00+00:00"
}
],
"links": {
"first": null,
"last": null,
"prev": null,
"next": null
},
"meta": {
"path": "https://api.closepact.com/v1/api/products",
"per_page": 50,
"next_cursor": null,
"prev_cursor": null
}
}Query parameters
qstringText the name or SKU contains, case-insensitively and with å, ä and ö folded correctly.
Example
städactive_onlybooleanOnly products that are on sale (
is_active).Default
falsesortstringSort field. Anything else sorts by name.
One of
nameskuunit_price_centscreated_atupdated_atDefault
namedirectionstringSort direction.
One of
ascdescDefault
asclimitintegerItems per page. Values outside 1–200 are clamped.
Default
501–200cursorstringOpaque 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)requiredskustringrequirednamestringrequireddescriptionstringnullablerequiredtypestringrequiredone_timefor a single delivery,recurringfor a subscription charged perbilling_cycle,servicefor work,bundlefor a package.One of
one_timerecurringservicebundleunit_price_centsintegerrequiredPrice per unit in öre, excluding VAT: 160000 is 1 600 kr.
≥ 0
vat_ratenumberrequiredMoms in percent: 25, 12, 6 or 0. Copied onto every quote line made from the product.
currencystringrequiredexactly 3 characters
billing_cyclestringnullablerequiredOne of
monthlyyearlyis_activebooleanrequiredWhether the product is on sale.
service_idstring(uuid)nullablerequiredThe service in your scheduling catalogue this product is performed as, or null. When an accepted quote becomes a work order, a line made from this product becomes that service on the job; without a link it is carried over as a line of free text.
metadataobject | array of valuesrequiredFree-form values. An empty set comes back as
[].at most 0 items
propertiesobject | array of valuesrequiredThe product's custom field values, keyed by property id (see List product properties). Numbers come back as JSON numbers, booleans as booleans and dates as
YYYY-MM-DDstrings. A product with no values has[].at most 0 items
created_by_namestringnullablerequiredName of the member who created the product.
updated_by_namestringnullablerequiredName of the member who last changed it.
created_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 product
/productsAdds a product to the catalogue, on sale unless you say otherwise. The SKU must be unique among the organization's products; reuse of a deleted product's SKU is not supported. Recorded as created by the member the key acts as.
The SKU makes retries safe: sending the same SKU again is a 422 rather than a second product.
curl -X POST 'https://api.closepact.com/v1/api/products' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sku": "HEM-VV",
"name": "Hemstäd varannan vecka",
"description": "Två timmar per tillfälle.",
"type": "recurring",
"unit_price_cents": 160000,
"billing_cycle": "monthly",
"properties": {
"area_sqm": 85,
"rut": true
}
}'{
"data": {
"id": "01a15b70-6c7d-7e8f-9a0b-1c2d3e4f5a6b",
"sku": "HEM-VV",
"name": "Hemstäd varannan vecka",
"description": "Två timmar per tillfälle.",
"type": "recurring",
"unit_price_cents": 160000,
"vat_rate": 25,
"currency": "SEK",
"billing_cycle": "monthly",
"is_active": true,
"service_id": null,
"metadata": [],
"properties": {
"01a15b60-1d2e-7f3a-8b4c-5d6e7f8a9b0c": 85,
"01a15b61-2e3f-7a4b-9c5d-6e7f8a9b0c1d": true
},
"created_by_name": "Anna Lindqvist",
"updated_by_name": "Anna Lindqvist",
"created_at": "2026-09-01T10:00:00+00:00",
"updated_at": "2026-09-01T10:00:00+00:00"
}
}Request body
application/json · required
skustringrequiredYour article number. Unique within the organization, deleted products included: a deleted product keeps its SKU, and sending a SKU already taken is a 422 on
sku.at most 64 characters
namestringrequiredat most 255 characters
descriptionstringnullableat most 2,000 characters
typestringnullableDefaults to
one_time.unit_price_centsintegerrequiredPrice per unit in öre, excluding VAT.
≥ 0
vat_ratenumbernullableMoms in percent. Quote lines made from the product inherit it.
One of
251260Default
25currencystringnullableISO 4217 code, upper case. Stored as sent.
Default
"SEK"exactly 3 charactersbilling_cyclestringnullableHow often a recurring product is charged.
One of
monthlyyearlyis_activebooleannullableWhether the product is on sale.
Default
trueservice_idstring(uuid)nullableThe service in your scheduling catalogue (
GET /services) this product is performed as. When an accepted quote becomes a work order, a line made from the product becomes that service on the job. A service of another organization, or a deleted one, is a 422.metadataobjectnullableFree-form values, for example your own system's id.
propertiesmap of (string | number | boolean)nullableCustom field values, keyed by property id or by its
key. A value is converted to the property's type: numbers to decimals, booleans from true/false/1/0/"yes"/"no", dates toYYYY-MM-DD, everything else to text.nullor an empty string clears the value. Properties not named are left as they are; unknown ids and keys are ignored.
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredidstring(uuid)requiredskustringrequirednamestringrequireddescriptionstringnullablerequiredtypestringrequiredone_timefor a single delivery,recurringfor a subscription charged perbilling_cycle,servicefor work,bundlefor a package.One of
one_timerecurringservicebundleunit_price_centsintegerrequiredPrice per unit in öre, excluding VAT: 160000 is 1 600 kr.
≥ 0
vat_ratenumberrequiredMoms in percent: 25, 12, 6 or 0. Copied onto every quote line made from the product.
currencystringrequiredexactly 3 characters
billing_cyclestringnullablerequiredOne of
monthlyyearlyis_activebooleanrequiredWhether the product is on sale.
service_idstring(uuid)nullablerequiredThe service in your scheduling catalogue this product is performed as, or null. When an accepted quote becomes a work order, a line made from this product becomes that service on the job; without a link it is carried over as a line of free text.
metadataobject | array of valuesrequiredFree-form values. An empty set comes back as
[].at most 0 items
propertiesobject | array of valuesrequiredThe product's custom field values, keyed by property id (see List product properties). Numbers come back as JSON numbers, booleans as booleans and dates as
YYYY-MM-DDstrings. A product with no values has[].at most 0 items
created_by_namestringnullablerequiredName of the member who created the product.
updated_by_namestringnullablerequiredName of the member who last changed it.
created_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
List product properties
/products/propertiesReturns the custom fields your organization defined for products, in display order. A product's properties are keyed by these ids; when writing them you may use the stable key instead. Properties are defined in the panel. Not paginated.
curl 'https://api.closepact.com/v1/api/products/properties' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY"{
"data": [
{
"id": "01a15b60-1d2e-7f3a-8b4c-5d6e7f8a9b0c",
"organization_id": "01a14eb3-c5f0-7c55-8c2e-6a0e1f7b9d21",
"key": "area_sqm",
"label": "Yta (m²)",
"type": "number",
"options": [],
"is_required": false,
"position": 1,
"created_at": "2026-08-20T12:00:00.000000Z",
"updated_at": "2026-08-20T12:00:00.000000Z",
"deleted_at": null
},
{
"id": "01a15b62-3f4a-7b5c-8d6e-7f8a9b0c1d2e",
"organization_id": "01a14eb3-c5f0-7c55-8c2e-6a0e1f7b9d21",
"key": "frequency",
"label": "Intervall",
"type": "select",
"options": [
"Varje vecka",
"Varannan vecka",
"Var fjärde vecka"
],
"is_required": true,
"position": 2,
"created_at": "2026-08-20T12:01:00.000000Z",
"updated_at": "2026-08-20T12:01:00.000000Z",
"deleted_at": null
}
]
}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)requiredorganization_idstring(uuid)requiredkeystringrequiredStable machine name, usable instead of the id when writing values.
labelstringrequiredtypestringrequiredOne of
textnumberbooleandateselectoptionsarray of stringsrequiredThe choices of a
selectproperty.is_requiredbooleanrequiredpositionintegerrequiredcreated_atstring(date-time)nullablerequiredupdated_atstring(date-time)nullablerequireddeleted_atstring(date-time)nullablerequiredAlways null here.
- 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
curl 'https://api.closepact.com/v1/api/products/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY"{
"data": {
"id": "01a15b71-7d8e-7f9a-ab1c-2d3e4f5a6b7c",
"sku": "FL-1",
"name": "Flyttstäd",
"description": null,
"type": "one_time",
"unit_price_cents": 540000,
"vat_rate": 25,
"currency": "SEK",
"billing_cycle": null,
"is_active": true,
"service_id": null,
"metadata": {
"fortnox_article": "1004"
},
"properties": [],
"created_by_name": "Anna Lindqvist",
"updated_by_name": null,
"created_at": "2026-09-01T10:05:00+00:00",
"updated_at": "2026-09-01T10:05:00+00:00"
}
}Path parameters
productstring(uuid)requiredThe product'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)requiredskustringrequirednamestringrequireddescriptionstringnullablerequiredtypestringrequiredone_timefor a single delivery,recurringfor a subscription charged perbilling_cycle,servicefor work,bundlefor a package.One of
one_timerecurringservicebundleunit_price_centsintegerrequiredPrice per unit in öre, excluding VAT: 160000 is 1 600 kr.
≥ 0
vat_ratenumberrequiredMoms in percent: 25, 12, 6 or 0. Copied onto every quote line made from the product.
currencystringrequiredexactly 3 characters
billing_cyclestringnullablerequiredOne of
monthlyyearlyis_activebooleanrequiredWhether the product is on sale.
service_idstring(uuid)nullablerequiredThe service in your scheduling catalogue this product is performed as, or null. When an accepted quote becomes a work order, a line made from this product becomes that service on the job; without a link it is carried over as a line of free text.
metadataobject | array of valuesrequiredFree-form values. An empty set comes back as
[].at most 0 items
propertiesobject | array of valuesrequiredThe product's custom field values, keyed by property id (see List product properties). Numbers come back as JSON numbers, booleans as booleans and dates as
YYYY-MM-DDstrings. A product with no values has[].at most 0 items
created_by_namestringnullablerequiredName of the member who created the product.
updated_by_namestringnullablerequiredName of the member who last changed it.
created_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 product
/products/{product}Changes the fields you send and leaves the rest alone, recording the member the key acts as as the last to update it. metadata is replaced as a whole; properties changes only the properties you list (send null to clear one).
Quote lines already made from the product keep the name and price they were made with. Set is_active to false to take a product off sale without deleting it.
curl -X PATCH 'https://api.closepact.com/v1/api/products/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"unit_price_cents": 170000,
"properties": {
"area_sqm": null
}
}'{
"data": {
"id": "01a15b70-6c7d-7e8f-9a0b-1c2d3e4f5a6b",
"sku": "HEM-VV",
"name": "Hemstäd varannan vecka",
"description": "Två timmar per tillfälle.",
"type": "recurring",
"unit_price_cents": 170000,
"vat_rate": 25,
"currency": "SEK",
"billing_cycle": "monthly",
"is_active": true,
"service_id": null,
"metadata": [],
"properties": {
"01a15b61-2e3f-7a4b-9c5d-6e7f8a9b0c1d": true
},
"created_by_name": "Anna Lindqvist",
"updated_by_name": "Erik Sjöberg",
"created_at": "2026-09-01T10:00:00+00:00",
"updated_at": "2026-10-18T07:55:00+00:00"
}
}Path parameters
productstring(uuid)requiredThe product's id.
Request body
application/json · required
skustringUnique within the organization, deleted products included; a SKU another product holds is a 422.
at most 64 characters
namestringat most 255 characters
descriptionstringnullableat most 2,000 characters
typestringone_timefor a single delivery,recurringfor a subscription charged perbilling_cycle,servicefor work,bundlefor a package.One of
one_timerecurringservicebundleunit_price_centsinteger≥ 0
vat_ratenumberApplies to lines added from now on; lines already on quotes keep the rate they were made with.
One of
251260currencystringexactly 3 characters
billing_cyclestringnullableOne of
monthlyyearlyis_activebooleanservice_idstring(uuid)nullableThe scheduling service the product is performed as; null unlinks it. Read when a quote is accepted, so quotes already accepted are not affected.
metadataobjectpropertiesmap of (string | number | boolean)nullableCustom field values, keyed by property id or by its
key. A value is converted to the property's type: numbers to decimals, booleans from true/false/1/0/"yes"/"no", dates toYYYY-MM-DD, everything else to text.nullor an empty string clears the value. Properties not named are left as they are; unknown ids and keys are ignored.
Responses
Headers
X-RateLimit-Limit— Requests this key may make per minute.X-RateLimit-Remaining— Requests left in the current minute.
dataobjectrequiredidstring(uuid)requiredskustringrequirednamestringrequireddescriptionstringnullablerequiredtypestringrequiredone_timefor a single delivery,recurringfor a subscription charged perbilling_cycle,servicefor work,bundlefor a package.One of
one_timerecurringservicebundleunit_price_centsintegerrequiredPrice per unit in öre, excluding VAT: 160000 is 1 600 kr.
≥ 0
vat_ratenumberrequiredMoms in percent: 25, 12, 6 or 0. Copied onto every quote line made from the product.
currencystringrequiredexactly 3 characters
billing_cyclestringnullablerequiredOne of
monthlyyearlyis_activebooleanrequiredWhether the product is on sale.
service_idstring(uuid)nullablerequiredThe service in your scheduling catalogue this product is performed as, or null. When an accepted quote becomes a work order, a line made from this product becomes that service on the job; without a link it is carried over as a line of free text.
metadataobject | array of valuesrequiredFree-form values. An empty set comes back as
[].at most 0 items
propertiesobject | array of valuesrequiredThe product's custom field values, keyed by property id (see List product properties). Numbers come back as JSON numbers, booleans as booleans and dates as
YYYY-MM-DDstrings. A product with no values has[].at most 0 items
created_by_namestringnullablerequiredName of the member who created the product.
updated_by_namestringnullablerequiredName of the member who last changed it.
created_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
- 422The request body or query failed validation.Errors
- 429The key has made more requests this minute than its limit allows.Errors
Delete a product
/products/{product}Removes the product from the catalogue: it leaves lists and lookups at once. Quote lines made from it keep their name, price and product_id. To stop selling something you may want to sell again, set is_active to false instead.
curl -X DELETE 'https://api.closepact.com/v1/api/products/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40' \
-H "Authorization: Bearer $CLOSEPACT_API_KEY"204 — no response bodyPath parameters
productstring(uuid)requiredThe product's id.
Responses
- 204The product 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
- 429The key has made more requests this minute than its limit allows.Errors