Supercommerce API Docs
Store API

Catalog Module — Storefront

HTTP surface for unauthenticated catalog reads. The storefront uses these endpoints to render the navigation tree, brand / tag / ingredient pages, the product detail page (PDP),…

HTTP surface for unauthenticated catalog reads. The storefront uses these endpoints to render the navigation tree, brand / tag / ingredient pages, the product detail page (PDP), and to resolve slugs to ids for deep-linking. Only active, non-deleted rows are visible — admin and vendor product/variant management lives in sibling docs.

Source: api-modules/catalog/src/controllers/public-catalog.controller.ts (taxonomy reads), api-modules/catalog/src/controllers/store-product.controller.ts (product detail).

The four taxonomies (categories, brands, tags, ingredients) share a single response shape (CatalogItemResponse). Cart, order, and inventory consume product+variant data via this module's services; HSN classification lives on product_variant, not the product, because variants in the same family can carry different HSN codes.


Conventions

Authentication

Endpoint groupAuth
GET /store/catalog/**none (public)
GET /store/products/:slugnone (public)

No session is required. Soft-deleted (deletedAt IS NOT NULL) and inactive (isActive = false) rows are filtered server-side; the storefront never sees them.

Response envelope

Successful responses are wrapped by ResponseInterceptor:

{
  "data": <payload>,
  "message": "Success",
  "statusCode": 200,
  "metadata": { /* pagination on list endpoints */ }
}

Paginated lists use the standard metadata: { total, limit, offset, hasMore } shape.

Error envelope

statusCodeerrorCode examples
400BAD_REQUEST, VALIDATION_ERROR (bad query coercion)
404NOT_FOUND
500INTERNAL_SERVER_ERROR, DATABASE_ERROR

QueryDto

List endpoints accept the platform's standard QueryDto:

NameTypeDefaultNotes
searchValuestring?Combined with searchField + searchOperator (contains / starts_with / ends_with)
searchFieldstring?Allowed fields are per-service (typically title, slug)
limitint1001..500
offsetint0≥ 0
sortBy, sortDirectionstring?, asc/desc, descAllowed sort fields are per-service
filters[]JSON?[{ field, value, operator }]; operator is one of eq, ne, lt, lte, gt, gte, in, not_in, contains, starts_with, ends_with

Domain types

CatalogItemResponse

Shared by brand / category / tag / ingredient.

type CatalogItemResponse = {
  id: string;
  title: string;
  description: string | null;
  slug: string;                            // lowercase alnum + hyphens
  image: string | null;
  metadata: Record<string, unknown> | null;
  isActive: boolean;                       // always true for storefront reads
  createdAt: string;                       // ISO
  updatedAt: string;
  deletedAt: string | null;                // always null for storefront reads
};

Categories

Base path: /store/catalog/categories.

GET /store/catalog/categories — List active categories

Paginated. Accepts the standard QueryDto.

Response 200 — paginated CatalogItemResponse[].


GET /store/catalog/categories/tree — Active category tree

Returns the full parent/child hierarchy in a single call, materialized server-side. Inactive and soft-deleted categories (and their subtrees) are pruned. Not paginated.

Response 200 — array of category nodes with nested children: CategoryNode[] per the category service's tree shape.


GET /store/catalog/categories/slug/:slug — Get a category by slug

Path params

NameNotes
slugLowercase alnum + hyphens. The service filters to isActive=true, deletedAt IS NULL

Response 200CatalogItemResponse.

Errors

StatusCodeWhen
404NOT_FOUNDNo active category with that slug

GET /store/catalog/categories/:id — Get a category by id

Response 200CatalogItemResponse.

Errors

StatusCodeWhen
404NOT_FOUNDId does not exist, or row is inactive / soft-deleted

Brands

Base path: /store/catalog/brands. Same shape as categories — minus the tree endpoint.

GET /store/catalog/brands — List active brands

Paginated QueryDto. Response 200 — paginated CatalogItemResponse[].

GET /store/catalog/brands/slug/:slug

Response 200CatalogItemResponse. 404 if the brand is missing / inactive / deleted.

GET /store/catalog/brands/:id

Response 200CatalogItemResponse. 404 if the brand is missing / inactive / deleted.


Tags

Base path: /store/catalog/tags. Same shape as brands.

GET /store/catalog/tags — List active tags

Paginated QueryDto. Response 200 — paginated CatalogItemResponse[].

GET /store/catalog/tags/slug/:slug

Response 200CatalogItemResponse. 404 if the tag is missing / inactive / deleted.

GET /store/catalog/tags/:id

Response 200CatalogItemResponse. 404 if the tag is missing / inactive / deleted.


Ingredients

Base path: /store/catalog/ingredients. Same shape as brands.

GET /store/catalog/ingredients — List active ingredients

Paginated QueryDto. Response 200 — paginated CatalogItemResponse[].

GET /store/catalog/ingredients/slug/:slug

Response 200CatalogItemResponse. 404 if the ingredient is missing / inactive / deleted.

GET /store/catalog/ingredients/:id

Response 200CatalogItemResponse. 404 if the ingredient is missing / inactive / deleted.


Products (PDP)

Base path: /store/products. The public product-detail surface that backs the storefront PDP. Listing and faceted discovery live in search.md; this endpoint is the single-product hydrate once a slug is known.

GET /store/products/:slug — Product detail by slug

Returns a store-visible product with its options, variants, content tabs, taxonomy id arrays, and a slim vendor summary. Visibility gating — only a product that is status=active, visibility=public, non-deleted, and published (publishedAt IS NOT NULL AND publishedAt <= now()) is returned; anything else is a 404 (the same neutral 404 as an unknown slug, so unpublished/hidden products can't be probed).

Path params

NameNotes
slugProduct slug. The public PDP URL is /product/{slug} (singular) on the storefront; this API path is store/products/:slug.

Response 200StoreProductDetail:

type StoreVendorSummary = {
  id: string;
  name: string;
  slug: string;
  logo: string | null;
};

type StoreProductDetail = {
  product: ProductRecord;            // core product columns (title, subtitle,
                                     // description, brandId, thumbnail, images,
                                     // SEO meta, hsCode, etc.)
  options: ProductOptionDetail[];    // option groups + values (e.g. Size, Color)
  variants: ProductVariantDetail[];  // purchasable variants: price/specialPrice
                                     // (subunits), sku/ean/upc/barcode, hsnCode,
                                     // min/max per cart, option-value refs
  tabs: TabRecord[];                 // rich-content tabs (description, how-to, etc.)
  categoryIds: string[];
  tagIds: string[];
  ingredientIds: string[];
  vendor: StoreVendorSummary;
};

The product / options / variants / tabs sub-shapes are identical to the ProductDetail base returned by the admin/vendor product-detail endpoints — see ../admin/catalog.md for the full field-level breakdown. The storefront variant differs only in the gating above and the slim StoreVendorSummary (no admin-only vendor fields). Prices on variants are integer subunits.

Errors

StatusCodeWhen
404NOT_FOUNDNo product with that slug, or it is inactive / non-public / unpublished / soft-deleted, or its vendor cannot be resolved

  • search — actual product listing and faceting for the storefront. This module only exposes taxonomy reads + single-product detail; product search/filter lives in search.md.
  • bannerGET /store/banners/:entityType/slug/:slug accepts the same slug values exposed by this surface. See banner.md.
  • cart — variants added to the cart come from this module's product+variant tables.

On this page