Happy House - Ecommerce Docs
Developer ResourcesCatalog

Catalog API Reference

Complete API contracts for the Catalog module, including routes, auth, DTOs, responses, errors, examples, and integration notes.

Catalog - API Reference

Audience: Frontend engineers, mobile engineers, backend engineers, QA, and API consumers. Scope: Admin, storefront and job-facing APIs owned by the Catalog module (categories, brands, brand series, tags, jobs, search).

1. Documentation Evidence

AreaFiles InspectedWhat Was Verified
Controllersadmin/{category,brand,brand-series,job}/*.controller.ts, catalog-category-bulk.controller.ts, catalog-brand-bulk.controller.ts, catalog-brand-series-bulk.controller.ts, customer/**/*.controller.ts, catalog/{admin,customer}/tag/*.controller.tsRoutes, methods, guards, permissions, status codes, registration order
DTOsdto/*.ts under each leafRequest/query/response validation and defaults
Servicesall catalog servicesBehavior, side effects, error codes
Schemapackages/db/src/schema/catalog/*.tsConstraints, enums, indexes
Jobs/cachepackages/jobs/src/index.ts, cache-invalidation.tags.tsQueue names, payloads, domains
Error registryapps/api/src/common/types/error-codes.tsCATALOG_*, CATEGORY_*, BRAND_*, BRAND_SERIES_*, TAG_*

2. Module Summary

FieldValue
Module namecatalog
Module slugcatalog
Primary actorsguest, admin, worker
API surfacesadmin, mobile
Base route prefixes/api/admin/catalog/{categories,brands,brand-series,jobs,tags}, /api/mobile/catalog/{categories,category-tree,brands,series,search,tags}
Auth model@Public() storefront; JwtAuthGuard + RoleGuard admin
PersistencePostgreSQL, Redis (cache), BullMQ (CATALOG queue)
Runtime source of truthPostgreSQL tables
Sibling docsBackend, Features and flows

3. Concepts and Terminology

TermMeaningSource FileUsed By
publicIdExposed UUID v7 identifier; integer PK never leaves the serviceschemaAll routes
slugRoute-safe URL key; owned by per-entity slug tables, current + retired under one uniquecatalog-slug.service.tsStorefront :slug routes
canonicalSlugCurrent slug returned when a retired slug was requestedslug serviceStorefront detail
effectiveVisibleEntity is_visible AND every ancestor's, AND non-deletedcatalog-category-tree.service.tsAdmin responses
depthTree depth, root = 0, cap 6schemaCategory routes
retired slugFormer URL of an entity; never re-hands to another entityslug tablesStorefront
isStalledDerived flag: job in processing past the 15-min claim TTLcatalog-job-admin.service.tsJob detail

4. API Surface Map

4.1 Admin categories — /api/admin/catalog/categories

MethodPathPermissionRate limitControllerPurpose
GET/Categories_READADMIN_READ 30/minCatalogCategoryAdminControllerSibling list at one tree level
GET/treeCategories_READADMIN_READ 30/minsameFull non-deleted tree
GET/:publicIdCategories_READADMIN_READ 30/minsameDetail
GET/:publicId/breadcrumbsCategories_READADMIN_READ 30/minsameAncestor chain
POST/Categories_CREATEADMIN_WRITE 10/minsameCreate (201)
PATCH/:publicIdCategories_UPDATEADMIN_WRITE 10/minsameGeneral update
PATCH/:publicId/moveCategories_UPDATEADMIN_WRITE 10/minsameMove to new parent/root
PATCH/:publicId/visibilityCategories_UPDATEADMIN_WRITE 10/minsameVisibility
DELETE/:publicIdCategories_DELETEADMIN_WRITE 10/minsameSoft delete (200, message-only)
POST/:publicId/restoreCategories_RESTOREADMIN_WRITE 10/minsameRestore (200)
POST/bulk/deleteCategories_DELETEADMIN_BULK_WRITE 5/minCatalogCategoryBulkControllerBulk soft delete (200)
POST/bulk/restoreCategories_RESTOREADMIN_BULK_WRITE 5/minsameBulk restore (200)
POST/bulk/visibilityCategories_UPDATEADMIN_BULK_WRITE 5/minsameBulk visibility (200)
PATCH/reorderCategories_UPDATEADMIN_REORDER 30/minsameReorder siblings (200, message-only)

4.2 Admin brands — /api/admin/catalog/brands

MethodPathPermissionRate limitControllerPurpose
GET/Brands_READADMIN_READ 30/minCatalogBrandAdminControllerPaginated list
GET/:publicIdBrands_READADMIN_READ 30/minsameDetail
POST/Brands_CREATEADMIN_WRITE 10/minsameCreate (201)
PATCH/:publicIdBrands_UPDATEADMIN_WRITE 10/minsameUpdate
PATCH/:publicId/visibilityBrands_UPDATEADMIN_WRITE 10/minsameVisibility
DELETE/:publicIdBrands_DELETEADMIN_WRITE 10/minsameSoft delete (200, message-only)
POST/:publicId/restoreBrands_RESTOREADMIN_WRITE 10/minsameRestore (200)
POST/bulk/deleteBrands_DELETEADMIN_BULK_WRITE 5/minCatalogBrandBulkControllerBulk delete (200)
POST/bulk/restoreBrands_RESTOREADMIN_BULK_WRITE 5/minsameBulk restore (200)
POST/bulk/visibilityBrands_UPDATEADMIN_BULK_WRITE 5/minsameBulk visibility (200)
PATCH/reorderBrands_UPDATEADMIN_REORDER 30/minsameReorder all brands (200, message-only)

4.3 Admin brand series — /api/admin/catalog/brand-series

Same 11-route shape as brands with BrandSeries_* permissions; create requires an owning brand (404 BRAND_SERIES_BRAND_NOT_FOUND, 409 BRAND_SERIES_BRAND_DELETED); reorder is sibling-scoped per brand (CATALOG_REORDER_MIXED_PARENT on mixed brands).

4.4 Admin jobs — /api/admin/catalog/jobs

MethodPathPermissionRate limitPurpose
POST/importCatalog_CREATE + entity-specificADMIN_ASYNC_JOB_SUBMIT 10/hourImport job (multipart; Idempotency-Key)
POST/exportCatalog_READADMIN_ASYNC_JOB_SUBMIT 10/hourExport job (Idempotency-Key)
POST/:publicId/cancelCatalog_UPDATEADMIN_WRITE 10/minCancel queued/processing
GET/Catalog_READADMIN_READ 30/minPaginated list
GET/:publicIdCatalog_READADMIN_READ 30/minDetail incl. errors, isStalled

4.5 Storefront — /api/mobile/catalog/*

MethodPathRate limitPurpose
GET/categoriesPUBLIC_READ 60/minRoot visible categories
GET/category-treePUBLIC_HIGH_FREQUENCY 300/minVisible roots with their visible direct children, depth 1
GET/categories/:slug/breadcrumbsPUBLIC_READ 60/minAncestor chain by slug
GET/categories/:slugPUBLIC_READ 60/minDetail (current or retired slug)
GET/brandsPUBLIC_READ 60/minPaginated visible brands
GET/brands/:slug/seriesPUBLIC_READ 60/minVisible series of a brand
GET/brands/:slugPUBLIC_READ 60/minBrand detail
GET/seriesPUBLIC_READ 60/minPaginated visible series
GET/series/:slugPUBLIC_READ 60/minSeries detail
GET/searchPUBLIC_SEARCH 60/minSearch all three entities
GET/search/suggestionsPUBLIC_SUGGEST 300/minAutocomplete (≤10)
GET/tagsPUBLIC_READ 60/minVisible tags
GET/tags/:slugPUBLIC_READ 60/minTag by slug

Storefront series base is /series (not brand-series); the admin path is /brand-series.

5. Auth, Identity, and Permissions

SurfaceGuard/DecoratorIdentity ShapePermissionGuest AllowedNotes
Storefront@Public() + IpThrottlerGuardNoneN/AYesRate limits per §4.5
AdminJwtAuthGuard, RoleGuardreq.userCategories_* / Brands_* / BrandSeries_* / Catalog_* / Tags_*Nosuperadmin bypasses
Import submitroute Catalog_CREATE + service re-checkreq.userentity-specific create (Categories_CREATE etc.)No403 CATALOG_JOB_ENTITY_PERMISSION_DENIED

Idempotency: Idempotency-Key required on import/export submit (scopes catalog-job-import / catalog-job-export). Missing → 400 IDEMPOTENCY_KEY_REQUIRED; replay with different payload → 409 IDEMPOTENCY_KEY_CONFLICT; in-flight → 409 IDEMPOTENCY_REQUEST_IN_PROGRESS.

6. DTO and Model Reference

6.1 CreateCategoryDto / UpdateCategoryDto

FieldTypeRequiredDefaultValidationExampleSource
namestringYesN/A2..150, trimmed"Living Room Furniture"create-category.dto.ts
descriptionstringNoNULL"Sofas and armchairs"
parentIdUUID v7No (create)NULL@IsUUID("7")018f4e2a-…Not accepted on PATCH — use /move
slugstringNogenerated^[a-z0-9]+(-[a-z0-9]+)*$, max 160"living-room-furniture"
displayOrdernumberNo0int >= 00
isVisiblebooleanNotruetrue
imageUrl / imageAltstringNoNULLmax 2048 / 255; alt requires url
seoTitle / seoDescriptionstringNoNULLmax 160 / 320

MoveCategoryDto { parentId: UUID v7 \| null } — required but nullable (null = root). CategoryVisibilityDto { isVisible: boolean }.

6.2 CreateCatalogBrandDto / CreateCatalogBrandSeriesDto

Brand: name (2..150, required), description (max 2000), slug, logoUrl, logoAlt (requires url), seoTitle, seoDescription. Series adds required brandPublicId (UUID v7). No displayOrder in create payloads — ordering is set via reorder.

6.3 Bulk DTOs

CatalogBulkIdsDto { publicIds: string[] } — non-empty UUID v7 array; duplicates deduped before the 100-item cap (409 CATALOG_BULK_LIMIT_EXCEEDED). CatalogBulkVisibilityDto adds isVisible. CatalogReorderDto { publicIds: string[] } — index position becomes displayOrder.

6.4 Query DTOs

QueryDto base: pagination (true), page (1), size (20, max 100), sort (updatedAt), order (desc). Category adds parentId, deletedOnly; brand adds includeDeleted; series adds includeDeleted, brandPublicId; jobs add kind, entity, status. Sortable fields: name, displayOrder, createdAt, updatedAt (jobs: createdAt, updatedAt, finishedAt).

7. Enum Reference

EnumValueMeaningRuntime EffectSource
catalog_job_kindimport / exportJob kindenums.ts
catalog_job_entitycategory / brand / brand_seriesImport/export targetEntity-specific permission check
catalog_job_statusqueued / processing / completed / failed / cancelledJob lifecycleLease claim, cooperative cancel

8. Endpoint Reference

8.1 GET /api/mobile/catalog/categories/:slug

Purpose

Category detail for a storefront category page. Accepts a current or retired slug; returns canonicalSlug so the frontend can redirect.

Auth and Permissions

@Public(), PUBLIC_READ 60/min, no permission.

Response

200{ publicId, name, canonicalSlug, description, parentId, imageUrl, imageAlt, seoTitle, seoDescription }. parentId is null when the parent is soft-deleted or hidden — a deleted parent's id is never returned.

Error Cases

HTTPCodeCondition
404CATEGORY_NOT_FOUNDMissing, hidden, deleted, or under a hidden/deleted ancestor — both slug branches

8.2 POST /api/admin/catalog/categories

Purpose

Create a category (optionally under a parent). Guarded by the tree invariants: parent liveness, depth cap.

Auth and Permissions

JwtAuthGuard, RoleGuard, IpThrottlerGuard; Categories_CREATE; ADMIN_WRITE 10/min; no idempotency header (create is not idempotent-guarded).

Request

Body per §6.1.

Response

201{ publicId, name, slug, description, parentId, depth, displayOrder, isVisible, effectiveVisible, imageUrl, imageAlt, seoTitle, seoDescription, createdAt, updatedAt, deletedAt }.

Side Effects

category + category_slug rows in one transaction (advisory lock first when parented); activity record; cache invalidation.

Error Cases

HTTPCodeCondition
400VALIDATION_FAILEDimageAlt without imageUrl
404CATEGORY_PARENT_NOT_FOUNDUnknown parent
409CATEGORY_PARENT_DELETED / CATEGORY_MAX_DEPTH_EXCEEDED / CATALOG_SLUG_GENERATION_FAILEDDeleted parent / depth / slug exhaustion

8.3 PATCH /api/admin/catalog/categories/:publicId/move

The only parent-mutating route. Body { parentId: null } moves to root. 404 CATEGORY_NOT_FOUND/CATEGORY_PARENT_NOT_FOUND; 409 CATEGORY_PARENT_DELETED, CATEGORY_CIRCULAR_HIERARCHY, CATEGORY_MAX_DEPTH_EXCEEDED. Side effect: one-statement subtree rewrite.

8.4 DELETE /api/admin/catalog/:entity/:publicId (categories, brands, brand-series)

200 message-only body. 404 entity not found; 409 already deleted / has active children (category) / has active series (brand). Restore: POST /:publicId/restore, 200 with the entity payload; 409 not-deleted.

8.5 Bulk and reorder

POST /bulk/{delete,restore,visibility}200 with { succeeded: string[], failures: [{ publicId, errorCode }] }; per-item failures never roll back the batch; duplicates count once. PATCH /reorder200 message-only; the deduplicated set must exactly equal the alive sibling set (409 CATALOG_REORDER_INVALID_ITEM / CATALOG_REORDER_MIXED_PARENT).

8.6 POST /api/admin/catalog/jobs/import

multipart/form-data (file, entity). CSV/XLSX, 25 MB, 50,000 rows. Entity-specific permission re-check. Returns 200 with the queued job.

8.7 POST /api/admin/catalog/jobs/export

JSON { entity, includeDeleted?, search? }; 200 with the queued job. > 50,000 matching rows fails the job (CATALOG_EXPORT_TOO_LARGE).

8.8 POST /api/admin/catalog/jobs/:publicId/cancel

200 with the job row; 404 CATALOG_JOB_NOT_FOUND; 409 CATALOG_JOB_NOT_CANCELLABLE from a terminal state. Cooperative: a processing import aborts and rolls back.

8.9 GET /api/admin/catalog/jobs(/:publicId)

List (paginated, filters, ordered by createdAt; page cap → 400 PAGINATION_LIMIT_TOO_LARGE) and detail (adds startedAt, isStalled, retained errors).

8.10 GET /api/admin/catalog/jobs/import-template

Downloads the column template for an import file — header row only, no example row, because a downloadable import file is eventually uploaded unmodified and a data row would create junk. Zero rows is a no-op.

entity is one of category, brand, brand_series.

Permission Catalog_READ — the file carries no data, only column names. Rate limit ADMIN_READ 30/min. Returns 200 as text/csv; charset=utf-8 with Content-Disposition: attachment. An unknown entity fails DTO validation with 400.

Columns: category -> name, slug, description, parentSlug, displayOrder, isVisible, imageUrl, imageAlt, seoTitle, seoDescription; brand swaps the image pair for logoUrl, logoAlt and drops parentSlug; brand_series adds a required brandSlug.

The list is generated from the same constant the row builder's fields are typed against, so a column cannot silently diverge from what the parser reads: satisfies rejects a column that is not a row field, an Exclude assertion rejects a row field with no column, and a round-trip spec parses the emitted template back through the real builder.

Route ordering is load-bearing — this literal segment is declared above @Get(":publicId") in the controller. Declared after it, the wildcard swallows the path and the request fails as "publicId must be a UUID".

8.11 Tag endpoints

Admin /api/admin/catalog/tags (list/detail/create(201)/update/visibility/delete(200)/restore(200); Tags_*; errors TAG_NOT_FOUND, TAG_SLUG_ALREADY_EXISTS, TAG_ALREADY_DELETED, TAG_NOT_DELETED, TAG_HAS_LINKED_PRODUCTS). Storefront /api/mobile/catalog/tags (list/detail by slug; 404 TAG_NOT_FOUND).

8.12 GET /api/mobile/catalog/category-tree

Purpose

The whole storefront navigation in one request: every visible root category, each with its visible direct children.

It exists because no other customer response exposed a category's parent. GET /categories filters parentId IS NULL and its DTO carries no parent field, so a consumer could only recover the hierarchy by calling GET /categories/{slug}/breadcrumbs once per descendant — up to ~95 requests to render one menu, against a shared per-IP budget. That is what this endpoint replaces.

Depth is 1. A child carries no children of its own. A grandchild is not returned.

Source Evidence

FileWhat it establishes
catalog/customer/category-tree/catalog-category-tree-customer.controller.tsRoute, @Public(), rate limit
catalog/customer/category-tree/catalog-category-tree-customer.service.tsThe single SQL statement, the caps, the drop-on-unresolved-slug rule
catalog/customer/category-tree/dto/category-tree-node.dto.tsResponse shape
catalog/shared/catalog.constants.tsCATALOG_CUSTOMER_ROOT_CATEGORY_LIST_MAX, CATALOG_CUSTOMER_TREE_CHILDREN_PER_ROOT_MAX, CATALOG_CUSTOMER_TREE_CHILD_ROWS_MAX
mobile/mobile.module.tsRegistered as a concrete leaf in MOBILE_CHILDREN

Auth and Permissions

@Public(), IpThrottlerGuard + PUBLIC_HIGH_FREQUENCY 300/min, no permission.

The budget differs from the rest of the storefront catalog surface on purpose: this renders in the shared navigation on every page view, which is what PUBLIC_HIGH_FREQUENCY names, while /categories is an ordinary content read at PUBLIC_READ 60/min.

Request

No parameters. No query string, no body. Unknown query parameters are rejected by the global ValidationPipe's forbidNonWhitelisted.

Response

200 — an array of nodes in the standard envelope:

{
  "message": "Category tree fetched successfully",
  "data": [
    {
      "publicId": "01a0192f-31bf-71ee-8bc3-f3e0cbf7e5e3",
      "name": "Smartphones",
      "slug": "smartphones",
      "description": "Discover the latest smartphones…",
      "imageUrl": "http://localhost:5002/public/thumbnail/KAHfnL.webp",
      "imageAlt": "Latest smartphones and mobile phones",
      "displayOrder": 1,
      "children": [
        {
          "publicId": "01a01935-d765-7003-974f-ffbb32477c56",
          "name": "Android Phones",
          "slug": "android-phones",
          "description": "Explore Android smartphones…",
          "imageUrl": "https://cdn.example.com/categories/android-phones.jpg",
          "imageAlt": "Android smartphones",
          "displayOrder": 1
        }
      ]
    }
  ]
}

children is always present, [] for a childless root. A consumer may treat it as required.

Ordering is roots by displayOrder then id, and children by displayOrder then id within their own root — the same ordering GET /categories uses. The database does the ordering; a consumer that re-sorts is stating a second opinion about the same question.

Side Effects

None. One read, no writes, no cache population, no queue job. There is deliberately no server-side cache: the sole consumer caches the response for an hour on its own side.

Error Cases

HTTPCodeCondition
429PUBLIC_HIGH_FREQUENCY budget exhausted for the caller's key

There is no 404 and no 400. An empty catalogue is 200 with data: [], because "no categories" is a valid state of the store rather than a missing resource.

Edge Cases

CaseBehaviour
Root hidden or soft-deletedRoot and all its children are absent
Child hidden or soft-deletedThat child is absent; its root and siblings are unaffected
Node has no current slugDropped, and a WARN names its publicId. A row with no current slug has no reachable URL, so emitting slug: "" would list a category that links to the collection root
Category renamedThe current slug is returned; retired slugs are not
More than 200 visible rootsRoots past 200 by displayOrder are absent, and a WARN says so
A root with more than 200 visible childrenChildren past 200 within that root are absent, and a WARN names the parent
More than 4,800 child rows in totalRoots ordered last render childless, and a WARN says so. 4,800 ÷ 200 = 24 roots can each hold a full 200; the two ceilings are nested, not independent
Grandchildren existNot returned. The endpoint is depth 1

The caps exist because SlugOwnershipService.getCurrentSlugs uses an unchunked inArray, which has a hard ceiling at Postgres's 65,535 bind parameters. Truncation is deterministic — by displayOrder, never arbitrary — and never silent.

Example Requests

curl -s http://localhost:5002/api/mobile/catalog/category-tree

Consistency

One statement, one snapshot. Roots and children are read by a single UNION ALL rather than two queries, so a category demoted from root to child between two reads cannot appear twice in one response, and a promoted one cannot vanish. That race is absent rather than mitigated — which is also why there is no transaction here.

Slug resolution runs as two further batched statements (one per level, never one per node), so a request issues three statements in total. The claim is that the tree rows come from one snapshot, not that the request makes one round trip.

8.13 GET /api/mobile/catalog/categories

Purpose

Visible root categories, flat. parentId IS NULL — it does not describe the hierarchy; use 8.12 for that.

Auth and Permissions

@Public(), PUBLIC_READ 60/min, no permission.

Response

200[{ publicId, name, slug, description, imageUrl, imageAlt, displayOrder }], ordered by displayOrder then id, capped at CATALOG_CUSTOMER_ROOT_CATEGORY_LIST_MAX (200).

Edge Cases

CaseBehaviour
Root has no current slugDropped, with a WARN naming its publicId

That last row is a behaviour change. This endpoint previously emitted slug: "" for such a row, which put a category in the list whose link resolved to the collection root and whose own detail route 404'd. It now matches 8.12, because two public endpoints disagreeing about which categories exist is worse than either answer: the home page would show a category the navigation does not.

9. Flow Diagrams

9.1 Route Ownership

9.2 Request Sequence (move)

9.3 Error Branch (delete category)

EndpointPagination TypeDefault SizeMax SizeSort FieldsFiltersResult Cap
Admin lists (category/brand/series/jobs)offset page/size20100name/displayOrder/createdAt/updatedAt (jobs: createdAt/updatedAt/finishedAt)per §6.4offset depth 10,000 (400 PAGINATION_LIMIT_TOO_LARGE)
GET /mobile/catalog/categoriesnone (bounded)displayOrderroot+visible200
GET /mobile/catalog/brands / seriesoffset page/size20100displayOrdervisible200 (series per brand)
GET /mobile/catalog/searchoffset page/size20100similarity rank, displayOrder, identity, term
Suggestionsnonerankentity, term10

Search ranking is fixed: similarity DESC, display_order ASC, id ASC; term < 2 chars = no search; terms > 64 chars skip the Redis cache.

11. Caching, Jobs, and External Integrations

IntegrationUsed?DetailsSource
Redis cacheYesThree catalog domains (category/brand/series) + search prefix; CACHE_TTL.STANDARD 300s; cross-entity patternscache-invalidation.tags.ts
BullMQYesCATALOG queue: catalog.import_entities, catalog.export_entities; lease claim; registered by its own module (not REGISTERED_QUEUES); sweep on MAINTENANCEpackages/jobs/src/index.ts
External APINo

13. Mandatory Deep API Documentation Pack

13.1 Route-by-Route Completeness Matrix

RouteController MethodDTOsService MethodGuardsPermissionsCacheJobsDB TouchesErrorsDocumented?
GET /api/admin/catalog/categoriesfindAllFetchCategoryDtoCatalogCategoryAdminService.findAllJWT+Role+IpThrottleCategories_READcategory, slugs400/404/422→400Yes
GET /api/admin/catalog/categories/treefindTree…findTreesameCategories_READcategory400 CATALOG_TREE_TOO_LARGEYes
GET /api/admin/catalog/categories/:publicIdfindByIdCategoryParamsDto…findByIdsameCategories_READcategory404Yes
GET /api/admin/catalog/categories/:publicId/breadcrumbsfindBreadcrumbsCategoryParamsDto…findBreadcrumbssameCategories_READcategory404Yes
POST /api/admin/catalog/categoriescreateCreateCategoryDtoCatalogCategoryWriteService.createsameCategories_CREATEinvalidatecategory, slug400/404/409Yes
PATCH /api/admin/catalog/categories/:publicIdupdateUpdateCategoryDto…updatesameCategories_UPDATEinvalidatecategory, slug400/404/409Yes
PATCH /:publicId/movemoveMoveCategoryDto…movesameCategories_UPDATEinvalidatecategory subtree404/409Yes
PATCH /:publicId/visibilitysetVisibilityCategoryVisibilityDto…setVisibilitysameCategories_UPDATEinvalidatecategory404/409Yes
DELETE /:publicIddeleteCategoryParamsDto…deletesameCategories_DELETEinvalidatecategory404/409Yes
POST /:publicId/restorerestoreCategoryParamsDto…restoresameCategories_RESTOREinvalidatecategory404/409Yes
POST /bulk/* + PATCH /reorder (category)4 methodsBulk DTOsCatalogCategoryBulkService.*sameper routeinvalidatecategory409Yes
Brand routes (11)11 methodsBrand DTOsCatalogBrandAdminService.* + bulksameBrands_*invalidate (admin list cached)brand, slugs, series404/409Yes
Series routes (11)11 methodsSeries DTOsCatalogBrandSeriesAdminService.* + bulksameBrandSeries_*invalidateseries, brand, slugs404/409Yes
Job routes (5)5 methodsJob DTOsCatalogJobAdminService.*sameCatalog_*outbox/queuecatalog_job, outbox_events400/403/404/409Yes
Storefront category (3)3 methodsslug paramsCatalogCategoryCustomerService.*Public+IpThrottledomaincategory, slugs404Yes
Storefront brand (3)3 methodsQueryDtoCatalogBrandCustomerService.*Public+IpThrottledomainbrand, series, slugs404Yes
Storefront series (2)2 methodsQueryDtoCatalogBrandSeriesCustomerService.*Public+IpThrottledomainseries, brand, slugs404Yes
Storefront search (2)2 methodsSearch DTOsCatalogSearchCustomerService.*Public+IpThrottlesearch3 tablesYes
Tag routes (9)9 methodsTag DTOsCatalogTagAdminService.* + customerJWT+Role / PublicTags_*invalidatetag, links404/409Yes

13.2 Request/Response Exhaustiveness

Covered in §8: minimal create payloads (§6.1), success responses (§8.1, §8.2), empty-list behavior (admin lists return data: [] + pagination metadata; storefront bounded lists return []), validation error (400 VALIDATION_FAILED representative), domain errors per endpoint (§8 error tables), rate-limit behavior (throttler 429), admin permission errors (403).

13.3 API Diagram Pack

Route ownership (§9.1), sequence per endpoint family (§9.2, backend §7), activity diagrams per mutation family (§9.3, feature §6), error decision trees (§9.3), async/job flow (backend §7.2), cache flow (backend §8).

13.4 Consumer Integration Notes

ConsumerRequired KnowledgeFailure HandlingContract Stability
Web frontendStorefront slugs, canonicalSlug redirect pattern, effectiveVisible404 on hidden/deleted → remove from UI; redirect on canonicalSlugStable
Mobile appPUBLIC_SEARCH/PUBLIC_SUGGEST rate limits (ip+device)429 → back offStable
Admin panelTree invariants, bulk per-item failures, reorder exact-set rule409 codes map to specific admin UI messagesStable
QARetired slug behavior, depth cap, delete-blocked casesReproduce via exact error codesStable
Internal servicesImport entity permission, idempotency keys on jobs403 CATALOG_JOB_ENTITY_PERMISSION_DENIED → token fixStable

13.5 API Tradeoffs and Rationale

DecisionChosen BehaviorAlternatives ConsideredWhy This TradeoffRiskMitigation
Slug ownership tablesCurrent + retired under one uniqueSlug column + separate historyRe-pointing is unrepresentableMore tablesSchema probes
Bulk per-item resultssucceeded/failures in dataSingle errorCodeEnvelope carries one errorCodePartial failures look like successDocumented
Reorder exact-setStrict equality with siblingsPartial reorderNo silently-stale items409 churnExact error codes
Retired slugs resolve200 + canonicalSlug3xx redirectFrontend owns routingDuplicate redirect logicDocumented
One error code per statusPAGINATION_LIMIT_TOO_LARGE = 400 everywhereMixed 400/422Clients branch on statusReview-enforced

13.6 API Change Impact

ChangeAffected ConsumersBackend ImpactData ImpactMigration Needed?Compatibility Plan
Reorder rate limit → ADMIN_REORDERAdmin panelConstant swapNoneNoHigher budget (30/min)
Import entity permissionAdmin panelService re-checkNoneNo403 with exact code

14. Zero-Omission API Checklist

  • Every controller route is documented (§4, §8, §13.1).
  • Every parent route prefix and runtime URL is documented (§2, §4).
  • Every DTO field, enum, default, transform and validator is documented (§6, §7).
  • Every response field, nullable field, generated field and omitted raw entity field is documented (§8).
  • Every auth, guard, permission, public decorator and guest identity branch is documented (§5).
  • Every success, validation, auth, permission, not-found, conflict, rate-limit and server-error branch is documented (§8).
  • Every DB read/write, cache hit/miss/invalidation, queue job, audit log and external call is documented (§11, backend §8/§9).
  • Every route has examples for minimal request, success response and representative failures (§8).
  • Every endpoint family has route, sequence, activity and error diagrams (§9, backend §7).
  • Every tradeoff and compatibility risk is documented (§13.5, §13.6).
  • The API doc links to backend and features/flows (§1, See Also).

15. Integration Checklist

  • Every route from controllers is documented.
  • Every DTO field is documented.
  • Every enum value is documented.
  • Every response envelope is documented.
  • Every error code is documented.
  • Every auth guard and permission is documented.
  • Every cache key, queue job and external call is documented.
  • Every diagram matches the current code.
  • The API doc links to backend and features/flows.

See Also

On this page

Catalog - API Reference1. Documentation Evidence2. Module Summary3. Concepts and Terminology4. API Surface Map4.1 Admin categories — /api/admin/catalog/categories4.2 Admin brands — /api/admin/catalog/brands4.3 Admin brand series — /api/admin/catalog/brand-series4.4 Admin jobs — /api/admin/catalog/jobs4.5 Storefront — /api/mobile/catalog/*5. Auth, Identity, and Permissions6. DTO and Model Reference6.1 CreateCategoryDto / UpdateCategoryDto6.2 CreateCatalogBrandDto / CreateCatalogBrandSeriesDto6.3 Bulk DTOs6.4 Query DTOs7. Enum Reference8. Endpoint Reference8.1 GET /api/mobile/catalog/categories/:slugPurposeAuth and PermissionsResponseError Cases8.2 POST /api/admin/catalog/categoriesPurposeAuth and PermissionsRequestResponseSide EffectsError Cases8.3 PATCH /api/admin/catalog/categories/:publicId/move8.4 DELETE /api/admin/catalog/:entity/:publicId (categories, brands, brand-series)8.5 Bulk and reorder8.6 POST /api/admin/catalog/jobs/import8.7 POST /api/admin/catalog/jobs/export8.8 POST /api/admin/catalog/jobs/:publicId/cancel8.9 GET /api/admin/catalog/jobs(/:publicId)8.10 GET /api/admin/catalog/jobs/import-template8.11 Tag endpoints8.12 GET /api/mobile/catalog/category-treePurposeSource EvidenceAuth and PermissionsRequestResponseSide EffectsError CasesEdge CasesExample RequestsConsistency8.13 GET /api/mobile/catalog/categoriesPurposeAuth and PermissionsResponseEdge Cases9. Flow Diagrams9.1 Route Ownership9.2 Request Sequence (move)9.3 Error Branch (delete category)10. Pagination, Sorting, Filtering, and Search11. Caching, Jobs, and External Integrations13. Mandatory Deep API Documentation Pack13.1 Route-by-Route Completeness Matrix13.2 Request/Response Exhaustiveness13.3 API Diagram Pack13.4 Consumer Integration Notes13.5 API Tradeoffs and Rationale13.6 API Change Impact14. Zero-Omission API Checklist15. Integration ChecklistSee Also