Configuration API Guide — Standalone Polygon Integration Flow

Welcome to the decoloop CPQ API 2.0 Standalone Polygon Integration Flow.

This guide explains the Standalone Polygon approach for applications that configure individual items independently before they are grouped into a backend Project. This flow is used for configuring products in, for example, a webshop and other apps that manage their own session or client-side state.

In this architecture, the frontend application configures individual items as standalone polygons. The parent application stores a stable reference to each configured item, usually the standalone polygon ID, in its own session state, client-side state, line item, or application-level record. At finalization, submission, save/send, or promotion time, those standalone polygons are promoted into a single backend Project.

This flow is a good fit for integrations where:

  • each line item or application-level record represents one independently configured item;
  • the user can add, remove, or edit configured items before finalization or submission;
  • the parent application owns the session, navigation, draft, or client-side state experience;
  • a backend Project is only needed once the configured items are finalized, submitted, saved, or sent;
  • customer, delivery, or application-level data may only be known at finalization.

If your integration creates a backend Project upfront and manages all configured items inside that project session, use the Project-based Guide.

Architecture overview

The Standalone Polygon flow has four main phases:

  1. Authentication / client initialization
  2. Creating a Standalone Polygon
    • Choose the entry point that matches the user journey: Article-First / Webshop when the user starts from a known SKU or article code, or Configuration-First / CPQ UI when the user starts by configuring a product step by step.
  3. Configuring the Standalone Polygon
  4. Promoting standalone polygons to a Project at finalization / submission

The key difference from the Project-based flow is timing:

  • In the Project-based flow, the Project exists before item configuration starts.
  • In the Standalone Polygon flow, the configured items exist first, and the Project is created later during finalization, submission, save/send, or project promotion.

JavaScript/TypeScript Library

The recommended TypeScript client is available through the @sieval/woontotaal-client package.

npm install @sieval/woontotaal-client

Use the fetch client primarily from trusted server-side code, such as backend services, server-side rendering lifecycle code, API routes, backend finalization handlers, or submission handlers. Do not initialize the client in a public browser application with CPQ credentials or a CPQ bearer token. Client-side use is only appropriate when the browser calls your own backend/API-route proxy and that proxy performs CPQ authentication and request forwarding server-side:

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

const client = new WoonTotaalFetchClient('https://sandbox.decoloop.com');

You can also generate your own client from the OpenAPI specification if your integration requires custom transport, logging, retries, or framework-specific behavior.

1. Authentication / client initialization

The decoloop CPQ API uses JWT bearer tokens for authenticated requests. Create a token with the authentication endpoint and configure the client with the returned access token.

Security Warning

In 99% of integrations using this API, CPQ calls and authentication must be handled server-side.

Direct client-side calls leak secrets (API keys, passwords, and JWT tokens) via browser devtools and network tabs.

Secure your communication by routing all CPQ requests through backend API routes, SSR, or a backend proxy.

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

const client = new WoonTotaalFetchClient('https://sandbox.decoloop.com');

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',
  username: 'employee_a',
  password: 'test',
});

client.setConfig({
  bearerToken: token.access_token,
});

Impersonation

If a user has enough rights, impersonation can be used to make API calls for other licensees or employees. This is useful for multi-branch, multi-location, multi-store, or delegated sales environments.

Available headers:

  • WT-Impersonation-LicenseeId
  • WT-Impersonation-DomainName
  • WT-Impersonation-EmployeeId
  • WT-Impersonation-Username

NOTE: These headers need to be present for each request that should use impersonation.

Localization

If the decoloop CPQ configuration contains multiple languages, use the WT-Request-Language header to set the request language.

WT-Request-Language: NL

The value should be a two-letter language code, for example NL, EN, FR, or DE.

NOTE: The requested language must be present and active in the WoonTotaal database.

Important concepts

  • Standalone Polygon: an independently configured item that is not yet part of a backend Project.
  • Project: the backend container used to group one or more configured items into a project or finalized submission.
  • Polygon: one configured item. After promotion to a Project, standalone polygons become regular project polygons.
  • Model: manufacturing method for a product, for example a pleated curtain, wave curtain, or horizontal blind.
  • Material: part of an end product, for example a fabric for a curtain.
  • CategoryLink: product part or material slot, for example main fabric, lining, or border.
  • Property: configurable question or option for a product.
  • ListValue: selectable option for a property.
  • Wizard: configured steps that can be used to build a guided configurator UI.

Configuration basics

A standalone polygon generally needs the same core configuration data as a project polygon:

  • a valid Model;
  • at least one valid Material;
  • valid required dimensions;
  • required property values;
  • optional description for display, review, finalization, or submission.

Once enough information is available, decoloop CPQ can calculate prices, return configurable properties, validate the item, and prepare it for promotion to a Project.

2. Creating a Standalone Polygon

How you create and populate a standalone polygon depends on where your user starts.

Entry pointTypical user journeyCreate payloadConfiguration impact
Article-First / WebshopThe user starts from a known SKU, article code, product detail page, or catalog line.Create the polygon with the resolved modelId and known materialCode.Model and material are already applied; continue with dimensions, property values, description, validation, and pricing.
Configuration-First / CPQ UIThe user starts in a configurator and chooses the product model and material during the flow.Create an empty polygon first.Apply model and material later as the user makes selections.

Use the entry point that matches the parent application's user experience. A webshop usually knows the selected article before opening the configurator. A CPQ-style configurator usually starts with an empty product shell and lets the user choose model, material, dimensions, and options in sequence.

Choose the starting point first

Do not duplicate CPQ state in the parent application. Store the returned standalonePolygonId and any application-owned display fields, then refresh the standalone polygon from CPQ when validation, pricing, or submission decisions are needed.

Article-First / Webshop

Use this flow when the parent application starts from a known article code, SKU, product code, or external catalog reference. First resolve the compatible models for the material code and select one, then create the standalone polygon with both the selected modelId and materialCode.

const materialCode = 'SKU-123';

// Returns all models that can be configured for this material code.
const models = await client.model_GetForMaterialByCode(materialCode);

if (models.length === 0) {
  throw new Error(`No configurable model found for material code "${materialCode}".`);
}

// Select a model. Here we take the first match; a UI may let the user choose
// when more than one model is returned.
const model = models[0];

const standalonePolygon = await client.standalonePolygon_Create({
  modelId: model.id,
  materialId: null,
  materialCode,
  polygonTypeId: null,
  width: null,
  height: null,
});

const standalonePolygonId = standalonePolygon.id;

After this call, the standalone polygon already has the model and material context needed to continue configuration. The remaining steps are usually dimensions, required property values, description, validation, and pricing.

Configuration-First / CPQ UI

Use this flow when the user starts with a blank configurator or guided CPQ UI and chooses the model and material during the configuration session.

You can initialize the standalone polygon empty, or with known values such as model, material, width, height, or polygon type.

const standalonePolygon = await client.standalonePolygon_Create({
  // Optional Model.Id, if the product model is already known.
  modelId: null,

  // Optional Material.Id or Material.Code, if the material is already known.
  materialId: null,
  materialCode: null,

  // Optional PolygonType.Id.
  polygonTypeId: null,

  // Optional initial dimensions in centimeters.
  width: null,
  height: null,
});

const standalonePolygonId = standalonePolygon.id;

Store the returned standalonePolygonId on the corresponding line item, session entry, client-side state record, or application-level record.

The parent application should only keep a stable reference to the CPQ configuration plus application-owned display, draft, or finalization fields. The full standalone polygon response should remain owned by CPQ and be refreshed from the API when validation, pricing, or submission decisions are needed.

Raw v2 endpoint equivalent

If you are not using the generated TypeScript client, call the equivalent v2 endpoint directly.

const response = await fetch('https://sandbox.decoloop.com/api/gateway/v2/StandalonePolygon/Create', {
  method: 'POST',
  headers: {
    Authorization: `bearer ${accessToken}`,
    'Content-Type': 'application/json',
    'WT-Request-Language': 'NL',
  },
  body: JSON.stringify({
    modelId: null,
    materialId: null,
    materialCode: null,
    polygonTypeId: null,
    width: null,
    height: null,
  }),
});

if (!response.ok) {
  throw new Error(`Failed to create standalone polygon: ${response.status}`);
}

const standalonePolygon = await response.json();

3. Configuring the Standalone Polygon

After creating the standalone polygon, update it as the user makes selections. The exact sequence depends on the entry point used in section 2.

In the Configuration-First / CPQ UI flow, the user chooses the model and material during configuration, so the full sequence usually applies:

  1. retrieve or select a model;
  2. apply the model;
  3. retrieve valid materials;
  4. apply the material;
  5. set dimensions;
  6. set property values;
  7. set a description for application display, review, or submission;
  8. validate and show calculated prices.

In the Article-First / Webshop flow, the model and material were already resolved when the standalone polygon was created. Steps 1 through 4 are therefore usually skipped, unless the user changes the selected article or material after creation. Continue with dimensions, property values, description, validation, and pricing.

Retrieve models

const models = await client.model_GetAll();

const selectedModel = models[0];

if (selectedModel.children?.length) {
  throw new Error('Invalid model selection: only leaf models can be selected.');
}

Set model

let standalonePolygon = await client.standalonePolygon_SetModel({
  polygonId: standalonePolygonId,
  modelId: selectedModel.id,
});

Browse and set material

After the model is known, the polygon response indicates which category links are available or required.

const categoryLink = standalonePolygon.categoryLinks[0];

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

const selectedMaterial = pagedMaterials.items[0];

standalonePolygon = await client.standalonePolygon_SetMaterial({
  polygonId: standalonePolygonId,
  categoryLinkId: categoryLink.id,
  materialId: selectedMaterial.id,
});

Configuration-First cross-reference: use standalonePolygon_SetMaterialByCode when an empty standalone polygon later receives an article code, SKU, product code, or external catalog reference. In the Article-First / Webshop flow, pass materialCode during standalonePolygon_Create instead.

If your application starts from an article code, SKU, product code, or external catalog reference during configuration:

standalonePolygon = await client.standalonePolygon_SetMaterialByCode({
  polygonId: standalonePolygonId,
  categoryLinkId: categoryLink.id,
  materialCode: 'SKU-123',
});

Set dimensions

Dimensions should be sent in centimeters.

standalonePolygon = await client.standalonePolygon_SetDimensions({
  polygonId: standalonePolygonId,
  width: 100,
  height: 250,
  isGrossDimension: false,
});

console.log(standalonePolygon.prices?.totalPrice);

NOTE: Dimensions should always be set in centimeters.

Set property values

Once the model, material, and dimensions are known, decoloop CPQ can return configurable properties.

const categoryLinkMaterial = standalonePolygon.categoryLinks[0].categoryLinkMaterial;

for (const property of categoryLinkMaterial.properties) {
  console.log(property.displayLabel);
}

const property = categoryLinkMaterial.properties[0];
const selectedListValue = property.listValues[0];

standalonePolygon = await client.standalonePolygon_SetPropertyValue({
  polygonId: standalonePolygonId,
  categoryLinkMaterialPropertyId: property.id,
  value: selectedListValue.value,
});

Set description

For frontend applications, it is useful to store a room name for example which might be printed on the product label.

standalonePolygon = await client.standalonePolygon_SetDescription({
  polygonId: standalonePolygonId,
  description: 'Living room',
  triggerCalculate: true,
});

Keep application state small

Prefer storing only the stable standalonePolygonId and application-owned display fields in the parent application's session state, client-side state, line item, or application-level record.

The standalone polygon ID is the link between the parent application's local item identifier and the CPQ configuration. Keeping that identifier allows the application to reopen, validate, price, or promote the configured item without duplicating CPQ state locally.

Avoid storing the entire polygon response in long-lived application state unless the integration has an explicit cache invalidation strategy. Prices, validation messages, available options, and configuration rules can change when the backend configuration changes, so CPQ should remain the source of truth for configuration and pricing.

4. Promoting to a Project at finalization / submission

At finalization, submission, save/send, or project promotion time, collect all standalone polygon IDs from the parent application's state and create a backend Project for them.

The finalization flow is:

  1. create a Project for the standalone polygons;
  2. store or update the customer if customer data is available;
  3. link the customer to the project when applicable;
  4. set the delivery address when applicable;
  5. complete each polygon to verify if it is fully configured, valid, and ready to be ordered;
  6. send or submit the project using project_Send.

The following example shows the CPQ API calls needed to promote standalone polygons into a backend Project:

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

const client = new WoonTotaalFetchClient('https://sandbox.decoloop.com');

const token = await client.auth_CreateToken({
  apiKey: '00000000-0000-0000-0000-000000000000',
  domainName: 'store_1',
  username: 'employee_a',
  password: 'test',
});

client.setConfig({
  bearerToken: token.access_token,
});

// Standalone polygon IDs retrieved from the parent application's state.
const standalonePolygonIds = [
  '0d6ef6f9-51a5-4c41-bf5c-28f2451f8a00',
  '3972c13d-bcf6-4312-a3b5-12d643f3f63a',
];

const externalReference = 'APP-RECORD-000123';

// 1. Promote standalone polygons into a backend Project.
const projectResponse = await client.standalonePolygon_CreateProject({
  externalReference,
  standalonePolygonIds,
});

const projectId = projectResponse.projectId;

// 2. Store the customer, if customer data is available.
const customer = await client.customer_StoreCustomer({
  id: null, // null = insert new; set to an existing id to update
  firstName: 'Jane',
  lastName: 'Doe',
  emailAddress: 'jane.doe@example.com',
  phone: '+31612345678',
});

const customerId = customer.id;

// 3. Link the customer to the Project.
await client.project_SetCustomer({
  projectId,
  customerId,
});

// 4. Set the delivery address for the Project from the customer, if applicable.
//    If this is not called, default behaviour is store delivery.
await client.project_SetDeliveryAddressFromCustomer({
  projectId,
  customerId,
});

// 5. Complete each promoted polygon to verify if it is fully configured, valid, and ready to be ordered.
for (const polygonId of standalonePolygonIds) {
  await client.polygon_Complete({
    polygonId,
  });
}

// 6. Send or submit the Project.
const project = await client.project_Send({
  projectId,
  customer: null, // optional Customer object with consumer info
});

console.log('Project submitted. Project ID:', project.id);

NOTE: The generated client method names are based on the OpenAPI operation names. If your installed @sieval/woontotaal-client version exposes slightly different names, use the same request payloads with the corresponding generated methods from your API reference.

Error handling recommendations

For frontend application integrations, treat CPQ calls as part of the configuration and finalization lifecycle and handle failures explicitly.

Recommended behavior:

  • If standalone polygon creation fails, do not create or persist the configured line item as finalized.
  • If configuration updates fail, keep the user on the configurator step and show a recoverable validation message.
  • If project promotion fails, do not mark the application-level record or submission as finalized.
  • If project_Send fails, keep the application-level record or submission in a retryable state depending on your application architecture.
  • Log the standalonePolygonIds, projectId, externalReference, and local submission or transaction reference for support diagnostics.

Avoid silently dropping failed polygon updates. A line item or application-level record should either reference a valid standalone polygon or be removed/reconfigured.

When to use the Project-based flow instead

Use the Project-based Guide instead when:

  • the user starts inside a backend-owned project, workspace, or centralized workflow;
  • multiple configured items must be edited together before finalization;
  • the backend should own draft state throughout the session;
  • users need project-level editing, snapshots, approval steps, or centralized workflow handling;
  • customer, project, delivery, or transaction information is known before item configuration starts;
  • the integration needs project-level pricing, validation, reporting, or collaboration before individual items are finalized.

Next Steps

On this page