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

GET/products

Returns 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"
Response · 200
{
  "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

  • qstring

    Text the name or SKU contains, case-insensitively and with å, ä and ö folded correctly.

    Example städ

  • active_onlyboolean

    Only products that are on sale (is_active).

    Default false

  • sortstring

    Sort field. Anything else sorts by name.

    One ofnameskuunit_price_centscreated_atupdated_at

    Default name

  • directionstring

    Sort direction.

    One ofascdesc

    Default asc

  • limitinteger

    Items per page. Values outside 1–200 are clamped.

    Default 501–200

  • cursorstring

    Opaque cursor from meta.next_cursor or meta.prev_cursor of 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 objectsrequired
      • idstring(uuid)required
      • skustringrequired
      • namestringrequired
      • descriptionstringnullablerequired
      • typestringrequired

        one_time for a single delivery, recurring for a subscription charged per billing_cycle, service for work, bundle for a package.

        One ofone_timerecurringservicebundle

      • unit_price_centsintegerrequired

        Price per unit in öre, excluding VAT: 160000 is 1 600 kr.

        ≥ 0

      • vat_ratenumberrequired

        Moms in percent: 25, 12, 6 or 0. Copied onto every quote line made from the product.

      • currencystringrequired

        exactly 3 characters

      • billing_cyclestringnullablerequired

        One ofmonthlyyearly

      • is_activebooleanrequired

        Whether the product is on sale.

      • service_idstring(uuid)nullablerequired

        The 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 valuesrequired

        Free-form values. An empty set comes back as [].

        at most 0 items

      • propertiesobject | array of valuesrequired

        The 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-DD strings. A product with no values has [].

        at most 0 items

      • created_by_namestringnullablerequired

        Name of the member who created the product.

      • updated_by_namestringnullablerequired

        Name of the member who last changed it.

      • created_atstring(date-time)required
      • updated_atstring(date-time)required
    • linksobjectrequired
    • metaobjectrequired
  • 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: feature names the capability (the operation's x-feature) and required_tier the 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.
    Errors
  • 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (module_seat_required — an administrator assigns seats under Settings → Users), or the organization is suspended.
    Errors
  • 429The key has made more requests this minute than its limit allows.
    Errors

Create a product

POST/products
Scope write Module Proposals & e-signing Permission cpq.products.manage

Adds 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
  }
}'
Response · 201
{
  "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

  • skustringrequired

    Your 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

  • namestringrequired

    at most 255 characters

  • descriptionstringnullable

    at most 2,000 characters

  • typestringnullable

    Defaults to one_time.

  • unit_price_centsintegerrequired

    Price per unit in öre, excluding VAT.

    ≥ 0

  • vat_ratenumbernullable

    Moms in percent. Quote lines made from the product inherit it.

    One of251260

    Default 25

  • currencystringnullable

    ISO 4217 code, upper case. Stored as sent.

    Default "SEK"exactly 3 characters

  • billing_cyclestringnullable

    How often a recurring product is charged.

    One ofmonthlyyearly

  • is_activebooleannullable

    Whether the product is on sale.

    Default true

  • service_idstring(uuid)nullable

    The 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.

  • metadataobjectnullable

    Free-form values, for example your own system's id.

  • propertiesmap of (string | number | boolean)nullable

    Custom 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 to YYYY-MM-DD, everything else to text. null or 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.
    • dataobjectrequired
      • idstring(uuid)required
      • skustringrequired
      • namestringrequired
      • descriptionstringnullablerequired
      • typestringrequired

        one_time for a single delivery, recurring for a subscription charged per billing_cycle, service for work, bundle for a package.

        One ofone_timerecurringservicebundle

      • unit_price_centsintegerrequired

        Price per unit in öre, excluding VAT: 160000 is 1 600 kr.

        ≥ 0

      • vat_ratenumberrequired

        Moms in percent: 25, 12, 6 or 0. Copied onto every quote line made from the product.

      • currencystringrequired

        exactly 3 characters

      • billing_cyclestringnullablerequired

        One ofmonthlyyearly

      • is_activebooleanrequired

        Whether the product is on sale.

      • service_idstring(uuid)nullablerequired

        The 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 valuesrequired

        Free-form values. An empty set comes back as [].

        at most 0 items

      • propertiesobject | array of valuesrequired

        The 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-DD strings. A product with no values has [].

        at most 0 items

      • created_by_namestringnullablerequired

        Name of the member who created the product.

      • updated_by_namestringnullablerequired

        Name of the member who last changed it.

      • created_atstring(date-time)required
      • updated_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: feature names the capability (the operation's x-feature) and required_tier the 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.
    Errors
  • 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (module_seat_required — an administrator assigns seats under Settings → Users), or the organization is suspended.
    Errors
  • 422The request body or query failed validation.
    Errors
  • 429The key has made more requests this minute than its limit allows.
    Errors

List product properties

GET/products/properties

Returns 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"
Response · 200
{
  "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 objectsrequired
      • idstring(uuid)required
      • organization_idstring(uuid)required
      • keystringrequired

        Stable machine name, usable instead of the id when writing values.

      • labelstringrequired
      • typestringrequired

        One oftextnumberbooleandateselect

      • optionsarray of stringsrequired

        The choices of a select property.

      • is_requiredbooleanrequired
      • positionintegerrequired
      • created_atstring(date-time)nullablerequired
      • updated_atstring(date-time)nullablerequired
      • deleted_atstring(date-time)nullablerequired

        Always 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: feature names the capability (the operation's x-feature) and required_tier the 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.
    Errors
  • 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (module_seat_required — an administrator assigns seats under Settings → Users), or the organization is suspended.
    Errors
  • 429The key has made more requests this minute than its limit allows.
    Errors

Retrieve a product

GET/products/{product}

Returns one product with its property values.

curl 'https://api.closepact.com/v1/api/products/01a14f2c-7c1e-7d0b-9a3e-3f5b2c8d1e40' \
  -H "Authorization: Bearer $CLOSEPACT_API_KEY"
Response · 200
{
  "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)required

    The product's id.

Responses

  • Headers

    • X-RateLimit-Limit — Requests this key may make per minute.
    • X-RateLimit-Remaining — Requests left in the current minute.
    • dataobjectrequired
      • idstring(uuid)required
      • skustringrequired
      • namestringrequired
      • descriptionstringnullablerequired
      • typestringrequired

        one_time for a single delivery, recurring for a subscription charged per billing_cycle, service for work, bundle for a package.

        One ofone_timerecurringservicebundle

      • unit_price_centsintegerrequired

        Price per unit in öre, excluding VAT: 160000 is 1 600 kr.

        ≥ 0

      • vat_ratenumberrequired

        Moms in percent: 25, 12, 6 or 0. Copied onto every quote line made from the product.

      • currencystringrequired

        exactly 3 characters

      • billing_cyclestringnullablerequired

        One ofmonthlyyearly

      • is_activebooleanrequired

        Whether the product is on sale.

      • service_idstring(uuid)nullablerequired

        The 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 valuesrequired

        Free-form values. An empty set comes back as [].

        at most 0 items

      • propertiesobject | array of valuesrequired

        The 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-DD strings. A product with no values has [].

        at most 0 items

      • created_by_namestringnullablerequired

        Name of the member who created the product.

      • updated_by_namestringnullablerequired

        Name of the member who last changed it.

      • created_atstring(date-time)required
      • updated_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: feature names the capability (the operation's x-feature) and required_tier the 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.
    Errors
  • 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (module_seat_required — an administrator assigns seats under Settings → Users), or the organization is suspended.
    Errors
  • 404No such record in this organization.
    Errors
  • 429The key has made more requests this minute than its limit allows.
    Errors

Update a product

PATCH/products/{product}
Scope write Module Proposals & e-signing Permission cpq.products.manage

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
  }
}'
Response · 200
{
  "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)required

    The product's id.

Request body

application/json · required

  • skustring

    Unique within the organization, deleted products included; a SKU another product holds is a 422.

    at most 64 characters

  • namestring

    at most 255 characters

  • descriptionstringnullable

    at most 2,000 characters

  • typestring

    one_time for a single delivery, recurring for a subscription charged per billing_cycle, service for work, bundle for a package.

    One ofone_timerecurringservicebundle

  • unit_price_centsinteger

    ≥ 0

  • vat_ratenumber

    Applies to lines added from now on; lines already on quotes keep the rate they were made with.

    One of251260

  • currencystring

    exactly 3 characters

  • billing_cyclestringnullable

    One ofmonthlyyearly

  • is_activeboolean
  • service_idstring(uuid)nullable

    The scheduling service the product is performed as; null unlinks it. Read when a quote is accepted, so quotes already accepted are not affected.

  • metadataobject
  • propertiesmap of (string | number | boolean)nullable

    Custom 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 to YYYY-MM-DD, everything else to text. null or 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.
    • dataobjectrequired
      • idstring(uuid)required
      • skustringrequired
      • namestringrequired
      • descriptionstringnullablerequired
      • typestringrequired

        one_time for a single delivery, recurring for a subscription charged per billing_cycle, service for work, bundle for a package.

        One ofone_timerecurringservicebundle

      • unit_price_centsintegerrequired

        Price per unit in öre, excluding VAT: 160000 is 1 600 kr.

        ≥ 0

      • vat_ratenumberrequired

        Moms in percent: 25, 12, 6 or 0. Copied onto every quote line made from the product.

      • currencystringrequired

        exactly 3 characters

      • billing_cyclestringnullablerequired

        One ofmonthlyyearly

      • is_activebooleanrequired

        Whether the product is on sale.

      • service_idstring(uuid)nullablerequired

        The 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 valuesrequired

        Free-form values. An empty set comes back as [].

        at most 0 items

      • propertiesobject | array of valuesrequired

        The 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-DD strings. A product with no values has [].

        at most 0 items

      • created_by_namestringnullablerequired

        Name of the member who created the product.

      • updated_by_namestringnullablerequired

        Name of the member who last changed it.

      • created_atstring(date-time)required
      • updated_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: feature names the capability (the operation's x-feature) and required_tier the 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.
    Errors
  • 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (module_seat_required — an administrator assigns seats under Settings → Users), or the organization is suspended.
    Errors
  • 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

DELETE/products/{product}
Scope write Module Proposals & e-signing Permission cpq.products.manage

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"
Response · 204
204 — no response body

Path parameters

  • productstring(uuid)required

    The 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: feature names the capability (the operation's x-feature) and required_tier the 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.
    Errors
  • 403The key lacks the scope, or the member it acts as lacks the permission or holds no seat in the module (module_seat_required — an administrator assigns seats under Settings → Users), or the organization is suspended.
    Errors
  • 404No such record in this organization.
    Errors
  • 429The key has made more requests this minute than its limit allows.
    Errors