Products
List products
GET /access/api/v1/products
Query parameters:
| Parameter | Type | Description |
|---|---|---|
page | number | Page number (default: 1) |
limit | number | Items per page (default: 18, max: 50) |
sort | string | A-Z, Z-A, RECENT, OLD |
categories | string | Category slug — includes the category and all its descendants |
minPrice | number | Minimum price filter |
maxPrice | number | Maximum price filter |
featured | boolean | Only return featured products |
onSale | boolean | Only return products on sale |
term | string | Search within the product list |
Response:
{
"status": "success",
"data": {
"currentPage": 1,
"totalPages": 5,
"totalDocuments": 90,
"limit": 18,
"data": [
{
"_id": "69bc36fda53000e2ede23dca",
"title": "main product",
"slug": "main-product",
"featured": false,
"hasVariantions": false,
"date": "2026-03-19T17:48:45.415Z",
"categories": [
{ "_id": "69bc3612a53000e2ede23cff", "name": "child 2", "slug": "child-2" }
],
"primaryCategory": { "_id": "69bc3612a53000e2ede23cff", "name": "child 2", "slug": "child-2" },
"primaryImage": null,
"secondaryImage": null,
"price": 5,
"salePrice": null,
"onSale": false,
"actualPrice": 5,
"minPrice": null,
"maxPrice": null,
"sku": "SKU-B7WI956J7T",
"status": "in-stock",
"sellable": null,
"reviewAggregate": [{ "_id": null, "count": 1, "average": 3 }],
"reviewCount": 1,
"averageRating": 3
},
]
}
}
Pricing
Use actualPrice as the authoritative price for display and totals — it already accounts for any active sale. salePrice is only set when a discount is active and lower than price. For products without variations, minPrice and maxPrice are always null.
Stock
sellable is the number of units available. It is null when the merchant has stock tracking off for that product — in that case treat the product as always in stock. When tracking is on, use status (in-stock or out-of-stock) to decide whether to allow adding to cart.
Images
uploadUrl is a relative path, not a full URL. Prepend media.url from the settings response to build the full URL:
const imageUrl = settings.media.url + product.primaryImage.uploadUrl;
Always check that primaryImage is not null before accessing it.
Variant products
When hasVariantions is true, the product has multiple variants (e.g. different sizes or colours). In this case:
price,salePrice, andactualPriceare alwaysnull— there is no single price at the product level- Use
minPriceandmaxPriceto show the price range (e.g. "KES 200 – KES 800") - To get the exact price for a specific variant, call Get all variations or Get variation details
Get single product
GET /access/api/v1/products/:id
:id accepts either the product slug or the product _id.
Response — product without variations:
{
"status": "success",
"data": {
"_id": "69ac75f878a9512e92b56fb5",
"title": "product v2 main",
"slug": "product-v2-main",
"description": "<h1>We are the best here</h1><p>Product description...</p>",
"featured": false,
"hasVariantions": false,
"date": "2026-03-07T19:01:12.026Z",
"categories": [
{ "_id": "69bc34f9a53000e2ede23ba4", "name": "child 1", "slug": "child-1" }
],
"primaryCategory": { "_id": "69bc34f9a53000e2ede23ba4", "name": "child 1", "slug": "child-1" },
"media": [
{ "_id": "6985925dd59ed6381f73f026", "type": "image/jpeg", "size": "159472", "title": "image-1.jpg", "uploadUrl": "67e3cd865bf29cd7117f53a8/media/image-1.jpg", "position": 1 },
{ "_id": "69ac336aa7e93c257591fe5f", "type": "image/jpeg", "size": "36214", "title": "image-2.jpg", "uploadUrl": "67e3cd865bf29cd7117f53a8/media/image-2.jpg", "position": 2 }
],
"primaryImage": {
"_id": "6985925dd59ed6381f73f026",
"type": "image/jpeg",
"size": "159472",
"title": "image-1.jpg",
"uploadUrl": "67e3cd865bf29cd7117f53a8/media/image-1.jpg"
},
"secondaryImage": {
"_id": "69ac336aa7e93c257591fe5f",
"type": "image/jpeg",
"size": "36214",
"title": "image-2.jpg",
"uploadUrl": "67e3cd865bf29cd7117f53a8/media/image-2.jpg"
},
"price": 110,
"salePrice": 100,
"onSale": true,
"actualPrice": 100,
"minPrice": null,
"maxPrice": null,
"sku": "SKU-V4V0GDNPV7",
"status": "in-stock",
"sellable": 84,
"options": null,
"defaultOptions": null,
"hasShipping": true,
"shipping": {
"weight": 87,
"length": 55,
"width": 96,
"height": 12,
"weightUnit": "g",
"measurementUnit": "cm"
},
"reviewAggregate": [],
"reviewCount": 0,
"averageRating": 0
}
}
description is returned as an HTML-escaped string. Unescape it before rendering — see Get policy document for the unescape helper. options and defaultOptions are null for products without variations.
Response — product with variations:
{
"status": "success",
"data": {
"_id": "69ac80f778a9512e92b57705",
"title": "variable",
"slug": "variable",
"description": "",
"featured": false,
"hasVariantions": true,
"date": "2026-03-07T19:48:07.830Z",
"categories": [
{ "_id": "6985925bd59ed6381f73d88e", "name": "new one_edited", "slug": "new-one_edited" }
],
"primaryCategory": { "_id": "6985925bd59ed6381f73d88e", "name": "new one_edited", "slug": "new-one_edited" },
"media": [
{ "_id": "6985925dd59ed6381f73f21a", "type": "image/png", "size": "66684", "title": "image-1.png", "uploadUrl": "67e3cd865bf29cd7117f53a8/media/image-1.png", "position": 1 },
{ "_id": "6985925dd59ed6381f73f026", "type": "image/jpeg", "size": "159472", "title": "image-2.jpg", "uploadUrl": "67e3cd865bf29cd7117f53a8/media/image-2.jpg", "position": 2 }
],
"primaryImage": {
"_id": "6985925dd59ed6381f73f21a",
"type": "image/png",
"size": "66684",
"title": "image-1.png",
"uploadUrl": "67e3cd865bf29cd7117f53a8/media/image-1.png"
},
"secondaryImage": {
"_id": "6985925dd59ed6381f73f026",
"type": "image/jpeg",
"size": "159472",
"title": "image-2.jpg",
"uploadUrl": "67e3cd865bf29cd7117f53a8/media/image-2.jpg"
},
"price": null,
"salePrice": null,
"onSale": true,
"actualPrice": null,
"minPrice": 0,
"maxPrice": 50,
"sku": "SKU-SKU-sku-main",
"status": "in-stock",
"sellable": 5,
"options": [
{
"_id": "69ac80f778a9512e92b5770d",
"name": "Color",
"position": 1,
"values": [
{ "_id": "69ac80f778a9512e92b57707", "value": "red" },
{ "_id": "69ac80f778a9512e92b57708", "value": "green" },
{ "_id": "69ac80f778a9512e92b57709", "value": "blue" }
]
},
{
"_id": "69ac811078a9512e92b57737",
"name": "Size",
"position": 2,
"values": [
{ "_id": "69ac811078a9512e92b5772d", "value": "sm" },
{ "_id": "69ac811078a9512e92b5772e", "value": "lg" }
]
}
],
"defaultOptions": [
{
"_id": "69ac80f778a9512e92b5770d",
"name": "Color",
"value": { "_id": "69ac80f778a9512e92b57707", "value": "red" }
},
{
"_id": "69ac811078a9512e92b57737",
"name": "Size",
"value": { "_id": "69ac811078a9512e92b5772d", "value": "sm" }
}
],
"hasShipping": false,
"shipping": null,
"reviewAggregate": [],
"reviewCount": 0,
"averageRating": 0
}
}
options drives the variant selector UI — render one dropdown or button group per option using values. defaultOptions tells you which option values to pre-select on page load (the first variant to display). When the customer changes a selection, join the selected value _ids with a dash to form the variationKey and call Get all variations or Get variation details to fetch the matching variant's price, stock, and images.
To display reviews and the star rating summary on the product page, call the Reviews endpoints using the product _id.
Possible errors: PRODUCT_NOT_FOUND
Get all variations
GET /access/api/v1/products/variants
Fetch all variants for a product after loading the product detail page. Each variant contains its own price, stock, images, and shipping dimensions.
Query parameters:
| Parameter | Type | Required |
|---|---|---|
productId | string | Yes — slug or _id |
Response:
{
"status": "success",
"data": {
"product": {
"_id": "69859254d59ed6381f73ae2b",
"title": "details variant",
"slug": "details-variant"
},
"totalVariants": 2,
"variants": [
{
"_id": "6954440add51aff39872bbf6",
"key": "6954440add51aff39872bbd6-6954440add51aff39872bbd8",
"position": 1,
"optionsValues": [
{ "key": { "_id": "6954440add51aff39872bbde", "name": "Colors" }, "value": { "_id": "6954440add51aff39872bbd6", "value": "redz" } },
{ "key": { "_id": "6954440add51aff39872bbdc", "name": "Size" }, "value": { "_id": "6954440add51aff39872bbd8", "value": "sm" } }
],
"sku": "SKU-OTHER-MAIN",
"trackStock": true,
"status": "in-stock",
"sellable": 5,
"price": 5000,
"salePrice": 4300,
"onSale": true,
"actualPrice": 4300,
"hasShipping": true,
"shipping": {
"weight": 90,
"length": 180,
"width": 270,
"height": 360,
"weightUnit": "kg",
"measurementUnit": "cm"
},
"media": [
{ "_id": "68f3ae7aa0756e3f6977179c", "type": "image/png", "size": "70912", "title": "image-1.png", "uploadUrl": "67e3cd865bf29cd7117f53a8/media/image-1.png", "position": 1 }
],
"primaryImage": {
"_id": "68f3ae7aa0756e3f6977179c",
"type": "image/png",
"size": "70912",
"title": "image-1.png",
"uploadUrl": "67e3cd865bf29cd7117f53a8/media/image-1.png",
"position": 1
}
},
{
"_id": "6954440add51aff39872bbf7",
"key": "6954440add51aff39872bbd7-6954440add51aff39872bbd8",
"position": 2,
"optionsValues": [
{ "key": { "_id": "6954440add51aff39872bbde", "name": "Colors" }, "value": { "_id": "6954440add51aff39872bbd7", "value": "Blue" } },
{ "key": { "_id": "6954440add51aff39872bbdc", "name": "Size" }, "value": { "_id": "6954440add51aff39872bbd8", "value": "sm" } }
],
"sku": "SKU-2H01VGVROH",
"trackStock": false,
"status": "in-stock",
"sellable": null,
"price": 910,
"salePrice": null,
"onSale": false,
"actualPrice": 910,
"hasShipping": true,
"shipping": {
"weight": 55,
"length": 65,
"width": 75,
"height": 85,
"weightUnit": "kg",
"measurementUnit": "m"
},
"media": [
{ "_id": "68f3acbba0756e3f69771745", "type": "image/webp", "size": "87150", "title": "image-2.webp", "uploadUrl": "67e3cd865bf29cd7117f53a8/media/image-2.webp", "position": 1 }
],
"primaryImage": {
"_id": "68f3acbba0756e3f69771745",
"type": "image/webp",
"size": "87150",
"title": "image-2.webp",
"uploadUrl": "67e3cd865bf29cd7117f53a8/media/image-2.webp",
"position": 1
}
}
]
}
}
Each variant has a key — a dash-joined string of its option value IDs. When a customer selects options, join their chosen value _ids in the same order to form the key, then find the matching variant in this list to update the displayed price, stock, and images without another API call.
Possible errors: PRODUCT_ID_REQUIRED, PRODUCT_NOT_FOUND
Get variation details
GET /access/api/v1/products/variant-details
Fetches a single variant by product ID and variation key. Use this if you need to fetch one variant on demand rather than loading all variants upfront.
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
productId | string | Yes | Slug or _id |
variationKey | string | Yes | Dash-joined option value IDs matching the desired variant (e.g. 6954440add51aff39872bbd6-6954440add51aff39872bbd8) |
Response: Same shape as a single variant object from Get all variations.
Possible errors: PRODUCT_ID_REQUIRED, INVALID_VARIATION_KEY, INVALID_OPTION_VALUES, VARIANT_NOT_FOUND
Best sellers
GET /access/api/v1/products/best-sellers
Query parameters: page, limit
Response:
{
"status": "success",
"data": {
"currentPage": 1,
"totalPages": 1,
"totalDocuments": 2,
"limit": 12,
"data": []
}
}
Each item in data follows the same shape as List products.
Similar products
GET /access/api/v1/products/similar/:id
:id is the product slug or _id.
Query parameters: page, limit
Response:
{
"status": "success",
"data": {
"currentPage": 1,
"totalPages": 1,
"totalDocuments": 2,
"limit": 12,
"data": []
}
}
Each item in data follows the same shape as List products.
Hydrate cart
POST /access/api/v1/products/cart
Request body:
{
"cart": [
{
"productId": "69bc36fda53000e2ede23dca",
"quantity": 2,
"type": "product",
"variationKey": ""
}
]
}
variationKey is required for variant products — pass "" for products without variations.
Response:
{
"status": "success",
"total": 1,
"data": [
{
"_id": "69bc36fda53000e2ede23dca",
"title": "Running Shoes",
"slug": "running-shoes",
"primaryImage": null,
"secondaryImage": null,
"hasVariantions": false,
"primaryCategory": { "_id": "...", "name": "Footwear", "slug": "footwear" },
"media": [],
"price": 49.99,
"salePrice": null,
"onSale": false,
"actualPrice": 49.99,
"minPrice": null,
"maxPrice": null,
"sku": "SKU-B7WI956J7T",
"trackStock": false,
"status": "in-stock",
"sellable": null,
"variationKey": null,
"variation": null
}
]
}
Use actualPrice for order totals. Items no longer available are absent from the response.
Cart similarities
POST /access/api/v1/products/cart-similarities
Request body: Same as hydrate cart.
Query parameters: page, limit
Response:
{
"status": "success",
"data": {
"currentPage": 1,
"totalPages": 1,
"totalDocuments": 2,
"limit": 12,
"data": []
}
}
Each item in data follows the same shape as List products.