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 with the commerce:read scope, sent as Authorization: Bearer {api_key}. 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.
typestringalwaysOne of simple, variable, configurable, digital, license, subscription. simple, variable, and configurable are stockable and shippable.
purchasabilitystringalwaysOne of direct, quote, both. quote means the product cannot be checked out directly; route the buyer to a quote request.
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.
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 no stock rows exist). 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.
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. 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",
      "type": "simple",
      "purchasability": "direct",
      "sku": "TABLE-OAK",
      "slug": "oak-table",
      "name": "Oak Table",
      "description": "Solid oak table.",
      "status": "active",
      "meta_title": null,
      "meta_description": 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 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.
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",
    "type": "variable",
    "purchasability": "direct",
    "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.",
    "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",
        "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 lacks the commerce:read scope.
403{"error":"Module not enabled: commerce"}Tenant does not have the commerce 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.