Configurator
Prijsofferte
Prijs een specifieke configuratie van een configureerbaar product en ontvang een regel-voor-regel uitsplitsing.
Bekijk als MarkdownHet quote-endpoint prijst één concrete configuratie. Roep het aan bij elke wijziging in de selectie van de koper om een live totaal te tonen. Het is read-only: er wordt geen cart of order aangemaakt, en het vereist alleen een leesscope: een key met ofwel catalog:read ofwel commerce:read wordt geaccepteerd.
Het quote-endpoint prijst één concrete configuratie. Roep het aan bij elke wijziging in de selectie van de koper om een live totaal te tonen. Het is read-only: er wordt geen cart of order aangemaakt, en het vereist alleen een leesscope: een key met ofwel catalog:read ofwel commerce:read wordt geaccepteerd.
POST /v1/api/commerce/configurator/products/{id}/quote
Prijst een specifieke configuratie van een actief made-to-measure-product (capability made_to_measure). De server valideert de geselecteerde keuzes (bestaan, beschikbaarheid, maximaal één per single-groep, verplichte groepen gedekt, requires- en excludes-regels), daarna de dimensies (elke geconfigureerde dimensie aanwezig, een geheel getal, binnen zijn min en max na aanscherping door de constraints van de geselecteerde keuzes), bepaalt de basisprijs uit de winnende prijsmatrix (of de productprijs wanneer het product geen dimensies heeft), past de keuzedelta's toe in twee passes (eerst fixed, per_area en percent, daarna elke percent_of_total tegen dat subtotaal zonder samengestelde berekening), en retourneert het totaal met een regel-voor-regel uitsplitsing. Zie de hoofdstukintroductie voor het volledige algoritme.
Path-parameters
| Naam | Type | Verplicht | Beschrijving |
|---|---|---|---|
| id | string (uuid) | Ja | Product-ID. Moet een actief product met capability made_to_measure zijn. |
Body-parameters
De request body is JSON.
| Naam | Type | Verplicht | Beschrijving |
|---|---|---|---|
| dimensions | object | Voorwaardelijk | Map van dimensie-key (uit het schema) naar een geheel getal in de eenheid van de dimensie (bijvoorbeeld cm). Een waarde met decimalen wordt geweigerd met een 422 op dimensions.{key}. Verplicht zodra het product dimensies heeft: elke geconfigureerde dimensie moet aanwezig en numeriek zijn. Onbekende keys worden geweigerd met een 422. Mag alleen weggelaten worden bij een product zonder dimensies. |
| choices | array of string (uuid) | Nee | Geselecteerde optiekeuze-ID's. Duplicaten worden ontdubbeld. Elk element moet een UUID zijn die naar een beschikbare keuze van dit product verwijst, dus een keuze die het schema-endpoint publiceert. Verplichte groepen moeten gedekt zijn, en per single-groep is maximaal één keuze toegestaan. Laat weg of stuur een lege array alleen wanneer het product geen verplichte groepen heeft. |
Responsevelden
| Naam | Type | Verplicht | Beschrijving |
|---|---|---|---|
| total | integer | altijd | Totaalprijs in minor units (centen). |
| currency | string | altijd | De ISO-valutacode van het product, bijvoorbeeld "EUR". |
| breakdown | array | altijd | Regelitems. Begint altijd met {"label":"Base","amount":<base>}, gevolgd door één regel per geprijsde keuze met label "{Group name}: {Choice name}", in volgorde van groep- en keuzepositie (met alle percent_of_total-regels na de andere). Keuzes met een delta van 0 verschijnen alsnog met amount: 0. Alle bedragen zijn integer minor units. |
| matrix | object or null | altijd | {id, name} van de prijsmatrix die de basisprijs leverde. null wanneer de basis uit de eigen prijs van het product kwam (een product zonder dimensies). |
Fouten
Authenticatie- en infrastructuurfouten zijn hetzelfde als op de andere storefront-routes:
| Status | Body | Oorzaak |
|---|---|---|
| 401 | {"message":"Unauthenticated."} | Ontbrekende of ongeldige API-key. |
| 403 | {"message":"Invalid ability provided."} | De key heeft noch de scope catalog:read, noch commerce:read. |
| 403 | {"error":"Module not enabled: configurator"} | De tenant heeft de configurator-module niet ingeschakeld. Let op de key error in plaats van message. |
| 403 | {"message":"Tenant suspended.","code":"tenant_suspended"} | Het tenant-account is geschorst. |
| 404 | {"message":"..."} | Het product bestaat niet, is niet actief, of de capability is niet made_to_measure. |
| 429 | {"message":"Too Many Attempts."} | Rate limit van 120 requests per minuut overschreden. |
422-validatiefouten
Alle validatiefouten gebruiken de standaard Laravel-envelope: {"message":"<first error>","errors":{"<field>":["<message>", ...]}}, zonder code op het hoogste niveau. Dat wijkt af van een cart-add, die dezelfde configuratiefouten meldt met code INVALID_CONFIGURATION op het hoogste niveau (zie Cartregels). Er zijn twee lagen.
Invoervalidatie (de vorm van de request body): dimensions moet een object zijn, choices moet een array zijn, en elk choices.*-element moet een UUID zijn. Voorbeeld:
Domeinvalidatie (de configuratie zelf): dezelfde envelope; elke foutkey bevat precies één melding. Keuzefouten worden eerst gecontroleerd en kortsluiten de rest: zodra een keuzefout optreedt, worden dimensies in die response niet gevalideerd. De mogelijke keys en meldingen:
| Foutkey | Melding | Oorzaak |
|---|---|---|
choices | One or more choices are not available. | Een keuze-ID bestaat niet op dit product, of de keuze is niet beschikbaar: inactief, verwijderd, of zonder vervulbare requires-doelgroep. Het bericht is bewust generiek en noemt de niet-beschikbare keuze nooit bij naam. |
choices | Choose one {Group}. | Twee of meer keuzes geselecteerd in een groep van type single. |
choices | {Group} is required. | Een verplichte groep heeft geen geselecteerde keuze. |
choices | Area-priced choice needs two dimensions. | Een per_area-keuze is geselecteerd op een product dat niet precies twee dimensies heeft. |
choices.{choiceId} | {Choice} cannot be combined with {Target}. | Een excludes-regel is overtreden. |
choices.{choiceId} | {Choice} requires one of: {names}. | Aan een requires-regel is niet voldaan (geen van de targets in één targetgroep is geselecteerd). {names} noemt alleen keuzes die het schema voor dit product publiceert, dus een naam in dit bericht is altijd een keuze die de koper kan selecteren. |
dimensions.{key} | {Label} is required. | De dimensie ontbreekt in het request of de waarde is niet numeriek. |
dimensions.{key} | {Label} must be a whole number. | De waarde heeft decimalen, bijvoorbeeld 120.5. |
dimensions.{key} | {Label} must be between {min} and {max} {unit}. | De waarde valt buiten het bereik van de dimensie. |
dimensions.{key} | {Label} must be between {min} and {max} {unit} when {choice names} is selected. | De waarde valt buiten het bereik na aanscherping door de constraints van de geselecteerde keuzes. |
dimensions.{key} | {Label} has no valid range for the selected choices ({names}). | De constraints van de geselecteerde keuzes laten geen bereik over (effectief minimum is groter dan effectief maximum). |
dimensions.{key} | Unknown dimension. | Het request bevat een dimensiekey die niet op het product is geconfigureerd. |
configuration | not_priceable | Er kon geen basisprijs worden bepaald: een product zonder dimensies heeft een null-prijs, geen enkele prijsmatrix heeft conditions waaraan de geselecteerde keuzes voldoen, of de winnende matrix heeft geen prijsregel die de dimensiewaarden dekt. Een matrix met een condition op een keuze die niet beschikbaar is, kan nooit vervuld worden en levert dus nooit een basisprijs; het schema toont zo'n matrix ook niet. |
Voorbeeld van een domeinvalidatiefout:
Let op: requires_quote op een keuze veroorzaakt hier nooit een fout: het endpoint prijst zulke configuraties gewoon. De vlag, beschikbaar in het schema, signaleert alleen dat de storefront de koper naar een handmatige offerteaanvraag hoort te leiden in plaats van naar directe checkout.
curl -X POST "https://api.borkol.com/v1/api/commerce/configurator/products/9c2f1e6a-8b4d-4f2a-9e11-3d5a7b9c0e21/quote" \
-H "Authorization: Bearer {api_key}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"dimensions": { "width": 420, "depth": 300 },
"choices": [
"c1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c",
"f6a7b8c9-d0e1-4f2a-b3c4-d5e6f7a8b9c0"
]
}'const res = await fetch(
`https://api.borkol.com/v1/api/commerce/configurator/products/${productId}/quote`,
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
dimensions: { width: 420, depth: 300 },
choices: [
"c1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c",
"f6a7b8c9-d0e1-4f2a-b3c4-d5e6f7a8b9c0",
],
}),
}
);
if (res.status === 422) {
const { errors } = await res.json();
// errors is a map of field => [message]; render per-field.
} else {
const { data } = await res.json();
}{
"data": {
"total": 561500,
"currency": "EUR",
"breakdown": [
{ "label": "Base", "amount": 530000 },
{ "label": "LED lighting: LED set (6)", "amount": 31500 }
],
"matrix": {
"id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a",
"name": "Glass roof"
}
}
}{
"message": "The choices.0 field must be a valid UUID.",
"errors": {
"choices.0": ["The choices.0 field must be a valid UUID."]
}
}{
"message": "Width must be between 200 and 600 cm when Glass is selected.",
"errors": {
"dimensions.width": [
"Width must be between 200 and 600 cm when Glass is selected."
]
}
}POST /v1/api/commerce/configurator/products/{id}/quote
Prijst een specifieke configuratie van een actief made-to-measure-product (capability made_to_measure). De server valideert de geselecteerde keuzes (bestaan, beschikbaarheid, maximaal één per single-groep, verplichte groepen gedekt, requires- en excludes-regels), daarna de dimensies (elke geconfigureerde dimensie aanwezig, een geheel getal, binnen zijn min en max na aanscherping door de constraints van de geselecteerde keuzes), bepaalt de basisprijs uit de winnende prijsmatrix (of de productprijs wanneer het product geen dimensies heeft), past de keuzedelta's toe in twee passes (eerst fixed, per_area en percent, daarna elke percent_of_total tegen dat subtotaal zonder samengestelde berekening), en retourneert het totaal met een regel-voor-regel uitsplitsing. Zie de hoofdstukintroductie voor het volledige algoritme.
Path-parameters
| Naam | Type | Verplicht | Beschrijving |
|---|---|---|---|
| id | string (uuid) | Ja | Product-ID. Moet een actief product met capability made_to_measure zijn. |
Body-parameters
De request body is JSON.
| Naam | Type | Verplicht | Beschrijving |
|---|---|---|---|
| dimensions | object | Voorwaardelijk | Map van dimensie-key (uit het schema) naar een geheel getal in de eenheid van de dimensie (bijvoorbeeld cm). Een waarde met decimalen wordt geweigerd met een 422 op dimensions.{key}. Verplicht zodra het product dimensies heeft: elke geconfigureerde dimensie moet aanwezig en numeriek zijn. Onbekende keys worden geweigerd met een 422. Mag alleen weggelaten worden bij een product zonder dimensies. |
| choices | array of string (uuid) | Nee | Geselecteerde optiekeuze-ID's. Duplicaten worden ontdubbeld. Elk element moet een UUID zijn die naar een beschikbare keuze van dit product verwijst, dus een keuze die het schema-endpoint publiceert. Verplichte groepen moeten gedekt zijn, en per single-groep is maximaal één keuze toegestaan. Laat weg of stuur een lege array alleen wanneer het product geen verplichte groepen heeft. |
curl -X POST "https://api.borkol.com/v1/api/commerce/configurator/products/9c2f1e6a-8b4d-4f2a-9e11-3d5a7b9c0e21/quote" \
-H "Authorization: Bearer {api_key}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"dimensions": { "width": 420, "depth": 300 },
"choices": [
"c1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c",
"f6a7b8c9-d0e1-4f2a-b3c4-d5e6f7a8b9c0"
]
}'const res = await fetch(
`https://api.borkol.com/v1/api/commerce/configurator/products/${productId}/quote`,
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
dimensions: { width: 420, depth: 300 },
choices: [
"c1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c",
"f6a7b8c9-d0e1-4f2a-b3c4-d5e6f7a8b9c0",
],
}),
}
);
if (res.status === 422) {
const { errors } = await res.json();
// errors is a map of field => [message]; render per-field.
} else {
const { data } = await res.json();
}{
"data": {
"total": 561500,
"currency": "EUR",
"breakdown": [
{ "label": "Base", "amount": 530000 },
{ "label": "LED lighting: LED set (6)", "amount": 31500 }
],
"matrix": {
"id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a",
"name": "Glass roof"
}
}
}Responsevelden
| Naam | Type | Verplicht | Beschrijving |
|---|---|---|---|
| total | integer | altijd | Totaalprijs in minor units (centen). |
| currency | string | altijd | De ISO-valutacode van het product, bijvoorbeeld "EUR". |
| breakdown | array | altijd | Regelitems. Begint altijd met {"label":"Base","amount":<base>}, gevolgd door één regel per geprijsde keuze met label "{Group name}: {Choice name}", in volgorde van groep- en keuzepositie (met alle percent_of_total-regels na de andere). Keuzes met een delta van 0 verschijnen alsnog met amount: 0. Alle bedragen zijn integer minor units. |
| matrix | object or null | altijd | {id, name} van de prijsmatrix die de basisprijs leverde. null wanneer de basis uit de eigen prijs van het product kwam (een product zonder dimensies). |
Fouten
Authenticatie- en infrastructuurfouten zijn hetzelfde als op de andere storefront-routes:
| Status | Body | Oorzaak |
|---|---|---|
| 401 | {"message":"Unauthenticated."} | Ontbrekende of ongeldige API-key. |
| 403 | {"message":"Invalid ability provided."} | De key heeft noch de scope catalog:read, noch commerce:read. |
| 403 | {"error":"Module not enabled: configurator"} | De tenant heeft de configurator-module niet ingeschakeld. Let op de key error in plaats van message. |
| 403 | {"message":"Tenant suspended.","code":"tenant_suspended"} | Het tenant-account is geschorst. |
| 404 | {"message":"..."} | Het product bestaat niet, is niet actief, of de capability is niet made_to_measure. |
| 429 | {"message":"Too Many Attempts."} | Rate limit van 120 requests per minuut overschreden. |
422-validatiefouten
Alle validatiefouten gebruiken de standaard Laravel-envelope: {"message":"<first error>","errors":{"<field>":["<message>", ...]}}, zonder code op het hoogste niveau. Dat wijkt af van een cart-add, die dezelfde configuratiefouten meldt met code INVALID_CONFIGURATION op het hoogste niveau (zie Cartregels). Er zijn twee lagen.
Invoervalidatie (de vorm van de request body): dimensions moet een object zijn, choices moet een array zijn, en elk choices.*-element moet een UUID zijn. Voorbeeld:
{
"message": "The choices.0 field must be a valid UUID.",
"errors": {
"choices.0": ["The choices.0 field must be a valid UUID."]
}
}Domeinvalidatie (de configuratie zelf): dezelfde envelope; elke foutkey bevat precies één melding. Keuzefouten worden eerst gecontroleerd en kortsluiten de rest: zodra een keuzefout optreedt, worden dimensies in die response niet gevalideerd. De mogelijke keys en meldingen:
| Foutkey | Melding | Oorzaak |
|---|---|---|
choices | One or more choices are not available. | Een keuze-ID bestaat niet op dit product, of de keuze is niet beschikbaar: inactief, verwijderd, of zonder vervulbare requires-doelgroep. Het bericht is bewust generiek en noemt de niet-beschikbare keuze nooit bij naam. |
choices | Choose one {Group}. | Twee of meer keuzes geselecteerd in een groep van type single. |
choices | {Group} is required. | Een verplichte groep heeft geen geselecteerde keuze. |
choices | Area-priced choice needs two dimensions. | Een per_area-keuze is geselecteerd op een product dat niet precies twee dimensies heeft. |
choices.{choiceId} | {Choice} cannot be combined with {Target}. | Een excludes-regel is overtreden. |
choices.{choiceId} | {Choice} requires one of: {names}. | Aan een requires-regel is niet voldaan (geen van de targets in één targetgroep is geselecteerd). {names} noemt alleen keuzes die het schema voor dit product publiceert, dus een naam in dit bericht is altijd een keuze die de koper kan selecteren. |
dimensions.{key} | {Label} is required. | De dimensie ontbreekt in het request of de waarde is niet numeriek. |
dimensions.{key} | {Label} must be a whole number. | De waarde heeft decimalen, bijvoorbeeld 120.5. |
dimensions.{key} | {Label} must be between {min} and {max} {unit}. | De waarde valt buiten het bereik van de dimensie. |
dimensions.{key} | {Label} must be between {min} and {max} {unit} when {choice names} is selected. | De waarde valt buiten het bereik na aanscherping door de constraints van de geselecteerde keuzes. |
dimensions.{key} | {Label} has no valid range for the selected choices ({names}). | De constraints van de geselecteerde keuzes laten geen bereik over (effectief minimum is groter dan effectief maximum). |
dimensions.{key} | Unknown dimension. | Het request bevat een dimensiekey die niet op het product is geconfigureerd. |
configuration | not_priceable | Er kon geen basisprijs worden bepaald: een product zonder dimensies heeft een null-prijs, geen enkele prijsmatrix heeft conditions waaraan de geselecteerde keuzes voldoen, of de winnende matrix heeft geen prijsregel die de dimensiewaarden dekt. Een matrix met een condition op een keuze die niet beschikbaar is, kan nooit vervuld worden en levert dus nooit een basisprijs; het schema toont zo'n matrix ook niet. |
Voorbeeld van een domeinvalidatiefout:
{
"message": "Width must be between 200 and 600 cm when Glass is selected.",
"errors": {
"dimensions.width": [
"Width must be between 200 and 600 cm when Glass is selected."
]
}
}Let op: requires_quote op een keuze veroorzaakt hier nooit een fout: het endpoint prijst zulke configuraties gewoon. De vlag, beschikbaar in het schema, signaleert alleen dat de storefront de koper naar een handmatige offerteaanvraag hoort te leiden in plaats van naar directe checkout.