# Spec Delta ## MODIFIED Requirements ### Requirement: Public product browse Public `GET /api/products` SHALL return only `published` products whose shop is active, and SHALL remain readable without authentication. When `category_id` is supplied, the filter SHALL match that category **and every category beneath it**, so requesting a parent category returns products assigned to its child and grandchild categories. When `brand_id` is supplied the filter SHALL match that brand and compose with the other filters. The listing SHALL accept an optional `sort` of `price` or `sales`: `price` orders by each product's lowest active SKU price, and `sales` orders by units sold across orders that reached payment, which SHALL also be reported per product as `sold_count`. Any other `sort` value SHALL be rejected with a 400 `ApiError` rather than silently ignored. An unsorted listing SHALL order newest first. Paging SHALL keep returning `page` and `per_page` alongside the filtered `total`. #### Scenario: parent category includes descendant products - **WHEN** a shopper requests products for a category that has child categories holding published products - **THEN** the response contains the products assigned to those descendant categories, not only those assigned directly to the requested category #### Scenario: sort by lowest active SKU price - **WHEN** a shopper requests the product list with `sort=price` and `order=asc` - **THEN** products come back ordered by their lowest active SKU price ascending #### Scenario: sort by units sold - **WHEN** a shopper requests the product list with `sort=sales` and `order=desc` - **THEN** products come back ordered by their `sold_count` descending, and a product with no paid orders reports zero rather than being omitted #### Scenario: filter by brand - **WHEN** a shopper requests products with a `brand_id` alongside a `category_id` - **THEN** only products matching both filters are returned, and `total` reflects the combined filter #### Scenario: unsupported sort is rejected - **WHEN** a client requests a `sort` value that is neither `price` nor `sales` - **THEN** the API responds 400 with an `ApiError` body instead of ignoring the parameter #### Scenario: unpublished products never appear - **WHEN** any public listing or filter is applied - **THEN** products that are not `published`, or whose shop is not active, are absent from both `items` and `total`