Promotion Module API & Integration Guide
API contracts for promotion admin and customer/mobile coupon workflows.
Audience: Mobile/web frontend developers Scope: Coupon apply, validation endpoints
Promotion Module - API & Integration Guide
1. How to Read / Quick Metadata
- Module:
Promotion - Primary base URLs:
- Admin:
/api/admin/promotions - Customer:
/api/promotions - Mobile-composed customer:
/api/mobile/promotions
- Admin:
- Auth model:
- Admin routes:
JwtAuthGuard + RoleGuard + @Permissions(...) - Customer routes:
JwtAuthGuard
- Admin routes:
- Response envelope:
ResponseDto<T>with paginated responses for list endpoints
2. High-Level Overview
Promotion API covers:
- admin coupon lifecycle management (CRUD + disable)
- customer coupon operations (apply/remove)
- customer read paths (active promotions and personal usage history)
POST /promotions/apply supports two behaviors:
- cart mutation mode when
productIdis omitted - product-only validation mode when
productIdis provided
3. Core Concepts and Terminology
- promotion: coupon contract (
promotiontable) - promotion scope: target filter rows (
promotion_scope) tied to product/category/tag - promotion usage: redemption history (
promotion_usage) linked to customer + cart/order - eligibility model: digital-cart + online-payment only
- code normalization:
trim + uppercasebefore evaluation
Schema integrity contracts behind API behavior:
promotion_usage_target_check: at least one ofcart_id/order_idpromotion_scope_target_check: exactly one ofproduct_id/category_id/tag_id- non-negative checks for discount/cart-value fields
4. Route Summary
4.1 Admin
| Method | Path | Permission |
|---|---|---|
GET | /api/admin/promotions | Promotions_READ |
GET | /api/admin/promotions/:id | Promotions_READ |
POST | /api/admin/promotions | Promotions_CREATE |
PATCH | /api/admin/promotions/:id | Promotions_UPDATE |
DELETE | /api/admin/promotions/:id | Promotions_DELETE |
POST | /api/admin/promotions/:id/disable | Promotions_UPDATE |
4.2 Customer / Mobile
| Method | Path | Behavior |
|---|---|---|
POST | /api/promotions/apply | apply to cart when productId missing; validate-only when productId present |
POST | /api/promotions/remove | remove coupon from active cart |
GET | /api/promotions | list active promotions |
GET | /api/promotions/my-usage | list current user's promotion usage |
Mobile-composed routes are available under /api/mobile/promotions/....
Route Details
Apply Coupon
| Aspect | Details |
|---|---|
| Endpoint | POST /api/promotions/apply or /api/mobile/promotions/apply |
| Auth | JwtAuthGuard |
| Request | "" code: string, productId?: number "" |
| Response | ResponseDto<AppliedPromotionDto> |
| Errors | 400 not applicable, 404 not found |
Remove Coupon
| Aspect | Details |
|---|---|
| Endpoint | POST /api/promotions/remove or /api/mobile/promotions/remove |
| Auth | JwtAuthGuard |
| Request | empty |
| Response | ResponseDto<void> |
| Errors | - |
5. Query Parameters
Common list fields from QueryDto:
pagination(defaulttrue)page(default1)size(default20)search(optional)order(asc/desc, defaultdescunless endpoint override)
Endpoint-specific parameters:
- admin list (
GET /api/admin/promotions):status,sort - customer active list (
GET /api/promotions):discountType,sort,order(defaultasc) - customer usage list (
GET /api/promotions/my-usage):sort(discountApplied|createdAt)
6. Response Shape Examples
Active promotion list item example:
"
"id": 12,
"code": "SUMMER2026",
"description": "Summer sale discount",
"discountType": "percentage",
"discountValue": 10,
"maxDiscountCap": 5000,
"minCartValue": 1000,
"cartTypeEligibility": "digital",
"startDate": "2026-04-01T00:00:00.000Z",
"endDate": "2026-04-30T23:59:59.000Z"
"Product validation mode response (POST /api/promotions/apply with productId):
"
"message": "Coupon validated for product",
"data": "
"valid": true,
"discount": 1000,
"promotionId": 42,
"promotionCode": "SUMMER2026"
"",
"errorCode": null
"7. Enums
Promotion enums in active contracts:
promotion_status:active,disabled,expireddiscount_type:flat,percentagecart_type_eligibility:digitalpayment_method_eligibility:onlinescope_type:product,category,tag
8. Integration Diagram
9. Caching
| Key prefix | Purpose |
|---|---|
promotions:admin:list: | admin listing |
promotions:admin:detail: | admin detail |
promotions:customer:list: | customer active listing |
promotions:code: | normalized code lookup |
TTL envs:
PROMOTION_ADMIN_CACHE_TTL_SECONDSPROMOTION_CUSTOMER_CACHE_TTL_SECONDS
10. Error Handling
All promotion API errors return the standard error envelope with errorCode.
"
"statusCode": 400,
"errorCode": "PROMOTION_NOT_APPLICABLE",
"message": "Coupon is no longer active.",
"timestamp": "2026-03-20T10:00:00.000Z",
"path": "/api/promotions/apply"
"| HTTP | errorCode | Condition |
|---|---|---|
| 400 | PROMOTION_USAGE_CONFLICT | promotion has usage history and cannot be modified/deleted |
| 400 | PROMOTION_INVALID_DATE_RANGE | date validation failed |
| 400 | PROMOTION_DISCOUNT_INVALID | discount validation failed |
| 400 | PROMOTION_SCOPE_INVALID | one or more scope IDs are invalid |
| 400 | PROMOTION_NOT_APPLICABLE | coupon evaluation failed |
| 404 | PROMOTION_NOT_FOUND | promotion does not exist |
| 409 | PROMOTION_CODE_EXISTS | duplicate promotion code |
| 429 | RATE_LIMIT_EXCEEDED | admin or customer rate limit exceeded |
Time fields in this module are stored as timezone-aware values and should be handled as ISO-8601 instants by API consumers.
11. Endpoint Reference + Payload Cheatsheet
11.1 Payload Cheatsheet Table (Every Endpoint)
| Method | Path | Auth / Permission | Request DTO / Params | Success DTO | Notes |
|---|---|---|---|---|---|
| GET | /api/admin/promotions | Admin + Promotions_READ | QueryPromotionsAdminDto "" page?, size?, pagination?, search?, status?, sort?, order? "" | PromotionDto[] paginated | Admin lists all promotions with filters |
| GET | /api/admin/promotions/:id | Admin + Promotions_READ | path: id (int) | PromotionDto | Admin fetches single promotion detail with scopes |
| POST | /api/admin/promotions | Admin + Promotions_CREATE | CreatePromotionDto "" code: string, description?: string, discountType: "flat" | "percentage", discountValue: number, maxDiscountCap?: number, minCartValue?: number, cartTypeEligibility?: string, paymentMethodEligibility?: string, startDate: ISO8601, endDate: ISO8601, scopes?: ScopeDto[] "" | PromotionDto |
| PATCH | /api/admin/promotions/:id | Admin + Promotions_UPDATE | path: id (int), body: UpdatePromotionDto "" description?, discountType?, discountValue?, maxDiscountCap?, minCartValue?, startDate?, endDate?, isActive?, scopes? "" | PromotionDto | Admin updates promotion; fails if has usage |
| DELETE | /api/admin/promotions/:id | Admin + Promotions_DELETE | path: id (int) | "" deleted: boolean "" | Admin deletes promotion; fails if has usage history |
| POST | /api/admin/promotions/:id/disable | Admin + Promotions_UPDATE | path: id (int) | PromotionDto | Admin disables active promotion |
| POST | /api/promotions/apply | User JWT | ApplyPromotionDto "" code: string, productId?: number "" | AppliedPromotionDto or CouponValidationDto | Applies coupon to cart; if productId provided, validates only |
| POST | /api/mobile/promotions/apply | User JWT | ApplyPromotionDto "" code: string, productId?: number "" | AppliedPromotionDto or CouponValidationDto | Mobile-composed equivalent of apply |
| POST | /api/promotions/remove | User JWT | none | ResponseDto<void> | Removes applied coupon from active cart |
| POST | /api/mobile/promotions/remove | User JWT | none | ResponseDto<void> | Mobile-composed equivalent of remove |
| GET | /api/promotions | User JWT | QueryActivePromotionsDto "" page?, size?, pagination?, discountType?, sort?, order? "" | ActivePromotionDto[] paginated | Lists all currently active promotions |
| GET | /api/mobile/promotions | User JWT | QueryActivePromotionsDto "" page?, size?, pagination?, discountType?, sort?, order? "" | ActivePromotionDto[] paginated | Mobile-composed equivalent of active list |
| GET | /api/promotions/my-usage | User JWT | QueryMyUsageDto "" page?, size?, pagination?, sort? "" | PromotionUsageDto[] paginated | Lists current user's redemption history |
| GET | /api/mobile/promotions/my-usage | User JWT | QueryMyUsageDto "" page?, size?, pagination?, sort? "" | PromotionUsageDto[] paginated | Mobile-composed equivalent of usage list |
11.1.1 Admin Promotions Endpoint Query Variations
| Method | Path | Query Parameters | Notes |
|---|---|---|---|
| GET | /api/admin/promotions | (no params) | Default: page=1, size=20, sort=createdAt, order=desc |
| GET | /api/admin/promotions | ?page=2&size=50 | Custom pagination |
| GET | /api/admin/promotions | ?status=active | Filter by active status |
| GET | /api/admin/promotions | ?status=disabled | Filter by disabled status |
| GET | /api/admin/promotions | ?status=expired | Filter by expired status |
| GET | /api/admin/promotions | ?search=SUMMER | Search by code/description |
| GET | /api/admin/promotions | ?sort=startDate&order=asc | Sort by start date ascending |
11.1.2 Customer Active Promotions Query Variations
| Method | Path | Query Parameters | Notes |
|---|---|---|---|
| GET | /api/promotions | (no params) | Default: page=1, size=20, discountType optional, sort=startDate, order=asc |
| GET | /api/promotions | ?discountType=percentage | Filter percentage discounts only |
| GET | /api/promotions | ?discountType=flat | Filter flat discounts only |
| GET | /api/promotions | ?sort=endDate&order=asc | Sort by expiration date |
| GET | /api/promotions | ?pagination=false | Returns all active records |
11.1.3 Customer Usage History Query Variations
| Method | Path | Query Parameters | Notes |
|---|---|---|---|
| GET | /api/promotions/my-usage | (no params) | Default: page=1, size=20, sort=createdAt, order=desc |
| GET | /api/promotions/my-usage | ?sort=discountApplied | Sort by discount amount |
| GET | /api/promotions/my-usage | ?page=1&size=50 | Custom pagination |
11.1.4 Apply Coupon Request Variations
| Method | Path | Request Body | Notes |
|---|---|---|---|
| POST | /api/promotions/apply | "" "code": "SUMMER2026" "" | Apply to cart (productId omitted) |
| POST | /api/promotions/apply | "" "code": "SUMMER2026", "productId": 101 "" | Validate for specific product |
| POST | /api/mobile/promotions/apply | "" "code": "FLAT500" "" | Mobile equivalent - apply to cart |
| POST | /api/mobile/promotions/apply | "" "code": "FLAT500", "productId": 205 "" | Mobile equivalent - product validation |
11.1.5 Admin Create Promotion Request Variations
| Method | Path | Request Body | Notes |
|---|---|---|---|
| POST | /api/admin/promotions | "" "code": "SUMMER2026", "discountType": "percentage", "discountValue": 10, "maxDiscountCap": 5000, "startDate": "2026-04-01T00:00:00.000Z", "endDate": "2026-04-30T23:59:59.000Z" "" | Percentage discount with cap |
| POST | /api/admin/promotions | "" "code": "FLAT500", "discountType": "flat", "discountValue": 500, "minCartValue": 2000, "startDate": "2026-04-01T00:00:00.000Z", "endDate": "2026-06-30T23:59:59.000Z" "" | Flat discount with min cart |
| POST | /api/admin/promotions | "" "code": "PRODUCT50", "discountType": "percentage", "discountValue": 50, "startDate": "2026-04-01T00:00:00.000Z", "endDate": "2026-04-07T23:59:59.000Z", "scopes": ["" "scopeType": "product", "scopeId": 101 ""] "" | Product-specific promotion with scope |
11.1.6 Promotion Scope Variations
| scopeType | scopeId | Target |
|---|---|---|
| product | number | Specific product ID |
| category | number | Specific category ID |
| tag | number | Specific tag ID |
11.1.7 Promotion Response Field Variations
| Field | Type | Notes |
|---|---|---|
| id | number | Unique promotion ID |
| code | string | Normalized (uppercase) coupon code |
| description | string | null | Promotion description |
| discountType | string | flat or percentage |
| discountValue | number | Discount amount or percentage |
| maxDiscountCap | number | null | Maximum discount cap in paisa |
| minCartValue | number | null | Minimum cart value requirement |
| cartTypeEligibility | string | Currently only "digital" |
| paymentMethodEligibility | string | Currently only "online" |
| startDate | ISO8601 | Promotion start timestamp |
| endDate | ISO8601 | Promotion end timestamp |
| status | string | active/disabled/expired |
11.1.8 Coupon Validation Response Variations
| Field | Type | Notes |
|---|---|---|
| valid | boolean | Whether coupon applies to product |
| discount | number | Calculated discount in paisa |
| promotionId | number | Promotion ID |
| promotionCode | string | Normalized code |
11.1.9 Error Response Variations
| HTTP | errorCode | Trigger |
|---|---|---|
| 400 | PROMOTION_NOT_APPLICABLE | Coupon expired/inactive/ineligible |
| 400 | PROMOTION_INVALID_DATE_RANGE | Start date after end date |
| 400 | PROMOTION_DISCOUNT_INVALID | Invalid discount value/cap |
| 400 | PROMOTION_SCOPE_INVALID | Invalid scope IDs |
| 400 | PROMOTION_USAGE_CONFLICT | Has usage, cannot modify/delete |
| 404 | PROMOTION_NOT_FOUND | Promotion ID does not exist |
| 409 | PROMOTION_CODE_EXISTS | Duplicate code on create |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests |
11.1.10 Cache Invalidation Variations
| Trigger | Invalidated Key Pattern |
|---|---|
| POST /admin/promotions | promotions:admin:list:* |
| PATCH /admin/promotions/:id | promotions:admin:list:*, promotions:admin:detail:* |
| POST /admin/promotions/:id/disable | promotions:admin:list:*, promotions:customer:list:* |
| DELETE /admin/promotions/:id | promotions:admin:list:*, promotions:admin:detail:* |
| POST /promotions/apply | cart:user:userId:* |
| POST /promotions/remove | cart:user:userId:* |
11.1.11 Enums Reference
| Enum | Values |
|---|---|
| promotion_status | active, disabled, expired |
| discount_type | flat, percentage |
| cart_type_eligibility | digital |
| payment_method_eligibility | online |
| scope_type | product, category, tag |
12. Testing Scenarios
12.1 Suggested Test Scenarios
- Admin creates promotion with valid date range, publishes it, and promotion appears in customer active list (
GET /api/promotions). - Admin creates promotion with product scope (
scopes["" scopeType: "product", scopeId: N ""]), applies coupon, and discount applies only to that product. - User applies valid coupon code to cart without productId, and cart discount is applied successfully via cart mutation.
- User applies coupon code with productId (validation-only mode), receives
"" valid: true/false ""without modifying cart. - User applies expired/inactive coupon and receives
400 PROMOTION_NOT_APPLICABLEwith detailed reason. - User applies coupon without meeting minimum cart value and receives
400 PROMOTION_NOT_APPLICABLEfor minCartValue. - User removes applied coupon from cart and cart reverts to original pricing.
- Admin deletes promotion with usage history and receives
400 PROMOTION_USAGE_CONFLICT; disable succeeds.
See Also
- Feature Guide: See Promotion - Feature List Section 6 (State Models) for promotion lifecycle and scope diagrams.
- Backend Reference: See Promotion - Backend Documentation Section 11 for system architecture diagram.
- Apply endpoint returns
429 RATE_LIMIT_EXCEEDEDafter sliding window threshold exceeded.