# Configuration schema

The schema endpoint returns everything a storefront needs to render a configurator UI for one product: dimensions with their per-choice range constraints, the product's price matrices (for hinting which matrix applies), and the option groups with their choices, price deltas, cross-choice rules, images, and `requires_quote` flags. See the chapter introduction for how these concepts fit together.

## GET /v1/api/commerce/configurator/products/{id}/schema

Returns the complete configuration schema for one active configurable product. Everything in the response is pruned to currently available choices: a choice appears only when it is active, not deleted, and every one of its `requires` target groups still holds at least one available choice (pruning cascades, so a choice that requires an unavailable choice disappears too). Dimension constraints and `requires` and `excludes` lists reference only those available choice IDs, and a price matrix conditioned on a choice that is not available is omitted from `price_matrices` entirely. Returns 404 for any product that is not both `status = active` and `capability = made_to_measure`.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| id | string (uuid) | Yes | Product ID. Must be an active product with capability `made_to_measure`. |

### Response fields

The `data` object has three keys.

**data.dimensions** (array, in position order; the order defines the mapping to price matrix axes `dim1` and `dim2`):

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| key | string | always | Request key to use for this dimension in quote calls. |
| label | string | always | Display label. |
| unit | string | always | Display unit string, for example `"cm"`. |
| min | integer | always | Minimum allowed value before constraint tightening. |
| max | integer | always | Maximum allowed value before constraint tightening. |
| constraints | array | always | Per-choice range tightenings; empty array when none. Each item: `choice_id` (uuid), `min` (integer or null), `max` (integer or null). When that choice is selected, a non-null `min` raises the effective minimum and a non-null `max` lowers the effective maximum. |

**data.price_matrices** (array, in creation order):

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| id | string (uuid) | always | Matrix ID. Echoed back by the quote endpoint as `matrix.id` when this matrix supplied the base price. |
| name | string | always | Matrix name. |
| conditions | array of string (uuid) | always | Choice IDs that must all be selected for the matrix to be eligible. Empty array means always eligible. Among eligible matrices, the one with the most conditions wins. Matrices conditioned on a choice that is not available are not listed at all, so this array is never shortened relative to what the engine requires. |

**data.option_groups** (array, in position order):

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| id | string (uuid) | always | Group ID. |
| name | string | always | Group name. Also used as the prefix of quote breakdown labels (`"Group: Choice"`). |
| type | string | always | `single` (at most one choice selectable) or `multi` (any number). |
| required | boolean | always | Whether at least one choice from this group must be selected. |
| choices | array | always | Choices in position order, see below. |

**data.option_groups[].choices[]**:

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| id | string (uuid) | always | Choice ID. Send this in the quote request's `choices` array. |
| name | string | always | Choice name. |
| delta_type | string | always | `fixed`, `per_area`, `percent`, or `percent_of_total`. |
| delta_value | integer | always | Minor units for `fixed`; minor units per square meter for `per_area`; percent times 100 for `percent` and `percent_of_total` (1050 = 10.50 percent). |
| is_default | boolean | always | UI hint for the initial selection. |
| requires | array of string (uuid) | always | Choice IDs this choice requires. OR within one target group, AND across target groups. |
| excludes | array of string (uuid) | always | Choice IDs this choice cannot be combined with. |
| images | array | always | `[{url, alt}]`; empty array when the choice has no media. |
| requires_quote | boolean | always | When `true`, the storefront should route the buyer to a manual quote request instead of checkout. Informational: the quote endpoint still prices the configuration. |

```bash title="Request"
curl "https://api.borkol.com/v1/api/commerce/configurator/products/9c2f1e6a-8b4d-4f2a-9e11-3d5a7b9c0e21/schema" \
  -H "Authorization: Bearer {api_key}" \
  -H "Accept: application/json"
```

```javascript title="Request (JS)"
const res = await fetch(
  `https://api.borkol.com/v1/api/commerce/configurator/products/${productId}/schema`,
  {
    headers: {
      Authorization: `Bearer ${apiKey}`,
      Accept: "application/json",
    },
  }
);
const { data } = await res.json();
```

```json title="Response"
{
  "data": {
    "dimensions": [
      {
        "key": "width",
        "label": "Width",
        "unit": "cm",
        "min": 200,
        "max": 700,
        "constraints": [
          {
            "choice_id": "c1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c",
            "min": null,
            "max": 600
          }
        ]
      },
      {
        "key": "depth",
        "label": "Depth",
        "unit": "cm",
        "min": 150,
        "max": 400,
        "constraints": []
      }
    ],
    "price_matrices": [
      {
        "id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a",
        "name": "Glass roof",
        "conditions": ["c1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c"]
      },
      {
        "id": "e5f6a7b8-c9d0-4e1f-a2b3-c4d5e6f7a8b9",
        "name": "Base",
        "conditions": []
      }
    ],
    "option_groups": [
      {
        "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "Roof type",
        "type": "single",
        "required": true,
        "choices": [
          {
            "id": "c1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c",
            "name": "Glass",
            "delta_type": "fixed",
            "delta_value": 0,
            "is_default": true,
            "requires": [],
            "excludes": ["f6a7b8c9-d0e1-4f2a-b3c4-d5e6f7a8b9c0"],
            "images": [
              {
                "url": "https://cdn.borkol.com/t/9c2f/glass-roof.jpg",
                "alt": "Glass roof"
              }
            ],
            "requires_quote": false
          }
        ]
      },
      {
        "id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
        "name": "LED lighting",
        "type": "multi",
        "required": false,
        "choices": [
          {
            "id": "f6a7b8c9-d0e1-4f2a-b3c4-d5e6f7a8b9c0",
            "name": "LED set (6)",
            "delta_type": "per_area",
            "delta_value": 2500,
            "is_default": false,
            "requires": ["c1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c"],
            "excludes": [],
            "images": [],
            "requires_quote": false
          }
        ]
      }
    ]
  }
}
```

### Errors

| Status | Body | Cause |
| --- | --- | --- |
| 401 | `{"message":"Unauthenticated."}` | Missing or invalid API key. |
| 403 | `{"message":"Invalid ability provided."}` | Key carries neither the `catalog:read` nor the `commerce:read` scope. |
| 403 | `{"error":"Module not enabled: configurator"}` | Tenant does not have the configurator 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 its capability is not `made_to_measure`. |
| 429 | `{"message":"Too Many Attempts."}` | Rate limit of 120 requests per minute exceeded. |
