borkoldocs

Carts & Checkout

Cart lines

Add products to a cart, change line quantities, and remove lines, including variant and configurable-product rules.

View as Markdown

Cart lines hold the products in a cart. Each line snapshots the product name and SKU at add time, stores the resolved unit price in minor units, and, for configurable products, a full configuration snapshot. All three endpoints require the commerce:write scope, an active cart, and pass the cart ownership check (see the Carts page). Every endpoint returns the full cart envelope, the same shape as GET /v1/api/commerce/carts/{id}.

Product type rules

How a product is added depends on its type (simple, variable, configurable, digital, license, subscription):

  • Products with active variants (typically variable): variant_id is required and must be an active variant of the product.
  • Products without variants (simple, digital, license, subscription): variant_id must be omitted.
  • Configurable products: configuration is required and variant_id is forbidden. The configuration is an object {"dimensions": {...}, "choices": ["choice_id", ...]} priced by the configurator engine. The line stores a config_hash plus a full snapshot under attributes.configuration (dimensions, choices, price breakdown, component list, matrix cell). Component stock is reserved per configurator component.
  • For all other products, configuration is forbidden.

Lines deduplicate on the combination of product, variant, and configuration hash: adding the same combination again merges into the existing line, summing the quantity and re-resolving the unit price at the new quantity tier.

Unit prices resolve against the request's pricing context: the customer identified by X-Customer-Token if present, else the cart's bound customer, else guest pricing.

POST /v1/api/commerce/carts/{id}/lines

Adds a product to the cart, or merges into an existing identical line. Returns the full cart with status 201.

Parameters

Path

NameTypeRequiredDescription
idstring (UUID)yesCart id. Must be an active cart.

Body

NameTypeRequiredDescription
product_idstring (UUID)yesThe product to add. Must be an active product of the store.
variant_idstring (UUID) or nullconditionalRequired when the product has active variants; forbidden otherwise, and always forbidden for configurable products.
quantityintegeryesMinimum 1. The merged line quantity may not exceed the policy's max_quantity_per_line.
configurationobjectconditionalRequired for configurable products, forbidden otherwise. Shape: {"dimensions": {...}, "choices": [choice_ids]}. Validated by the configurator engine.

Headers

NameTypeRequiredDescription
X-Customer-TokenstringnoSelects the pricing context; required if the cart is bound to a non-guest customer.
Request
curl -X POST https://api.borkol.com/v1/api/commerce/carts/9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4/lines \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "variant_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
    "quantity": 2
  }'
Request (JS)
const res = await fetch(
  `https://api.borkol.com/v1/api/commerce/carts/${cartId}/lines`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      product_id: "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      variant_id: "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
      quantity: 2,
    }),
  }
);
const { data: cart } = await res.json();
Response
{
  "data": {
    "id": "9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4",
    "status": "active",
    "lines": [
      {
        "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
        "product_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "variant_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
        "name": "Garden Chair",
        "sku": "CHAIR-GRN-M",
        "quantity": 2,
        "unit_price_amount": 2250,
        "unit_price": { "amount": 2250, "currency": "EUR" },
        "line_total": { "amount": 4500, "currency": "EUR" },
        "attributes": null
      }
    ],
    "...": "same cart shape as GET /v1/api/commerce/carts/{id}"
  }
}

For a configurable product, the created line carries the configuration snapshot:

Response (configurable line excerpt)
{
  "attributes": {
    "configuration": {
      "dimensions": { "width": 300, "depth": 250 },
      "choices": ["c1d2e3f4-..."],
      "breakdown": [],
      "components": [{ "product_id": "...", "qty": 2 }],
      "matrix": {}
    }
  }
}

Errors

Domain failures are standard 422 validation errors with the message attached to the offending field in errors:

StatusBodyWhen
422errors.product_id: ["Product is not available."]Product missing, inactive, or (for configurable products) the configurator engine is absent.
422errors.variant_id: ["Choose a variant for this product."]Product has active variants and variant_id was omitted.
422errors.variant_id: ["Variant is not available."]Variant id does not resolve to an active variant of the product.
422errors.variant_id: ["This product has no variants."]variant_id sent for a product without variants.
422errors.variant_id: ["Configurable products have no variants."]variant_id sent for a configurable product.
422errors.configuration: ["Configure this product before adding it."]Configurable product without a configuration.
422errors.configuration: ["This product is not configurable."]configuration sent for a non-configurable product.
422errors.choices, errors.choices.{choiceId}, errors.dimensions.{key}, errors.configurationConfigurator engine errors, attached to their own field keys exactly as documented in the quote error catalog.
422{"message": "Quantity exceeds the per-line maximum.", "code": "cart_constraint"}Merged quantity would exceed max_quantity_per_line.
422{"message": "Cart line limit reached.", "code": "cart_constraint"}Adding a new distinct line would exceed max_lines.
404{"message": "..."}Cart unknown, expired, or not active.
403ownership, scope, module, or suspension bodySee the Carts page.
429{"message": "Too Many Attempts."}Rate limit exceeded.

PATCH /v1/api/commerce/carts/{id}/lines/{lineId}

Changes a line's quantity. Non-configured product lines are re-priced at the new quantity (tier pricing) using the request's pricing context. Configured lines, and lines whose variant has since been deactivated, keep their stored unit price; checkout is where unavailability surfaces. Returns the full cart with status 200.

Parameters

Path

NameTypeRequiredDescription
idstring (UUID)yesCart id. Must be an active cart.
lineIdstring (UUID)yesLine id. Must belong to this cart.

Body

NameTypeRequiredDescription
quantityintegeryesMinimum 1, maximum the policy's max_quantity_per_line.

Headers

NameTypeRequiredDescription
X-Customer-TokenstringnoSelects the pricing context; required if the cart is bound to a non-guest customer.
Request
curl -X PATCH https://api.borkol.com/v1/api/commerce/carts/9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4/lines/5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9 \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{"quantity": 3}'
Response
{
  "data": {
    "id": "9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4",
    "lines": [{ "id": "5e6f7a8b-...", "quantity": 3, "...": "..." }],
    "...": "same cart shape as GET /v1/api/commerce/carts/{id}"
  }
}

Errors

StatusBodyWhen
422{"message": "...", "errors": {"quantity": ["..."]}}Missing or non-integer quantity, or below 1.
422{"message": "Quantity exceeds the per-line maximum.", "code": "cart_constraint"}Above max_quantity_per_line.
404{"message": "..."}Cart unknown or not active, or the line does not belong to this cart.
403ownership, scope, module, or suspension bodySee the Carts page.
429{"message": "Too Many Attempts."}Rate limit exceeded.

DELETE /v1/api/commerce/carts/{id}/lines/{lineId}

Removes a line from the cart and releases any stock hold held for the cart. Returns the full cart, without the removed line, with status 200.

Parameters

Path

NameTypeRequiredDescription
idstring (UUID)yesCart id. Must be an active cart.
lineIdstring (UUID)yesLine id. Must belong to this cart.

No body.

Request
curl -X DELETE https://api.borkol.com/v1/api/commerce/carts/9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4/lines/5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9 \
  -H "Authorization: Bearer {api_key}"
Response
{
  "data": {
    "id": "9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4",
    "lines": [],
    "...": "same cart shape as GET /v1/api/commerce/carts/{id}"
  }
}

Errors

StatusBodyWhen
404{"message": "..."}Cart unknown or not active, or the line does not belong to this cart.
403ownership, scope, module, or suspension bodySee the Carts page.
429{"message": "Too Many Attempts."}Rate limit exceeded.