Skip to content

API reference

Catalogue

The products the shop's website shows, with their categories, brands, search and reviews. The website shows every active product, and a discontinued one only while the website branch still holds stock of it. Prices and stock come from the website branch chosen on the shop's Storefront API page, or the shop's first active branch when none is chosen: a product's price is the lowest of its variants' prices there, the branch's own price where it has one, before any sale, and its stock is what that branch holds. Prices are numbers in shillings. Lists of product cards come in pages of 20, up to 50 with per_page, in Laravel's paginator shape.

Every call sends the shop's key in the X-Storefront-Key header and asks for JSON. Calls marked with a customer token also send the signed-in customer's token. The integration guide explains keys, answers, errors and limits.

Products

GET

/api/v1/products

Scope
catalogue
Customer token
none

A page of the website's product cards, newest first unless another sort is asked for. The filters combine. When exactly one category slug is given and found, meta.category carries that category, its parent and its active sub-categories for a breadcrumb and filter list; the sub-categories come featured first, then popular, then the fullest.

Parameters

category string · query
An active category's slug, or several separated by commas. A parent category takes in its active sub-categories' products. Unknown slugs are ignored, and when none is known the list is not filtered by category.
brand string · query
A brand's slug, or several separated by commas.
filter string · query
sale for products on a sale running today and in stock at the website branch, or featured for featured products.
on_sale, is_featured boolean · query
The same as filter=sale and filter=featured, so both can be asked for at once.
min_price, max_price number · query
Keeps products whose price, before any sale, is at least or at most this, both ends included. A value that is not a number is ignored.
q string · query
Words the product's name or description holds, in any case. GET search looks in more places.
sort string · query
newest (the default), sale_first (products on a running sale first, then newest), price_asc, price_desc, name_asc or popular (most viewed first). Anything else sorts newest first.
per_page integer · query
Products a page, 1 to 50; 20 by default. A number outside the range is brought into it.
page integer · query
The page, from 1.

The card's price is the product's lowest variant price at the website branch; sale_price and discount_percentage are set only while a sale runs. stock_quantity is what the branch holds across all the product's variants, and is_new means added in the last 30 days. description, cost_price, weight, attributes, tags and the SEO fields are always empty on a card; GET products/{slug} has them. The paginator's links and meta.links carry only page, not the filters, sort or per_page, so build the next page's address from your own query.

Example answer
{
  "data": [
    {
      "_id": "01a16356-8a39-73c4-be32-af0ba275c8f5",
      "name": "Muc-Off tubeless sealant 140 ml",
      "slug": "muc-off-tubeless-sealant-140-ml",
      "sku": "MUC-SEAL-140",
      "description": null,
      "short_description": null,
      "price": 1800,
      "sale_price": null,
      "discount_percentage": null,
      "cost_price": null,
      "stock_quantity": 20,
      "manage_stock": true,
      "in_stock": true,
      "is_featured": false,
      "is_new": true,
      "has_variants": false,
      "is_active": true,
      "weight": null,
      "weight_unit": null,
      "images": [
        {
          "_id": "01a16356-8a3d-7128-9e41-6c2f0d8b7a15",
          "url": "https://media.debianpos.com/products/muc-off-tubeless-sealant-140-ml.jpg",
          "alt": "Muc-Off tubeless sealant 140 ml",
          "is_primary": true,
          "sort_order": 0
        }
      ],
      "primary_image": {
        "_id": "01a16356-8a3d-7128-9e41-6c2f0d8b7a15",
        "url": "https://media.debianpos.com/products/muc-off-tubeless-sealant-140-ml.jpg",
        "alt": "Muc-Off tubeless sealant 140 ml",
        "is_primary": true,
        "sort_order": 0
      },
      "category": {
        "_id": "01a16356-869c-7039-bd72-25da5ed9fb14",
        "id": "01a16356-869c-7039-bd72-25da5ed9fb14",
        "name": "Tyres and tubes",
        "slug": "tyres-and-tubes",
        "description": null,
        "image_url": null,
        "parent_id": "01a16356-8693-72a0-882a-401f785a9eb9"
      },
      "parent_category": {
        "_id": "01a16356-8693-72a0-882a-401f785a9eb9",
        "id": "01a16356-8693-72a0-882a-401f785a9eb9",
        "name": "Parts",
        "slug": "parts"
      },
      "brand": {
        "_id": "01a16356-8a34-73b5-ab46-854e2ca956df",
        "id": "01a16356-8a34-73b5-ab46-854e2ca956df",
        "name": "Muc-Off",
        "slug": "muc-off",
        "logo_url": null,
        "description": null
      },
      "attributes": [],
      "tags": [],
      "meta_title": null,
      "meta_description": null,
      "created_at": "2026-10-22T11:19:38.000000Z",
      "updated_at": "2026-10-22T11:19:38.000000Z"
    }
  ],
  "links": {
    "first": "https://app.debianpos.com/api/v1/products?page=1",
    "last": "https://app.debianpos.com/api/v1/products?page=1",
    "prev": null,
    "next": null
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 1,
    "links": [
      {
        "url": "https://app.debianpos.com/api/v1/products?page=1",
        "label": "1",
        "page": 1,
        "active": true
      }
    ],
    "path": "https://app.debianpos.com/api/v1/products",
    "per_page": 20,
    "to": 4,
    "total": 4,
    "category": {
      "id": "01a16356-8693-72a0-882a-401f785a9eb9",
      "name": "Parts",
      "slug": "parts",
      "description": null,
      "is_parent": true,
      "has_children": true,
      "parent": null,
      "children": [
        {
          "id": "01a16356-869c-7039-bd72-25da5ed9fb14",
          "name": "Tyres and tubes",
          "slug": "tyres-and-tubes",
          "product_count": 2
        }
      ]
    }
  }
}
GET

/api/v1/products/slugs

Scope
catalogue
Customer token
none

Every product the website shows, as its slug and when it last changed, most recently changed first. Not paged. For a site built ahead of time and its sitemap.

Example answer
{
  "data": [
    {
      "slug": "giant-talon-29-2",
      "updated_at": "2026-10-22T17:57:42.000000Z"
    },
    {
      "slug": "shimano-deore-12-speed-chain",
      "updated_at": "2026-10-22T11:19:41.000000Z"
    }
  ]
}
GET

/api/v1/products/new-arrivals

Scope
catalogue
Customer token
none

Up to 12 product cards, those in stock at the website branch first, and within each, the one added most recently first. An out-of-stock product appears only when fewer than 12 are in stock. Not paged; the same shape as GET products/featured.

GET

/api/v1/products/price-bounds

Scope
catalogue
Customer token
none

The lowest and highest product price on the website, before any sale, in whole shillings, for a price filter's slider. Products with no price are left out. With no priced product at all it answers 0 and 100000.

The bounds are cut to whole shillings, not rounded, so a highest price of 1,999.50 answers 1999.

Example answer
{
  "data": {
    "min_price": 1800,
    "max_price": 145000
  }
}
GET

/api/v1/products/by-merchant-id/{id}

Scope
catalogue
Customer token
none

The product card a Google Merchant Center item ID points at, for a page a shopping ad links to. The ID is looked up as a variant's SKU or ID first, then as a product's ID. When a variant matched, variant carries its ID, a label made of its colour and size ("Standard" when it has neither) and its price at the website branch before any sale.

Parameters

id string · path · required
A variant's SKU or ID, or a product's ID.

Answers 404 with "Product not found" when nothing matches or the product is not on the website. Matched by a product's ID, the answer has no variant.

Example answer
{
  "data": {
    "_id": "01a16356-8716-7254-a23b-4fa5bdd2e54c",
    "name": "Fox Speedframe helmet",
    "slug": "fox-speedframe-helmet",
    "sku": "FOX-SPF",
    "description": null,
    "short_description": null,
    "price": 24000,
    "sale_price": null,
    "discount_percentage": null,
    "cost_price": null,
    "stock_quantity": 22,
    "manage_stock": true,
    "in_stock": true,
    "is_featured": false,
    "is_new": true,
    "has_variants": false,
    "is_active": true,
    "weight": null,
    "weight_unit": null,
    "images": [],
    "primary_image": null,
    "category": {
      "_id": "01a16356-8690-7032-8482-2c7bc9a66b63",
      "id": "01a16356-8690-7032-8482-2c7bc9a66b63",
      "name": "Helmets",
      "slug": "helmets",
      "description": null,
      "image_url": null,
      "parent_id": null
    },
    "parent_category": null,
    "brand": {
      "_id": "01a16356-8711-7365-bc56-f1bd9f9a311c",
      "id": "01a16356-8711-7365-bc56-f1bd9f9a311c",
      "name": "Fox Racing",
      "slug": "fox-racing",
      "logo_url": null,
      "description": null
    },
    "attributes": [],
    "tags": [],
    "meta_title": null,
    "meta_description": null,
    "created_at": "2026-10-22T11:19:37.000000Z",
    "updated_at": "2026-10-03T06:30:00.000000Z"
  },
  "variant": {
    "_id": "01a16356-871a-70a2-a045-f9bde015f03e",
    "label": "Standard",
    "price": 24000
  }
}
GET

/api/v1/products/{slug}

Scope
catalogue
Customer token
none

A product's page. variants holds the variants a customer may pick, each with its price and stock at the website branch: one set to show on the website always, one left automatic until 90 days after it ran out. quantity is the branch's stock of those variants. reviews holds the average of the approved reviews to one decimal, their count and the five newest. meta is the product's own SEO title and description, else its name and the shop's name, and its short description, else "Buy {brand} {name} at {shop}, from {price}." with the description after it, cut to 160 characters.

Parameters

slug string · path · required
The product's slug.

Answers 404 with "No query results for model [App\Models\Product]." when no product the website shows has the slug. discounted_price and discount_percentage are set only while a sale runs. pdf_specification is the address of the product's specification sheet, or null.

Example answer
{
  "data": {
    "id": "01a16356-86d9-70ef-a8b8-caf1c046af33",
    "name": "Trek Marlin 5",
    "slug": "trek-marlin-5",
    "description": null,
    "short_description": "A hardtail trail bike with hydraulic disc brakes and a lockout fork.",
    "price": 112000,
    "discounted_price": null,
    "discount_percentage": null,
    "sku": "TRK-MAR5-M",
    "gtin": null,
    "condition": "new",
    "quantity": 10,
    "in_stock": true,
    "has_variants": true,
    "images": [
      {
        "id": "01a16356-86dc-7310-b6f4-2a8e1c9d5f07",
        "url": "https://media.debianpos.com/products/trek-marlin-5.jpg",
        "alt": "Trek Marlin 5"
      }
    ],
    "category": {
      "id": "01a16356-8686-731e-b7a2-f260c4766329",
      "name": "Mountain bikes",
      "slug": "mountain-bikes"
    },
    "parent_category": {
      "id": "01a16356-8681-735e-9814-a766a09b45ec",
      "name": "Bikes",
      "slug": "bikes"
    },
    "brand": {
      "id": "01a16356-86d1-7220-a46f-ab47e5711905",
      "name": "Trek",
      "slug": "trek"
    },
    "variants": [
      {
        "id": "01a16356-86df-7218-9383-60fd40425609",
        "sku": "TRK-MAR5-M",
        "size": null,
        "color": null,
        "name": "Medium",
        "price": 112000,
        "discounted_price": null,
        "quantity": 10,
        "in_stock": true,
        "images": [
          {
            "id": "01a16356-86dc-7310-b6f4-2a8e1c9d5f07",
            "url": "https://media.debianpos.com/products/trek-marlin-5.jpg",
            "is_primary": true
          }
        ]
      },
      {
        "id": "01a16356-86e0-71f0-9efb-7b04efb96aa7",
        "sku": "TRK-MAR5-L",
        "size": null,
        "color": null,
        "name": "Large",
        "price": 112000,
        "discounted_price": null,
        "quantity": 0,
        "in_stock": false,
        "images": []
      }
    ],
    "reviews": {
      "average_rating": 4.5,
      "count": 2,
      "items": [
        {
          "rating": 4,
          "comment": "Solid trail bike for the price. The stock tyres are a bit thin for Karura in the rains.",
          "author": "Brian Otieno",
          "date": "2026-10-22T11:19:39.000000Z"
        }
      ]
    },
    "meta": {
      "title": "Trek Marlin 5 | Bike Yard",
      "description": "A hardtail trail bike with hydraulic disc brakes and a lockout fork."
    },
    "is_new": true,
    "is_featured": false,
    "is_discontinued": false,
    "pdf_specification": null
  }
}

Categories

GET

/api/v1/categories

Scope
catalogue
Customer token
none

The active top-level categories, each with its active sub-categories in children, and how many of the website's products each holds; a top-level category's count takes in its sub-categories'. Categories with no products are included. Top-level categories come fullest first, then by name; sub-categories come featured first, then popular, then fullest, then by name. A top-level category's is_featured and is_popular are always false. Not paged.

Example answer
{
  "data": [
    {
      "id": "01a16356-8693-72a0-882a-401f785a9eb9",
      "name": "Parts",
      "slug": "parts",
      "description": null,
      "image": null,
      "product_count": 4,
      "is_featured": false,
      "is_popular": false,
      "children": [
        {
          "id": "01a16356-869c-7039-bd72-25da5ed9fb14",
          "name": "Tyres and tubes",
          "slug": "tyres-and-tubes",
          "description": null,
          "image": "https://media.debianpos.com/categories/tyres-and-tubes.jpg",
          "product_count": 2,
          "is_featured": false,
          "is_popular": true
        }
      ]
    }
  ]
}
GET

/api/v1/categories/slugs

Scope
catalogue
Customer token
none

Every active category, top-level and sub-category, as its slug and when it last changed, most recently changed first, including those with no products. Not paged.

Example answer
{
  "data": [
    {
      "slug": "accessories",
      "updated_at": "2026-10-22T11:19:37.000000Z"
    },
    {
      "slug": "bikes",
      "updated_at": "2026-10-22T11:19:37.000000Z"
    }
  ]
}
GET

/api/v1/categories/{slug}

Scope
catalogue
Customer token
none

One active category for its page heading. product_count counts the website's products in this category itself, not in its sub-categories. meta is the category's own SEO title and description, else its name and the shop's name, and "Explore our {name} collection at {shop}." followed by the first 100 characters of its description.

Parameters

slug string · path · required
The category's slug.

Answers 404 with "No query results for model [App\Models\Category]." when no active category has the slug.

Example answer
{
  "data": {
    "id": "01a16356-8690-7032-8482-2c7bc9a66b63",
    "name": "Helmets",
    "slug": "helmets",
    "description": null,
    "image": "https://media.debianpos.com/categories/helmets.jpg",
    "product_count": 2,
    "is_featured": false,
    "is_popular": false,
    "meta": {
      "title": "Helmets | Bike Yard",
      "description": "Explore our Helmets collection at Bike Yard."
    }
  }
}
GET

/api/v1/categories/{slug}/products

Scope
catalogue
Customer token
none

A page of the category's product cards, with its active sub-categories' products, newest first unless another sort is asked for. The same shape as GET products, without meta.category.

Parameters

slug string · path · required
The category's slug.
sort string · query
newest (the default), sale_first, price_asc, price_desc, name_asc or popular, as on GET products.
per_page integer · query
Products a page, 1 to 50; 20 by default.
page integer · query
The page, from 1.

Answers 404 with "No query results for model [App\Models\Category]." when no active category has the slug. GET products?category= does the same with filters and the breadcrumb.

Brands

GET

/api/v1/brands

Scope
catalogue
Customer token
none

The active brands that have at least one product on the website, with how many. Featured brands come first, then the fullest, then those shown on the home page, then the popular, then by name. Not paged.

Example answer
{
  "data": [
    {
      "id": "01a16356-86ad-7209-bdac-4d02b17e8836",
      "name": "Giant",
      "slug": "giant",
      "logo_url": "https://media.debianpos.com/brands/giant.png",
      "product_count": 2,
      "is_featured": false,
      "is_popular": false,
      "show_on_homepage": false
    }
  ]
}
GET

/api/v1/brands/slugs

Scope
catalogue
Customer token
none

Every active brand as its slug and when it last changed, most recently changed first, including brands with no product on the website. Not paged.

Example answer
{
  "data": [
    {
      "slug": "muc-off",
      "updated_at": "2026-10-22T11:19:38.000000Z"
    },
    {
      "slug": "fox-racing",
      "updated_at": "2026-10-22T11:19:37.000000Z"
    }
  ]
}
GET

/api/v1/brands/{slug}

Scope
catalogue
Customer token
none

One active brand in data and a page of its product cards in products, in the same paginator shape as GET products. The products in stock at the website branch come first, then the sort. meta is the brand's own SEO title and description, else its name and the shop's name, and "Explore our {name} collection at {shop}." followed by the first 100 characters of its description.

Parameters

slug string · path · required
The brand's slug.
sort string · query
newest (the default), sale_first, price_asc, price_desc, name_asc or popular, as on GET products.
per_page integer · query
Products a page, 1 to 50; 20 by default.
page integer · query
The page, from 1.

Answers 404 with "No query results for model [App\Models\Brand]." when no active brand has the slug.

Example answer
{
  "data": {
    "id": "01a16356-8711-7365-bc56-f1bd9f9a311c",
    "name": "Fox Racing",
    "slug": "fox-racing",
    "description": "Mountain bike and motocross gear from California.",
    "logo_url": "https://media.debianpos.com/brands/fox-racing.png",
    "is_featured": false,
    "is_popular": false,
    "meta": {
      "title": "Fox Racing | Bike Yard",
      "description": "Explore our Fox Racing collection at Bike Yard. Mountain bike and motocross gear from California."
    }
  },
  "products": {
    "data": [
      {
        "_id": "01a16356-8716-7254-a23b-4fa5bdd2e54c",
        "name": "Fox Speedframe helmet",
        "slug": "fox-speedframe-helmet",
        "sku": "FOX-SPF",
        "description": null,
        "short_description": null,
        "price": 24000,
        "sale_price": null,
        "discount_percentage": null,
        "cost_price": null,
        "stock_quantity": 22,
        "manage_stock": true,
        "in_stock": true,
        "is_featured": false,
        "is_new": true,
        "has_variants": false,
        "is_active": true,
        "weight": null,
        "weight_unit": null,
        "images": [],
        "primary_image": null,
        "category": {
          "_id": "01a16356-8690-7032-8482-2c7bc9a66b63",
          "id": "01a16356-8690-7032-8482-2c7bc9a66b63",
          "name": "Helmets",
          "slug": "helmets",
          "description": null,
          "image_url": null,
          "parent_id": null
        },
        "parent_category": null,
        "brand": {
          "_id": "01a16356-8711-7365-bc56-f1bd9f9a311c",
          "id": "01a16356-8711-7365-bc56-f1bd9f9a311c",
          "name": "Fox Racing",
          "slug": "fox-racing",
          "logo_url": null,
          "description": null
        },
        "attributes": [],
        "tags": [],
        "meta_title": null,
        "meta_description": null,
        "created_at": "2026-10-22T11:19:37.000000Z",
        "updated_at": "2026-10-03T06:30:00.000000Z"
      }
    ],
    "links": {
      "first": "https://app.debianpos.com/api/v1/brands/fox-racing?page=1",
      "last": "https://app.debianpos.com/api/v1/brands/fox-racing?page=1",
      "prev": null,
      "next": null
    },
    "meta": {
      "current_page": 1,
      "from": 1,
      "last_page": 1,
      "links": [
        {
          "url": "https://app.debianpos.com/api/v1/brands/fox-racing?page=1",
          "label": "1",
          "page": 1,
          "active": true
        }
      ],
      "path": "https://app.debianpos.com/api/v1/brands/fox-racing",
      "per_page": 20,
      "to": 1,
      "total": 1
    }
  }
}

Reviews

GET

/api/v1/products/{slug}/reviews

Scope
catalogue
Customer token
none

A page of a product's approved reviews, newest first. meta carries the page and, over all its approved reviews, the average rating to two decimals (null when there are none) and their count. author is the customer's name, or "Anonymous"; likes and dislikes are how many found the review helpful or not.

Parameters

slug string · path · required
The product's slug.
per_page integer · query
Reviews a page, 1 to 50; 10 by default.
page integer · query
The page, from 1.

Answers 404 with "No query results for model [App\Models\Product]." when no product the website shows has the slug. There is no links block; use meta.last_page to page. Reviews are written with POST products/{slug}/reviews.

Example answer
{
  "data": [
    {
      "id": "01a16356-9058-7387-aa7d-e566264d918c",
      "rating": 4,
      "comment": "Solid trail bike for the price. The stock tyres are a bit thin for Karura in the rains.",
      "author": "Brian Otieno",
      "date": "2026-10-22T11:19:39.000000Z",
      "likes": 0,
      "dislikes": 0
    },
    {
      "id": "01a16356-904f-714b-a78e-82bd02023010",
      "rating": 5,
      "comment": "Smooth on the Ngong Road forest trails and the gears shift cleanly. The team set it up for my height before I left.",
      "author": "Wanjiku Kamau",
      "date": "2026-10-22T11:19:39.000000Z",
      "likes": 2,
      "dislikes": 0
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 1,
    "per_page": 10,
    "total": 2,
    "average_rating": 4.5,
    "rating_count": 2
  }
}