Pricing
Pricing configurations are the rows used by the configurator to price one product opening/configuration. They are tenant-scoped and managed through the Fenestro public API.
For configurable products, do not calculate catalogue prices from base_price. Use POST /v1/products/{product_id}/pricing-configurations to manage pricing rows, then read catalogue card prices with GET /v1/categories/{category_id}/from-prices.
Endpoints
| Method | Path | Use |
|---|---|---|
| POST | /v1/products/{product_id}/pricing-configurations | Create one pricing row for one product. |
| GET | /v1/products/{product_id}/pricing-configurations | List pricing rows. Supports active, page, and page_size. |
| GET | /v1/products/{product_id}/pricing-configurations/{id} | Read one pricing row. |
| PATCH | /v1/products/{product_id}/pricing-configurations/{id} | Patch only the provided fields. |
| DELETE | /v1/products/{product_id}/pricing-configurations/{id} | Soft delete: the row becomes inactive. |
| DELETE | /v1/products/{product_id}/pricing-configurations/{id}/hard | Hard delete a mistaken/test row. |
| GET | /v1/categories/{category_id}/from-prices | Return product-id to computed catalogue "from" price. |
Important fields
| Field | Meaning |
|---|---|
leaf_count | Number of leaves/openings for this row. Accepted values are 1 to 4. |
opening_v1 ... opening_v4 | Opening type metadata used to identify the configuration in product steps. Accepted values: fixe, gauche, droite, oscillo, gauche_oscillo, droite_oscillo. |
fixed_part_type | Accepted values: aucune, allege, imposte. |
fixed_part_opening_* | Accepted values for fixed-part openings: fixe or oscillo. |
total_width_min, total_height_min | Minimum valid dimensions in millimeters. Formula from-prices evaluate the formula at these dimensions. |
minimum_price | Fallback price when no usable table/formula value exists. |
price_table | Main pricing payload. It is a string so it can contain either formula text or JSON. |
percentage_adjustment | Row-level adjustment applied after the row table/formula value. |
computed_minimum_price | What the API computes for this row from price_table, dimensions, minimum_price, and percentage_adjustment. |
price_per_square_meter, coefficients, surcharges | Optional area-based metadata fields. Catalogue from-prices use price_table first, then minimum_price. |
How pricing is used
| Area | Current behavior |
|---|---|
| Customer quote | The configurator reads price_table. Direct grid uses modeInterpolation; formula JSON uses A * surface_m2 + B and rejects exact keys in unavailable; simple A:B formula text is also supported. |
| Catalogue from-price | /v1/categories/{category_id}/from-prices computes only the minimum catalogue price. It is not a full quote endpoint for arbitrary dimensions. |
| Formula samples | Send formula samples inside price_table.echantillons. There is no separate public samples endpoint. |
price_per_square_meter and coefficients | Optional area-based metadata fields. They are not the primary catalogue pricing source. |
Price table formats
price_table accepts three formats.
1. Simple formula string
"100:50"
This means price = A * surface_m2 + B. For example, at 1000 x 1000 mm, 100 * 1 + 50 = 150.
2. Formula JSON with samples
{
"type": "formule",
"A": 100,
"B": 50,
"r2": 0.98,
"widths": [1000, 1200, 1400],
"heights": [1000, 1400, 1800],
"echantillons": [
{ "largeur": 1000, "hauteur": 1000, "prix": 150 },
{ "largeur": 1400, "hauteur": 1800, "prix": 302 }
],
"unavailable": ["1200_1400"]
}
The configurator uses A * surface_m2 + B. The unavailable array marks exact customer dimensions that must be rejected, using width_height keys.
3. Direct grid JSON
{
"type": "table-directe",
"modeInterpolation": "majoration",
"widths": [1000, 1200, 1400],
"heights": [1000, 1400, 1800],
"prices": [
[150, 180, 210],
[200, null, 260],
[245, 290, 335]
]
}
widths are columns, heights are rows, so prices[row][column] is the price for heights[row] and widths[column]. In the example, the 1200 x 1400 exact cell is unavailable because it is null. Use null, not 0, for unavailable cells.
modeInterpolation can be majoration, minoration, or formule. It controls customer-dimension interpolation in the configurator. The catalogue from-price shortcut still uses the cheapest filled positive cell.
Create examples
curl -X POST https://api.fenestro.io/v1/products/42/pricing-configurations \
-H "Authorization: Bearer fen_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"leaf_count": 2,
"fixed_part_type": "aucune",
"opening_v1": "gauche",
"opening_v2": "droite",
"total_width_min": 1000,
"total_width_max": 2200,
"total_height_min": 1000,
"total_height_max": 2400,
"minimum_price": 250,
"price_table": "{\"type\":\"table-directe\",\"modeInterpolation\":\"majoration\",\"widths\":[1000,1200],\"heights\":[1000,1400],\"prices\":[[150,180],[200,null]]}",
"percentage_adjustment": 0,
"active": true
}'
From-price calculation
- The API reads active products, active product steps, active pricing rows, and the authenticated tenant only.
- Step snapshots can contain
pricingConfig,pricing_configuration,pricingFormulas, orpricing_formulas. - If a step snapshot references a current pricing row id for the same product, the fresh row wins. This means patching a pricing row can update from-prices even if the step still contains an older snapshot.
- Direct-grid rows use the cheapest filled positive cell.
nulland non-positive cells are ignored. - Formula rows use
A * surface_m2 + Battotal_width_minandtotal_height_min. If the formula cannot be evaluated, samples are used as fallback. - If no table/formula value exists,
minimum_priceis used. percentage_adjustmentis applied at row level.- Product
price_adjustment_percentageis applied once after row-level pricing. Roof-window dimension flat prices are exempt, matching the existing configurator behavior.
Category from-prices
curl https://api.fenestro.io/v1/categories/12/from-prices \
-H "Authorization: Bearer fen_live_xxxxxxxxxxxx"
{
"42": 165,
"43": 254,
"44": null
}
The response is a product-id dictionary. A null value means the product is visible in the category but no active step contains enough usable pricing data.
This endpoint is for category cards and listing pages. It is not a quote endpoint for a user-entered width and height.
Common errors
| Error | Meaning |
|---|---|
not_found | The product, pricing row, or category does not exist for the authenticated tenant. |
pricing_configuration_in_use | A hard delete was rejected because business records still reference the row. |
validation_error | The request body is missing required fields or has invalid min/max ranges. |