Configuration API Guide — Project-based Integration Flow

Welcome to the decoloop CPQ API 2.0 Project-based Integration Flow. This guide explains the Project-centric approach: an architecture where a full Project is created upfront on the server and used to manage all configured items, or Polygons, within that project session.

This approach is best suited for custom-tailored environments, professional room or house planners, B2B sales portals, and integrations where draft states, multiple configured items, quote review, and project-level customer/order data are naturally represented by a backend Project.

NOTE: This documentation is for the 2.0 version of the API. This version is not backwards compatible with the previous Gateway API.

Project-centric architecture

In the Project-based flow, the client starts by creating a server-side Project. Every configured item is then added to that Project as a Polygon.

The API keeps the Project state on the server. After each change, the client can either retrieve the full updated Project or apply JSON Patch operations to a local copy. This makes the flow useful for sessions where the user is working inside a project, quote, room plan, house plan, or sales consultation.

Use this guide when:

  • the frontend session should map directly to one backend Project;
  • users can configure multiple items as part of the same quote or plan;
  • draft, edit, cancel-edit, and completion states should be stored on the backend project;
  • project-level customer, delivery, sales, or quote information is known before checkout;
  • the integration resembles a professional planner or B2B sales portal.

If your integration configures individual items independently in a webshop/cart and only creates a Project at checkout, use the Standalone Polygon Guide instead.

JavaScript/TypeScript Library

We have published a JavaScript/TypeScript library on NPM with 2 clients and a set of helper functions. You can find it here. It's also possible to generate your own client by downloading the OpenAPI specification and using a tool like https://editor.swagger.io/.

Authentication

The decoloop CPQ API uses JWT bearer tokens for authenticated requests. To create a token, use the CreateToken method. The response contains a token which can be used in an Authorization header like so:

Authorization: bearer {access_token}

When using one of the provided clients this will look something like this:

import { WoonTotaalFetchClient } from '@sieval/woontotaal-client/fetch';

const client = new WoonTotaalFetchClient('https://woontotaal.example.com');

// retrieve token using provided credentials
const token = await client.auth_CreateToken({
  // the api key is optional and provided by the owner of the WoonTotaal instance
  apiKey: '00000000-0000-0000-0000-000000000000',
  domainName: 'store_1',
  password: 'test',
  username: 'employee_a',
});

// set access token, the client will now automatically append it to the headers of any call
client.setConfig({ bearerToken: token.access_token });

Impersonation

If a user has enough rights, impersonation can be used to make API calls for other Licensees/Employees. This means calls can be made for other branches. A set of four HTTP headers is available for this functionality:

  • WT-Impersonation-LicenseeId Value can contain a Licensee.Id from the WT database.
  • WT-Impersonation-DomainName Value can contain a Licensee.DomainName from the WT database. A DomainName can contain for example a store number or company name.
  • WT-Impersonation-EmployeeId Value can contain a Employee.Id from the WT database.
  • WT-Impersonation-Username Value can contain a Employee.Username from the WT database.

NOTE: These headers need to be present for each call you want to use impersonation with.

Diff (Patch) Response

v2.0 comes in two flavours:

  • full Project response
  • JSON diff patch

This means that almost every API call has 2 variants. One always returns the full Project and one returns an array of operations to patch an existing JSON. More info JSON patches can be found in the JSON Patch RFC6902.

How to Use JSON Patches

JSON patches contain changes between 2 JSONs. This allows us to only send changes to the client. In order to apply these changes, first a full Project response is needed. This full response is usually retrieved by calling the Project/Get or Project/Create methods.

The @sieval/woontotaal-client NPM package contains a helper function, which takes care of patching. The below example creates a new Project and adds an empty Polygon (note: the example doesn't include authentication):

import { WoonTotaalFetchClient } from '@sieval/woontotaal-client/fetch';
import { patchProject } from '@sieval/woontotaal-client';

const client = new WoonTotaalFetchClient('https://woontotaal.example.com');

// create a new project, this will return a full Project response
let project = await client.project_Create();

// logs an empty array
console.log(project.polygons);

let operations = await client.polygon_AddDiff({
  projectId: project.id,
  polygonTypeId: null,
  modelId: null,
  materialId: null,
  materialCode: null,
  height: null,
  width: null,
});

// apply the operations to the project object
patchProject(project, operations);

// logs an array with 1 polygon
console.log(project.polygons);

When to use the Project-based flow

Choose this flow when the user journey is naturally centered around a backend Project and configurations need to be able to react to each other. For example to create sets of configurations for outlining patterns, equal tube sizes for roller blinds, etc. For webshops where each cart line is configured independently and only grouped into an order at checkout, use the Standalone Polygon Guide.

Localization

If the decoloop CPQ configuration contains multiple languages, a language header can be used to set the request language: WT-Request-Language. The value needs to be a 2 letter language code, for example: NL, EN, FR, DE.

WT-Request-Language: NL

NOTE: In order to use a different request language than the default, the language needs to be present and active in the WoonTotaal database.

Project-based configuration flow

In this flow, configuration starts from a server-side Project. Each configured item is added to that project as a Polygon.

Before we go into the flow, first let's clear up some terms and basic decoloop CPQ principles.

Terms

  • Material: part of a end product, for example a fabric for a curtain
  • Project: the server-side container for a project session, quote, room/house plan, or multi-item configuration
  • Polygon: one configured item inside a Project
  • CategoryLink: part of a product, for example for a curtain: main fabric, lining, borders
  • CategoryLinkMaterial: information about a Material applied to a CategoryLink
  • Property: property of a product, basically represents a question which requires an answer
  • ListValue: selectable option for a Property
  • Model: manufacturing method for a product, for example for a curtain: wave, pleated, eyelets
  • Wizard: configurator steps which should be displayed for a certain product

Configuration Basics

When configuring a Polygon a few basic rules apply to every configuration:

  • the Polygon needs a Model
  • the Polygon needs at least one Material
  • the Polygon needs to have valid required dimensions

After this, decoloop CPQ can start calculating and return a list of Properties.

Before starting, make sure you have:

  • instantiated a client
  • retrieved an access token for authorization

Entry Point

A configurator can have two entry points:

  1. Model — Retrieve a list of all Models, select one, and ask decoloop CPQ for a list of valid Materials for the selected Model.
  2. Material — Retrieve a list of valid Models for this Material by sending the SKU/article code to decoloop CPQ

Configuration Steps

1. Retrieve all Models

When retrieving all Models, decoloop CPQ returns a possibly nested menu. For example:

  • Curtain
    • Curtain, Pleated
    • Curtain, Wave
  • Horizontal blind
    • Horizontal blind, Wood
    • Horizontal blind, Aluminum
const models = await client.model_GetAll();

2. Create a Project

Before configuring items in the Project-based flow, create a new server-side Project. This Project becomes the shared context for all Polygons configured during the session.

let project = await client.project_Create();

3. Add a Polygon to the Project

We can now add a Polygon to the Project. A Polygon represents one configured item. It can be created empty or initialized with preselected values such as model, material, dimensions, or polygon type.

let operations = await client.polygon_AddDiff({
  projectId: project.id,

  // optional Model.Id, initialises the Polygon with this Model
  modelId: null,

  // optional Material.Code or Material.Id
  materialCode: null,
  materialId: null,

  // optional PolygonType.Id
  polygonTypeId: null,

  // optional width and height to initialise the Polygon with
  width: null,
  height: null,
});

// patch the created Project
patchProject(project, operations);

// a new Polygon is always added as last in the array
let polygon = project.polygons[0];

4. Set Model

Using the list of Models we retrieved in step 1, we can now set the Model on the newly created Polygon.

// always make sure the selected model does not have any items in its children
// only models without children are valid for selection
const model = models[0];
if (model.children.length) {
  throw new Error("Invalid model");
}

operations = await client.polygon_SetModelDiff({
  modelId: model.id,
  polygonId: polygon.id,
});

// patch the Project
patchProject(project, operations);

// refresh the Polygon variable
polygon = project.polygons[0];

console.log(polygon.modelDescription);

5. Set a Material

Now that we have set the Model, we know which CategoryLinks are available/required for this product.

const categoryLink = polygon.categoryLinks[0];

const pagedResult = await client.material_Browse({
  categoryId: null,
  categoryLinkId: categoryLink.id,
  codeFilter: null,
  colorFilter: null,
  descriptionFilter: null,
  pageNumber: 1,
  pageSize: 25,
});

const material = pagedResult.items[0];

// apply the Material
operations = await client.polygon_SetMaterialDiff({
  categoryLinkId: categoryLink.id,
  materialId: material.id,
  polygonId: polygon.id,
});

patchProject(project, operations);
polygon = project.polygons[0];

Using an article code/SKU:

operations = await client.polygon_SetMaterialByCodeDiff({
  categoryLinkId: categoryLink.id,
  materialCode: sku,
  polygonId: polygon.id,
});

6. Set Dimensions

Now we are ready to set the width/height (in cm) of the Polygon.

operations = await client.polygon_SetDimensionsDiff({
  polygonId: polygon.id,
  width: 100,
  height: 100,
});

patchProject(project, operations);
polygon = project.polygons[0];

// log the price of the Polygon
console.log(polygon.prices.totalPrice);

NOTE: the dimensions should always be set in centimeters.

7. Set Property Value

decoloop CPQ will now return a list of Properties for each CategoryLink. These Properties are questions which should be asked to the user.

const categoryMaterial = polygon.categoryLinks[0].categoryLinkMaterial;
categoryMaterial.properties.forEach(prp => {
  console.log(prp.displayLabel);
});

// set a property value
const property = categoryMaterial.properties[0];
const listValue = property.listValues[0];

operations = await client.polygon_SetPropertyValueDiff({
  polygonId: polygon.id,
  categoryLinkMaterialPropertyId: property.id,
  value: listValue.value,
});

patchProject(project, operations);
polygon = project.polygons[0];

Wizard (Optional)

If the Wizard functionality is configured for this decoloop CPQ instance and account, we can use it to build configurator steps.

// retrieve the Wizard steps for the current product
const steps = await client.wizard_GetStepsForProduct(polygon.productCategoryId);

// use the helper function to build the steps
const configuratorSteps = buildConfiguratorSteps(selectedPolygon, steps);

configuratorSteps.forEach(step => {
  const isValid = step.isValid;

  step.properties.forEach(prp => {
    const propertyIsValid = prp.isValid;
    const messages = prp.messages;
  });
});

Polygon.State

The Polygon.State property indicates the current configurator state of a Polygon. Possible values are: New, Completed, Editing.

Apply changes: Call Polygon/Complete or Polygon/CompleteDiff.

Cancel changes: Call Polygon/CancelEdit or Polygon/CancelEditDiff. This will restore the snapshot which was made while calling the Polygon/Edit method.

Next Steps

On this page