borkoldocs

Catalog

Products

List and fetch active products, including variants, images, categories, stock status, and customer-contextual pricing.

View as Markdown

The product endpoints return the authenticated tenant's active products. Both endpoints require a tenant API key carrying either the catalog:read or the commerce:read scope, sent as Authorization: Bearer {api_key}, and the catalog module enabled for the store. Tenancy is derived from the key, so no X-Tenant-Id header is needed. Requests count against the shared tenant rate limit of 120 requests per minute; exceeding it returns 429 {"message":"Too Many Attempts."} with Retry-After and X-RateLimit-* headers.

Only products with status active are ever returned. Draft, archived, and soft-deleted products are filtered from the list and return 404 on the detail endpoint.

Product object

All IDs are UUIDs. All money amounts are integers in minor units (cents) paired with the product's ISO currency code (for example "EUR", so 18900 means 189.00 EUR). Timestamps are ISO 8601 UTC.

NameTypeRequiredDescription
idstring (uuid)alwaysProduct ID.
capabilitystringalwaysHow the product is sold: standard (a plain or variant product, added to the cart with product_id and, when it has variants, variant_id) or made_to_measure (priced by the configurator, added to the cart with a configuration). Treat the enum as open and render an unknown value neutrally.
capability_activebooleanalwaysWhether the product's capability can be sold right now: standard needs the commerce module, made_to_measure the configurator module. When false a cart add refuses the product with CAPABILITY_INACTIVE; hide the buy button.
purchasabilitystringalwaysOne of direct, quote, both; direct when the merchant set nothing. quote means the product cannot be checked out directly; route the buyer to a quote request. The field is reported even when the store has the sales module disabled, but only a store with sales enabled (and quote requests switched on) refuses checkout for it.
unitstringalwaysUnit the product is sold in: piece, kg, g, l, m, m2, m3, or hour. A piece product takes whole cart quantities only; every other unit accepts up to three decimals (for example 0.75 kg).
skustring or nullalwaysProduct-level SKU.
slugstringalwaysURL slug, unique per tenant.
namestringalwaysProduct name.
descriptionstring or nullalwaysProduct description.
statusstringalwaysAlways active on this API.
meta_titlestring or nullalwaysSEO title.
meta_descriptionstring or nullalwaysSEO description.
compare_at_amountinteger or nullalwaysMerchant-set "compare at" (was) price in minor units, always higher than the product price. null when not set. Independent of list_price_amount, which reports the price before customer-group or tier pricing.
barcodestring or nullalwaysBarcode (EAN, UPC, or any merchant code).
weight_gramsinteger or nullalwaysShipping weight in grams.
requires_shippingbooleanalwaysWhether the product is physically shipped. A cart needs a shipping method at checkout only when it holds at least one line whose product requires shipping.
track_stockbooleanalwaysWhether the store tracks stock for this product. When false, stock_status is untracked and the product never runs out.
returnablebooleanalwaysWhether the merchant accepts returns. made_to_measure products are made to order and are never returnable once ordered, whatever this flag says (see the order line attributes.returnable).
lead_time_daysinteger or nullalwaysProduction or delivery lead time in days, 0 to 365. null when not set.
tagsarray of stringalwaysMerchant tags. Empty array when none.
social_imageobject or nullalwaysImage for social sharing (Open Graph), see the image object below, plus its id. null when not set; fall back to image.
priceobjectalways{amount, currency}. amount is the resolved unit price at quantity 1 for the request's customer context (guest when no X-Customer-Token header is sent). amount is null when the tenant hides prices from guests and the request is a guest request.
list_price_amountintegerconditionalOriginal (list) price in minor units. Present only when it differs from the resolved price and the tenant shows original prices.
quantity_tiersarrayconditional[{min_quantity, price_amount}] rows, present only when the tenant shows tier tables and product-scope tiers with min_quantity > 1 exist. Customer-group rows shadow public rows at the same min_quantity.
price_displayobjectconditional{mode} where mode is inc or ex (VAT-inclusive or VAT-exclusive display). Resolved from the tenant's vat_display setting (inc, ex, or auto; auto resolves to ex for B2B customers identified via X-Customer-Token, inc otherwise).
attributesobject or arrayalwaysFree-form attribute map set by the merchant. Serialized as [] when empty.
optionsarraydetail only[{name, values}], the product's option definitions (for example {"name":"Color","values":["Natural","Black"]}). Only included on GET /v1/api/commerce/products/{id}.
categoriesarrayalwaysActive categories the product belongs to: [{id, name, slug, is_active}].
variantsarrayalwaysActive variants, see the variant object below. Empty array when the product has no variants.
has_variantsbooleanalwaysWhether variants is non-empty.
price_fromobjectalways{amount, currency}. The lowest resolved variant price, or the product price when there are no variants. amount is null for guests when the tenant hides guest prices.
imageobject or nullalwaysPrimary image (the media item flagged primary, or the first one), see the image object below. null when the product has no media.
imagesarraydetail onlyFull gallery, each item is an image object plus id, is_primary (boolean), and sort_order (integer). Only included on GET /v1/api/commerce/products/{id}.
stock_statusstringconditionalOne of in_stock, low_stock, out_of_stock, backorder, untracked. untracked means the store does not count this product: track_stock is false, no stock rows exist, or the store has the stock module disabled. Omitted entirely when the stock module is not registered on the platform.
created_atstring (ISO 8601)alwaysCreation timestamp.
updated_atstring (ISO 8601)alwaysLast update timestamp.

The client-panel field stock_available is never included on this API.

Variant object

NameTypeRequiredDescription
idstring (uuid)alwaysVariant ID.
option_valuesobjectalwaysMap of option name to selected value, for example {"Color":"Natural"}.
skustring or nullalwaysVariant SKU.
barcodestring or nullalwaysVariant barcode.
weight_gramsinteger or nullalwaysVariant shipping weight in grams; null means the product's weight_grams applies.
priceobjectalways{amount, currency}. Falls back to the product price when the variant has no own price. amount is null for guests when the tenant hides guest prices.
is_activebooleanalwaysAlways true on this API (inactive variants are filtered out).
stock_statusstringconditionalSame enum as the product-level stock_status; every variant of a product with track_stock false is untracked. Present only when the stock module is registered.

Image object

NameTypeRequiredDescription
urlstringalwaysOriginal image URL.
variantsobjectalwaysOn-the-fly webp resize URLs keyed thumb (160px wide), card (480px wide), detail (1200px wide), served via wsrv.nl.
altstring or nullalwaysAlt text.

GET /v1/api/commerce/products

Returns a paginated list of the tenant's active products, newest first (created_at descending, not configurable). Each product includes its primary image, active variants, active categories, stock status (when the stock module is registered), and customer-contextual pricing. The list does not include the options array or the images gallery; fetch the detail endpoint for those. The page size is fixed at 20; there is no per_page, sort, or search parameter.

Query parameters

NameTypeRequiredDescription
filter[category]stringNoCategory slug. Matches products in that active category or any of its active descendants. An unknown or inactive slug yields an empty result set (200, not 404).
pageintegerNoPage number, default 1. Page size is fixed at 20.

Headers

NameTypeRequiredDescription
X-Customer-TokenstringNoStorefront customer session token. Switches pricing to that customer's context (group pricing, B2B ex-VAT display, tier tables). Without it, pricing is computed for a guest.
Request
curl "https://api.borkol.com/v1/api/commerce/products?filter%5Bcategory%5D=tables&page=1" \
  -H "Authorization: Bearer {api_key}" \
  -H "Accept: application/json"
Request (JS)
const res = await fetch(
  "https://api.borkol.com/v1/api/commerce/products?" +
    new URLSearchParams({ "filter[category]": "tables", page: "1" }),
  {
    headers: {
      Authorization: `Bearer ${apiKey}`,
      Accept: "application/json",
    },
  }
);
const { data, links, meta } = await res.json();
Response
{
  "data": [
    {
      "id": "9c2f1e6a-8b4d-4f2a-9e11-3d5a7b9c0e21",
      "capability": "standard",
      "capability_active": true,
      "purchasability": "direct",
      "unit": "piece",
      "sku": "TABLE-OAK",
      "slug": "oak-table",
      "name": "Oak Table",
      "description": "Solid oak table.",
      "status": "active",
      "meta_title": null,
      "meta_description": null,
      "compare_at_amount": null,
      "barcode": null,
      "weight_grams": 32000,
      "requires_shipping": true,
      "track_stock": true,
      "returnable": true,
      "lead_time_days": null,
      "tags": [],
      "social_image": null,
      "price": { "amount": 54900, "currency": "EUR" },
      "price_display": { "mode": "inc" },
      "attributes": [],
      "categories": [
        {
          "id": "7a1b3c5d-2e4f-4a6b-8c0d-1e2f3a4b5c6d",
          "name": "Tables",
          "slug": "tables",
          "is_active": true
        }
      ],
      "variants": [],
      "has_variants": false,
      "price_from": { "amount": 54900, "currency": "EUR" },
      "image": {
        "url": "https://cdn.borkol.com/t/9c2f/table.jpg",
        "variants": {
          "thumb": "https://wsrv.nl/?url=...&w=160&we=1&output=webp&q=80",
          "card": "https://wsrv.nl/?url=...&w=480&we=1&output=webp&q=80",
          "detail": "https://wsrv.nl/?url=...&w=1200&we=1&output=webp&q=80"
        },
        "alt": "Oak table"
      },
      "stock_status": "in_stock",
      "created_at": "2026-07-02T09:14:33.000000Z",
      "updated_at": "2026-08-01T15:20:11.000000Z"
    }
  ],
  "links": {
    "first": "https://api.borkol.com/v1/api/commerce/products?page=1",
    "last": "https://api.borkol.com/v1/api/commerce/products?page=3",
    "prev": null,
    "next": "https://api.borkol.com/v1/api/commerce/products?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 3,
    "links": [
      { "url": null, "label": "« Previous", "active": false },
      {
        "url": "https://api.borkol.com/v1/api/commerce/products?page=1",
        "label": "1",
        "active": true
      }
    ],
    "path": "https://api.borkol.com/v1/api/commerce/products",
    "per_page": 20,
    "to": 20,
    "total": 47
  }
}

The envelope is the standard Laravel resource paginator: a top-level data array plus links (first, last, prev, next, absolute URLs or null) and meta (current_page, from, last_page, links, path, per_page, to, total). per_page is always 20.

Errors

StatusBodyCause
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: catalog"}Tenant does not have the catalog module enabled. Note the error key instead of message.
403{"message":"Tenant suspended.","code":"tenant_suspended"}Tenant account is suspended.
429{"message":"Too Many Attempts."}Rate limit of 120 requests per minute exceeded.

GET /v1/api/commerce/products/{id}

Fetches one active product by UUID. Compared to the list endpoint, the detail response additionally includes the options array and the full images gallery (with is_primary and sort_order). Stock status and pricing are enriched the same way as on the list: guest pricing unless an X-Customer-Token header is present. Returns 404 for draft, archived, soft-deleted, or unknown products.

Path parameters

NameTypeRequiredDescription
idstring (uuid)YesProduct ID.

Headers

NameTypeRequiredDescription
X-Customer-TokenstringNoStorefront customer session token. Switches pricing to that customer's group and B2B context.
Request
curl "https://api.borkol.com/v1/api/commerce/products/9c2f1e6a-8b4d-4f2a-9e11-3d5a7b9c0e21" \
  -H "Authorization: Bearer {api_key}" \
  -H "Accept: application/json"
Response
{
  "data": {
    "id": "9c2f1e6a-8b4d-4f2a-9e11-3d5a7b9c0e21",
    "capability": "standard",
    "capability_active": true,
    "purchasability": "direct",
    "unit": "piece",
    "sku": "CHAIR-OAK",
    "slug": "oak-dining-chair",
    "name": "Oak Dining Chair",
    "description": "Solid oak chair with linen seat.",
    "status": "active",
    "meta_title": "Oak Dining Chair",
    "meta_description": "Handmade solid oak dining chair.",
    "compare_at_amount": 22900,
    "barcode": "8712345678906",
    "weight_grams": 6500,
    "requires_shipping": true,
    "track_stock": true,
    "returnable": true,
    "lead_time_days": 5,
    "tags": ["oak", "dining"],
    "social_image": {
      "id": "3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
      "url": "https://cdn.borkol.com/t/9c2f/chair-social.jpg",
      "variants": {
        "thumb": "https://wsrv.nl/?url=...&w=160&we=1&output=webp&q=80",
        "card": "https://wsrv.nl/?url=...&w=480&we=1&output=webp&q=80",
        "detail": "https://wsrv.nl/?url=...&w=1200&we=1&output=webp&q=80"
      },
      "alt": "Oak dining chair in a dining room"
    },
    "price": { "amount": 18900, "currency": "EUR" },
    "list_price_amount": 21900,
    "quantity_tiers": [
      { "min_quantity": 5, "price_amount": 17900 },
      { "min_quantity": 10, "price_amount": 16900 }
    ],
    "price_display": { "mode": "inc" },
    "attributes": { "material": "oak" },
    "options": [
      { "name": "Color", "values": ["Natural", "Black"] }
    ],
    "categories": [
      {
        "id": "7a1b3c5d-2e4f-4a6b-8c0d-1e2f3a4b5c6d",
        "name": "Chairs",
        "slug": "chairs",
        "is_active": true
      }
    ],
    "variants": [
      {
        "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
        "option_values": { "Color": "Natural" },
        "sku": "CHAIR-OAK-NAT",
        "barcode": "8712345678913",
        "weight_grams": null,
        "price": { "amount": 18900, "currency": "EUR" },
        "is_active": true,
        "stock_status": "in_stock"
      }
    ],
    "has_variants": true,
    "price_from": { "amount": 18900, "currency": "EUR" },
    "image": {
      "url": "https://cdn.borkol.com/t/9c2f/chair.jpg",
      "variants": {
        "thumb": "https://wsrv.nl/?url=...&w=160&we=1&output=webp&q=80",
        "card": "https://wsrv.nl/?url=...&w=480&we=1&output=webp&q=80",
        "detail": "https://wsrv.nl/?url=...&w=1200&we=1&output=webp&q=80"
      },
      "alt": "Oak dining chair front view"
    },
    "images": [
      {
        "id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
        "url": "https://cdn.borkol.com/t/9c2f/chair.jpg",
        "variants": {
          "thumb": "https://wsrv.nl/?url=...&w=160&we=1&output=webp&q=80",
          "card": "https://wsrv.nl/?url=...&w=480&we=1&output=webp&q=80",
          "detail": "https://wsrv.nl/?url=...&w=1200&we=1&output=webp&q=80"
        },
        "alt": "Oak dining chair front view",
        "is_primary": true,
        "sort_order": 0
      }
    ],
    "stock_status": "in_stock",
    "created_at": "2026-07-02T09:14:33.000000Z",
    "updated_at": "2026-08-01T15:20:11.000000Z"
  }
}

Errors

StatusBodyCause
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: catalog"}Tenant does not have the catalog module enabled.
403{"message":"Tenant suspended.","code":"tenant_suspended"}Tenant account is suspended.
404{"message":"..."}Product does not exist, is soft-deleted, or its status is not active.
429{"message":"Too Many Attempts."}Rate limit of 120 requests per minute exceeded.