borkoldocs

Configurator

Price quote

Price a specific configuration of a configurable product and get a line-item breakdown.

View as Markdown

The quote endpoint prices one concrete configuration. Call it on every change to the buyer's selection to show a live total. It is read-only: no cart or order is created, and it requires only the commerce:read scope.

POST /v1/api/commerce/configurator/products/{id}/quote

Prices a specific configuration of an active configurable product. The server validates the selected choices (existence, active, at most one per single group, required groups covered, requires and excludes rules), then the dimensions (every configured dimension present, integer, inside its min and max after tightening by the selected choices' constraints), resolves the base price from the winning price matrix (or the product price when the product has no dimensions), applies choice deltas in two passes (fixed, per_area, and percent first, then each percent_of_total against that subtotal without compounding), and returns the total with a line-item breakdown. See the chapter introduction for the full algorithm.

Path parameters

NameTypeRequiredDescription
idstring (uuid)YesProduct ID. Must be an active product of type configurable.

Body parameters

The request body is JSON.

NameTypeRequiredDescription
dimensionsobjectConditionalMap of dimension key (from the schema) to integer value in the dimension's unit (for example cm). Required whenever the product has dimensions: every configured dimension must be present and numeric. Unknown keys are rejected with a 422. May be omitted only for a product with no dimensions.
choicesarray of string (uuid)NoSelected option choice IDs. Duplicates are de-duplicated. Each element must be a UUID referencing an active choice of this product. Required groups must be covered, and at most one choice per single group is allowed. Omit or send an empty array only when the product has no required groups.
Request
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"
    ]
  }'
Request (JS)
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();
}
Response
{
  "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"
    }
  }
}

Response fields

NameTypeRequiredDescription
totalintegeralwaysTotal price in minor units (cents).
currencystringalwaysThe product's ISO currency code, for example "EUR".
breakdownarrayalwaysLine items. Always starts with {"label":"Base","amount":<base>}, followed by one line per priced choice labeled "{Group name}: {Choice name}", in group and choice position order (with all percent_of_total lines after the others). Choices with a delta of 0 still appear with amount: 0. All amounts are integer minor units.
matrixobject or nullalways{id, name} of the price matrix that supplied the base price. null when the base came from the product's own price (a product without dimensions).

Errors

Authentication and infrastructure errors are the same as on the other storefront routes:

StatusBodyCause
401{"message":"Unauthenticated."}Missing or invalid API key.
403{"message":"Invalid ability provided."}Key lacks the commerce:read scope.
403{"error":"Module not enabled: commerce"}Tenant does not have the commerce module enabled. Note the error key instead of message.
403{"message":"Tenant suspended.","code":"tenant_suspended"}Tenant account is suspended.
404{"message":"..."}Product does not exist, is not active, or is not of type configurable.
429{"message":"Too Many Attempts."}Rate limit of 120 requests per minute exceeded.

422 validation errors

All validation failures use the standard Laravel envelope: {"message":"<first error>","errors":{"<field>":["<message>", ...]}}. There are two layers.

Input validation (shape of the request body): dimensions must be an object, choices must be an array, and every choices.* element must be a UUID. Example:

Response (422, input validation)
{
  "message": "The choices.0 field must be a valid UUID.",
  "errors": {
    "choices.0": ["The choices.0 field must be a valid UUID."]
  }
}

Domain validation (the configuration itself): the same envelope; each error key carries exactly one message. Choice errors are checked first and short-circuit: when any choice error occurs, dimensions are not validated in that response. The possible keys and messages:

Error keyMessageCause
choicesOne or more choices are not available.A choice ID does not exist on this product, or the choice is inactive or deleted.
choicesChoose one {Group}.Two or more choices selected in a single type group.
choices{Group} is required.A required group has no selected choice.
choicesArea-priced choice needs two dimensions.A per_area choice was selected on a product that does not have exactly two dimensions.
choices.{choiceId}{Choice} cannot be combined with {Target}.An excludes rule is violated.
choices.{choiceId}{Choice} requires one of: {names}.A requires rule is unmet (none of the targets in one target group is selected).
dimensions.{key}{Label} is required.The dimension is missing from the request or its value is not numeric.
dimensions.{key}{Label} must be between {min} and {max} {unit}.The value is outside the dimension's range.
dimensions.{key}{Label} must be between {min} and {max} {unit} when {choice names} is selected.The value is outside the range after tightening by the selected choices' constraints.
dimensions.{key}{Label} has no valid range for the selected choices ({names}).The selected choices' constraints collapse the range (effective min exceeds effective max).
dimensions.{key}Unknown dimension.The request contains a dimension key that is not configured on the product.
configurationnot_priceableNo base price could be resolved: a product without dimensions has a null price, no price matrix's conditions are satisfied by the selected choices, or the winning matrix has no price rule covering the dimension values.

Example domain validation failure:

Response (422, domain validation)
{
  "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."
    ]
  }
}

Note that requires_quote on a choice never causes an error here: the endpoint prices such configurations normally. The flag, exposed in the schema, only signals that the storefront should route the buyer to a manual quote request instead of direct checkout.