Category Service
Overview
Categories are used to organize products into logical hierarchies. They support parent-child relationships and are used for navigation, filtering, reporting and merchandising.
Categories can optionally include their associated products when retrieved, filtered by entity access permissions.
All category operations are tenant-aware and scoped to the current tenant.
Endpoints
GET
/api/v1/Categories/{id}
Returns a single category by its numeric identifier.
Query parameters:
showProducts(bool, optional, default: false)- When
true, includes all active products in this category
- When
entityId(long, required if showProducts=true)- Filters products by entity access permissions
- Products are filtered via
EntityProductAccesstable
Behavior:
- Looks up category by ID
- Returns
404if the category does not exist - If
showProducts=trueandentityIdis not provided, returns500with error message: "EntityID is required if showing Products"
Response:
- Category with basic fields (Id, Code, Name, Slug, ParentId, IsActive)
- If
showProducts=true: includesProductsarray with full product details including product details (SKU, Title, Description, Summary), brand name, tax code, unit price, stock information (per warehouse), and dynamic properties
Authorization:
- Requires Bearer Token
GET
/api/v1/Categories/code/{code}
Returns a single category by its unique code.
Query parameters:
showProducts(bool, optional, default: false)- When
true, includes all active products in this category
- When
entityId(long, required if showProducts=true)- Filters products by entity access permissions
Behavior:
- Looks up category by
Code - Category codes are unique per tenant
- Returns
404if no category matches the provided code - If
showProducts=trueandentityIdis not provided, returns500with error message: "EntityID is required if showing Products"
Response:
- Same structure as
GetCategoryById
Authorization:
- Requires Bearer Token
GET
/api/v1/Categories/slug/{slug}
Returns a single category by its SEO-friendly slug.
Query parameters:
showProducts(bool, optional, default: false)- When
true, includes all active products in this category
- When
entityId(long, required if showProducts=true)- Filters products by entity access permissions
Behavior:
- Looks up category by
Slug - Slugs are URL-friendly identifiers (e.g., "home-living", "fashion-apparel")
- Returns
404if no category matches the provided slug - Returns
404if slug is empty or whitespace - If
showProducts=trueandentityIdis not provided, returns500with error message: "EntityID is required if showing Products"
Response:
- Same structure as
GetCategoryById
Authorization:
- Requires Bearer Token
GET
/api/v1/Categories
Returns all categories filtered by parent and active status.
Query parameters:
parentId(int)0→ root categories (no parent)> 0→ child categories of the given parent
isActive(bool) - filter by active flag
Behavior:
- Returns categories matching
isActive - Filters by parent category using
parentId - Returns
404if no categories match the filter - Does not include products (use individual category endpoints for that)
Authorization:
- Requires Bearer Token
POST
/api/v1/Categories
Creates a new category.
Request body:
Name(string, required) - category display nameCode(string, required) - unique category codeSlug(string, optional) - SEO-friendly URL slugParentId(long, optional)0→ root category
IsActive(bool, optional)
Behavior:
- Validation is handled in the service layer
- Category
Codemust be unique per tenant - Category
Slugmust be unique per tenant (if provided) - If
ParentIdis0, the category is treated as a root category - If
IsActiveis not provided, it is stored asfalse - Successful creation writes an audit log entry
Errors:
- Returns
400if validation fails (missing Name or Code) - Returns
400if category code already exists - Returns
400if slug already exists (when provided)
Authorization:
- Requires Bearer Token
PUT
/api/v1/Categories/{id}
Updates an existing category.
Request:
- Category ID is taken from the route
- Request body must include:
Name(string, required)Code(string, required)
- Optional fields:
Slug(string)ParentId(long)IsActive(bool)
Behavior:
- Only provided fields are updated
- Empty or whitespace values are ignored (existing values retained)
- If
ParentIdis0, the category becomes a root category - If
IsActiveis provided, it overwrites the existing value - If
Slugis provided and not empty, it updates the slug - Successful update writes an audit log entry
Errors:
- Returns
400if validation fails (missing Id, Name, or Code) - Returns
400if category does not exist
Authorization:
- Requires Bearer Token
DELETE
/api/v1/Categories/{id}
Deletes a category by its identifier.
Behavior:
- Performs a hard delete
- Record is permanently removed from the database
- Products associated with this category are not deleted
- Relationships in
ProductCategorytable are handled by cascade rules - Successful deletion writes an audit log entry
Errors:
- Returns
400if category does not exist or deletion fails
Authorization:
- Requires Bearer Token
Product Filtering
When showProducts=true is specified on GET endpoints, the following applies:
Access Control
- Only products accessible by the specified
entityIdare returned - Access is determined by the
EntityProductAccesstable - Products without entity access are excluded
Filtering Rules
- Only products where
IsActive=trueare included - Products must belong to the category (via
ProductCategoryjunction table)
Performance Optimization
Product data is loaded efficiently to prevent N+1 query problems using 4 optimized queries:
- Find product IDs (category + entity access filter)
- Load product basic data with projection
- Bulk load all stock items for found products
- Bulk load all properties for found products
- In-memory grouping and assignment
Response Structure
Products include complete details: SKU, Title, Description, Summary, Active status, Brand name, Tax Code, Unit Price, Stock Items (per warehouse with QuantityOnHand and QuantityAllocated), and Properties (Name/Value pairs).
Notes
- Validation is enforced in the service layer, not via model attributes
- Category hierarchy is managed using
ParentId ParentId = 0always represents a root categorySlugfield is optional and can be NULL in the database- All state-changing operations are audited
- Internal errors are logged but not exposed to clients
- Product filtering respects entity-level access control
- Products are loaded with optimized queries (4 queries total vs N+1 queries)