Configurators
The Configurator object — controls, calculations, parts and presets — how a configurator prices a product, test calculations, and the public showcase of shareable configurators.
A configurator is a pricing engine for one kind of product. The customer fills in controls (width, fabric, colour), and calculations turn those values into quantities of materials, i.e. a priced bill of materials. Parts optionally produce a production cut-list, and presets are ready-made configurations.
A configurator is what the widget shows to customers. A priced result saved to an order becomes a product.
The structure is stored as JSON and is not validated beyond formula syntax. A misspelled field does not cause an
error; the line it belongs to simply prices at 0. Always check a configurator with
Calculate Configurator and read its warnings.
Field types follow the field notation.
Access
Project endpoints require both the projects and configurators OAuth scopes. The user must be a member of the
project, and the owner's subscription must be active. Otherwise they return 404 RES_NOT_FOUND. The project owner
bypasses permission checks.
| Permission | Grants |
|---|---|
can_view_configurators |
Read configurators and run test calculations |
can_edit_configurators |
Create, update and delete configurators |
Material prices inside a configurator follow the material price permissions: a tier the user may not see is returned
as 0.
Showcase endpoints require only the configurators scope, except copying, which also needs projects.
Configurator Object
Configurator Structure
| Field | Type | Description |
|---|---|---|
| uuid | uuid | Configurator ID |
| name | string | Name, 1–255 characters |
| product_name | string | Template for the name of each priced product, see Product name template |
| product_unit | string | Unit of the finished product, usually pcs |
| product_work_cost | decimal | Flat amount added to the sale and dealer totals |
| product_amortization | decimal | Another flat amount added to the sale and dealer totals |
| price_multiplier | decimal | Multiplies every material price; 1 means no change |
| controls | array of control objects | Fields the customer fills in |
| calculations | array of calculation objects | Priced bill of materials |
| parts | array of part objects | Production cut-list, not priced |
| presets | array of preset objects | Ready-made configurations |
| materials | array of material objects | Materials linked to the configurator. Only in Get Configurator |
| is_shareable | boolean | Listed in the public showcase so other Configo users can copy it |
| is_publicly_accessible | boolean | Can be opened by a direct link |
| is_calculations_displayed | boolean | Show the material breakdown to the customer in the widget |
| status | string | active or suspended |
| image_uuid | ?uuid | Cover picture file |
| image_url | ?string | Relative URL of the picture, e.g. /uploads/{uuid} |
| created_at | ISO8601 datetime | When the configurator was created |
| updated_at | ISO8601 datetime | When the configurator was last changed |
Prices are rounded to the project's price_step: material lines to cents, totals to
the step.
Control Structure
{
"id": "$width",
"type": "number",
"label": "Width, mm",
"default": 550,
"value": "",
"class": "col-span-6",
"source": "",
"dataset": "",
"order": 1,
"min": 150,
"max": 2500,
"step": 1
}
| Field | Type | Description |
|---|---|---|
| id | string | Must start with $, then ASCII letters, digits or _, e.g. $width. Formulas refer to controls by this id |
| type | string | See the table below. An unknown type renders as a text input |
| label | string | Shown to the customer |
| default | any | Initial value. For select types it must match an option exactly; a value matching nothing selects the first option. "" leaves a select unset |
| value | string | Runtime state; always saved as "" |
| class | string | Width in a 12-column grid: col-span-1 … col-span-12 |
| order | integer | Display order, from 1 |
| source | string | Where options come from; meaning depends on type |
| dataset | any | Options or a lookup key; meaning depends on type |
| min?, max?, step? | number | For number. min/max are enforced by the widget only, not by calculations |
| rows? | number | For textarea |
| type | source | dataset | Value produced |
|---|---|---|---|
number, text, textarea |
— | — | What the customer typed |
select |
json or yaml |
A JSON string of an array, e.g. "[\"A\",\"B\"]" |
The chosen string |
select |
group |
A material group name | The chosen material's name |
select |
A material group name | A property key | That property's distinct values across the group |
select |
A material uuid | A property key whose value is an array | One entry of that array |
select_image |
— | A material group name | The chosen material's name, with its picture shown |
select_depend |
— | [{"property", "variable"}] |
Name of a material whose property equals the value of control variable; several entries are ANDed |
materials |
groups |
Array of group names | {<material uuid>: {name, unit, quantity}}: the customer ticks materials and enters quantities |
A materials control is billed directly from its value and needs no calculation entry.
Calculation Structure
One calculation produces at most one line of the bill of materials.
{
"material": "Fabric",
"formulas": [
{
"code": "$width * (max(1400, $height) + 200) / 1000000",
"variable": "$cloth",
"conditions": []
}
]
}
| Field | Type | Description |
|---|---|---|
| material | string | A material uuid (always that material) or a material group name (whichever the customer picked) |
| formulas | array | Up to 15 formulas, tried in order. The first whose conditions hold, whose material resolves and whose result is > 0 wins |
Formula Structure
| Field | Type | Description |
|---|---|---|
| code | string | An expression giving the quantity in the material's own unit. A constant like "2" is fine |
| variable | string | For a group material: id of the control that picks the material. "" for a uuid material |
| conditions | array | Up to 10 conditions, all of which must hold. [] means always |
There is no OR between conditions. To express OR, repeat the formula with different conditions; the first match wins.
Condition Structure
| Field | Type | Description |
|---|---|---|
| variable | string | Control id |
| operator | string | equal, not_equal, greater, less, include, not_include |
| value | any | Value to compare with. For a group-backed control, this is the material's name |
Part Structure
A cut-list row. Parts never affect the price.
{
"material": "<material uuid>",
"formulas": [
{
"sizes": 2,
"code": [
"$width - 43",
"$height",
""
],
"unit": "mm",
"item": {
"quantity": 1,
"unit": "pcs"
},
"conditions": []
}
]
}
| Field | Type | Description |
|---|---|---|
| material | uuid | Material the row is labelled with |
| formulas[].sizes | integer | 1, 2 or 3 dimensions |
| formulas[].code | array | Always three expressions; only the first sizes are used, the rest "" |
| formulas[].unit | string | Unit of the dimensions, usually mm |
| formulas[].item | object | {quantity, unit}: fixed piece count |
| formulas[].conditions | array | As in calculations |
Preset Structure
| Field | Type | Description |
|---|---|---|
| name | string | Shown to the customer |
| configuration | object | Control values, e.g. {"$width": 550, "$height": 1400} |
Formulas
Formulas use a dedicated expression language, not JavaScript:
- Arithmetic
+ - * / % **, comparisons (==is loose,===strict),&& || ?? !,? :. - Functions:
abs round ceil floor trunc max min pow sqrt exp log log10 sin cos tan asin acos atan atan2 random lower upper trim. Constants:PI E LN2 LN10 SQRT2 SQRT1_2. There is no property access and noMath.prefix. $idis replaced by the control's value. Values from the form can be strings, so"1200" + 1concatenates; write+$width + 1.- A
$idthat matches no control makes the whole formula evaluate to0. It does not cause an error; it shows up inwarnings.
Units are never converted. Controls are usually in millimetres and prices per square or running metre, so divide:
($width / 1000) * ($height / 1000) for m².
Every formula and the product name template are parsed when saved. One that fails to parse rejects the request with
400 CONFIGURATOR_FORMULA_INVALID, see Errors.
Product name template
product_name is text with two kinds of placeholders. A bare $control is substituted as is, and a ${...} block is
evaluated as an expression. An untouched control reads as the literal [none].
${'$system' == 'Uni' ? 'Closed PVC' : '$system'}; fabric - ${lower('$cloth')}; $width×$height mm
Price
sale total = Σ(quantity × (1 + percent_waste/100) × sale_price × price_multiplier) + product_work_cost + product_amortization
dealer total = same with dealer_price
purchase total = Σ(quantity × (1 + percent_waste/100) × purchase_price × price_multiplier)
List Configurators
GET /api/v1/projects/{project.uuid}/configurators
Returns the project's configurators, most recently updated first, without the structure fields (controls,
calculations, parts, presets), prices and materials.
Requires can_view_configurators.
Query String Params
| Field | Type | Description |
|---|---|---|
| status? | string | active or suspended |
Get Configurator
GET /api/v1/projects/{project.uuid}/configurators/{configurator.uuid}
Returns the full configurator, including its linked materials.
Requires can_view_configurators.
Create Configurator
POST /api/v1/projects/{project.uuid}/configurators
Requires can_edit_configurators.
JSON Params
| Field | Type | Description |
|---|---|---|
| name | string | 1–255 characters |
| product_name? | string | Up to 512 characters. Default "" |
| product_unit? | string | Up to 64 characters. Default pcs |
| product_work_cost? | decimal | ≥ 0. Default 0 |
| product_amortization? | decimal | ≥ 0. Default 0 |
| price_multiplier? | decimal | ≥ 0. Default 1 |
| controls? | array | Default [] |
| calculations? | array | Default [] |
| parts? | array | Default [] |
| presets? | array | Default [] |
| material_uuids? | array of uuid | Materials of this project to link. A material that is not linked is invisible to calculations |
| is_shareable? | boolean | Default false. true lists the configurator in the public showcase |
| is_publicly_accessible? | boolean | Default false |
| is_calculations_displayed? | boolean | Default true |
| status? | string | Default active |
| image_uuid? | ?uuid | Cover picture |
Create the materials first, then the configurator with all of them in material_uuids.
Example Request
{
"name": "Roller blind",
"product_name": "Roller blind $cloth, $width×$height mm",
"is_shareable": false,
"controls": [
{
"id": "$width",
"type": "number",
"label": "Width, mm",
"default": 550,
"value": "",
"class": "col-span-6",
"source": "",
"dataset": "",
"order": 1,
"min": 150,
"max": 2500,
"step": 1
},
{
"id": "$height",
"type": "number",
"label": "Height, mm",
"default": 1400,
"value": "",
"class": "col-span-6",
"source": "",
"dataset": "",
"order": 2,
"min": 150,
"max": 2600,
"step": 1
},
{
"id": "$cloth",
"type": "select",
"label": "Fabric",
"default": "Blackout 605",
"value": "",
"class": "col-span-12",
"source": "group",
"dataset": "Fabric",
"order": 3
}
],
"calculations": [
{
"material": "Fabric",
"formulas": [
{
"code": "$width * (max(1400, $height) + 200) / 1000000",
"variable": "$cloth",
"conditions": []
}
]
},
{
"material": "0199a2c1-5d6e-7f70-8182-93a4b5c6d7e8",
"formulas": [
{
"code": "$width / 1000",
"variable": "",
"conditions": []
}
]
}
],
"material_uuids": [
"0199a2c0-7a1b-7c2d-8e3f-a0b1c2d3e4f5",
"0199a2c1-5d6e-7f70-8182-93a4b5c6d7e8"
]
}
Response
201 Created
{
"uuid": "0199ab16-3d4e-7f50-8162-738495061728"
}
Modify Configurator
PATCH /api/v1/projects/{project.uuid}/configurators/{configurator.uuid}
Updates only the fields you send. controls, calculations, parts, presets and material_uuids each replace the
whole list when sent. At least one field must be present. Only the formulas you send are validated.
On update, price_multiplier below 1 is silently replaced with 1.
Requires can_edit_configurators.
Response
204 No Content
Replace Configurator
PUT /api/v1/projects/{project.uuid}/configurators/{configurator.uuid}
Same as Modify Configurator, but every field of Create Configurator is
required. image_uuid may be null.
Delete Configurator
DELETE /api/v1/projects/{project.uuid}/configurators/{configurator.uuid}
Deletes the configurator. Its materials and the products already made with it stay.
Requires can_edit_configurators.
Response
204 No Content
Calculate Configurator
POST /api/v1/projects/{project.uuid}/configurators/{configurator.uuid}/calculate
Runs the saved configurator with the given control values and returns the priced result. Price tiers the user's role
may not see (can_view_purchase_price, can_view_dealer_price, can_view_sale_price) count as 0 in every line and
total. Nothing is written. Use it to
test a configurator after changing it.
Requires can_view_configurators.
JSON Params
| Field | Type | Description |
|---|---|---|
| values? | object | Control values keyed by control id, including the $. Controls not given use their default |
Example Request
For a configurator with only the fabric line from the example above:
{
"values": {
"$width": 1200,
"$height": 1600,
"$cloth": "Blackout 605"
}
}
Response
200 OK
| Field | Type | Description |
|---|---|---|
| product_name | string | The rendered product name template |
| price_totals | object | {purchase, sale, dealer}, rounded to the project's price step |
| calculated_materials | array | Bill of materials lines, see below |
| calculated_parts | array | Cut-list rows: {name, quantity, unit, size, size_unit}, size like "1157×1600" |
| warnings | array of string | Problems found: a calculation that matched no material, conditions that never held, formulas that evaluated to 0, values for unknown controls. An empty array is the only sign that every calculation contributed a line |
A calculated_materials line:
| Field | Type | Description |
|---|---|---|
| uuid | uuid | Material ID |
| name | string | Material name |
| unit | string | Material unit |
| quantity | string | Quantity including waste, three decimals, e.g. "1.932" |
| price | object | Unit price per tier, {purchase, sale, dealer}, with price_multiplier applied |
| amount | object | price × quantity per tier |
{
"product_name": "Roller blind Blackout 605, 1200×1600 mm",
"price_totals": {
"purchase": 9.53,
"sale": 22.45,
"dealer": 17.01
},
"calculated_materials": [
{
"uuid": "0199a2c0-7a1b-7c2d-8e3f-a0b1c2d3e4f5",
"name": "Blackout 605",
"unit": "sm",
"quantity": "2.268",
"price": {
"purchase": 4.2,
"sale": 9.9,
"dealer": 7.5
},
"amount": {
"purchase": 9.53,
"sale": 22.45,
"dealer": 17.01
}
}
],
"calculated_parts": [],
"warnings": []
}
Showcase
Configurators with is_shareable: true and status: active, from all Configo projects, form a public catalog. Any
user can browse it and copy a configurator into their own project. Prices are never exposed through the showcase.
List Showcase Configurators
GET /api/v1/showcase/configurators
Requires the configurators scope.
Query String Params
| Field | Type | Description |
|---|---|---|
| search? | string | Name contains, case-insensitive |
| page? | integer | From 1. Default 1 |
| limit? | integer | 1–50. Default 20 |
Response
200 OK — an array of {uuid, name, product_name, product_unit, is_calculations_displayed, updated_at}, most recently
updated first.
Get Showcase Configurator
GET /api/v1/showcase/configurators/{configurator.uuid}
Returns the configurator's structure and materials. All prices, product_work_cost, product_amortization and
price_multiplier are 0.
Requires the configurators scope.
Copy Showcase Configurator
POST /api/v1/showcase/configurators/{configurator.uuid}/copy
Copies the configurator and all its materials into one of the user's projects. The copy is named
"<name> [showcase]", is not shareable, and gets new material uuids that calculations are remapped to. Prices are
copied only if the user is also a member of the source project; otherwise they are 0.
Requires the projects and configurators scopes, and can_edit_configurators in the target project.
JSON Params
| Field | Type | Description |
|---|---|---|
| project_uuid | uuid | Target project |
Response
201 Created
{
"uuid": "0199ab27-4e5f-7061-8273-849506172839"
}
Errors
| Status | Code | When |
|---|---|---|
| 400 | REQ_VALIDATION_FAILED |
The body does not match the schema |
| 400 | REQ_NO_DATA_PROVIDED |
Modify only: the body has no fields |
| 400 | CONFIGURATOR_FORMULA_INVALID |
A formula or the product name template does not parse; see below |
| 400 | RES_NOT_FOUND |
A material in material_uuids or image_uuid does not belong to this project |
| 403 | AUTH_INSUFFICIENT_SCOPE |
The token lacks a required scope |
| 403 | AUTH_NO_PROJECT_ACCESS |
The user's role lacks can_view_configurators (read, calculate) or can_edit_configurators (write, copy) |
| 404 | RES_NOT_FOUND |
The project or configurator does not exist, or a showcase configurator is not shared |
CONFIGURATOR_FORMULA_INVALID lists every problem in reason:
{
"id": 4013,
"code": "CONFIGURATOR_FORMULA_INVALID",
"message": "A configurator formula or the product name template could not be parsed.",
"reason": [
"calculations[0].formulas[0]: Expected \")\"",
"product_name: Unexpected character: ."
]
}