/api/promotions
List all promotions with optional filtering (requires administrator access)
Headers:
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
Query Parameters:
sort
- JSON array with field name and direction
["fieldName","ASC|DESC"]
sort=["name","ASC"]
- Sort by name in ascending order
page
- Page number (1-based)
page=1
- Get the first page
perPage
- Number of items per page
perPage=10
- Show 10 items per page
filter
- JSON object with field name/value pairs for filtering
{"fieldName1":"value1","fieldName2":value2}
filter={"name":"Summer"}
- Filter promotions with name containing "Summer"
q
in the filter will search across all text columns:
filter={"q":"summer","active":true}
- Find active promotions with "summer" in any text field
active
- Filter by active status (true/false)syncWithStripe
- Whether to sync with Stripe before fetching data (default: true)Example Requests:
GET /api/promotions?page=1&perPage=10&sort=["name","ASC"]&filter={"active":true}&syncWithStripe=true
GET /api/promotions?page=1&perPage=20&filter={"q":"summer"}
Response (200 OK):
{ "data": [ { "id": 1, "name": "Summer Sale", "description": "20% off summer products", "stripePromotionCodeId": "promo_1NcL5GJ8oAJtHC9rnvmaJ4Hl", "code": "SUMMER20", "couponId": 1, "maxRedemptions": 100, "redemptionCount": 45, "validUntil": "2023-09-30T23:59:59Z", "isActive": true, "createdAt": "2023-07-15T14:25:00Z", "updatedAt": null } // Additional promotion objects... ], "total": 2 }
Headers:
Content-Range: promotions 0-1/2 Accept-Range: promotions Access-Control-Expose-Headers: Content-Range X-Total-Count: 2
Error Responses:
{ "message": "Administrator access required" }
Notes:
syncWithStripe
parameter ensures promotion data is synchronized with Stripe before filtering/api/promotions/applied
List all applied promotions with filtering, sorting, and pagination (requires administrator access)
Headers:
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
Query Parameters:
sort
- JSON array with field name and direction
["fieldName","ASC|DESC"]
applied_at
, promotion_name
, promotion_code
, username
, email
, plan_name
sort=["applied_at","DESC"]
- Sort by application date descending
page
- Page number (1-based)
page=1
- Get the first page
perPage
- Number of items per page
perPage=10
- Show 10 items per page
filter
- JSON object with field name/value pairs for filtering
filter={"userId":42,"startDate":"2023-06-01","endDate":"2023-08-31"}
q
- Global search across promotion names, codes, usernames, and emailsuserId
- Filter by specific user IDpromotionId
- Filter by specific promotion IDsubscriptionId
- Filter by specific subscription IDstartDate
- Filter by applied date (YYYY-MM-DD format)endDate
- Filter by applied date (YYYY-MM-DD format)Example Requests:
GET /api/promotions/applied?page=1&perPage=10&sort=["applied_at","DESC"]&filter={"userId":42}
GET /api/promotions/applied?page=1&perPage=20&filter={"q":"summer","startDate":"2023-06-01","endDate":"2023-08-31"}
Response (200 OK):
{ "data": [ { "id": 1, "appliedAt": "2023-07-15T14:25:00Z", "promotionId": 5, "promotionName": "Summer Sale", "promotionCode": "SUMMER20", "couponId": 1, "couponType": "DISC", "couponTypeName": "Discount Percentage", "percentOff": 20, "trialDays": null, "subscriptionId": 18, "planName": "Premium Monthly", "planInterval": "month", "planAmount": 2999, "planCurrency": "usd", "userId": 42, "username": "johndoe", "email": "john@example.com" }, { "id": 2, "appliedAt": "2023-07-10T09:15:00Z", "promotionId": 6, "promotionName": "Early Adopter", "promotionCode": "EARLY25", "couponId": 2, "couponType": "DISC", "couponTypeName": "Discount Percentage", "percentOff": 25, "trialDays": null, "subscriptionId": 24, "planName": "Pro Annual", "planInterval": "year", "planAmount": 29999, "planCurrency": "usd", "userId": 37, "username": "janesmith", "email": "jane@example.com" } ], "total": 42 }
Headers:
Content-Range: applied-promotions 0-1/42 Accept-Range: applied-promotions Access-Control-Expose-Headers: Content-Range X-Total-Count: 42
Error Responses:
{ "message": "Administrator access required" } { "message": "Invalid date format in filter. Use YYYY-MM-DD format." }
Notes:
applied_at
in descending order (newest first)/api/promotions/:id
Get details of a specific promotion by ID (requires administrator access)
Headers:
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
Query Parameters:
includeCoupons
- Whether to include associated coupon details (true/false)Response (200 OK):
{ "id": 1, "name": "Summer Sale 2023", "description": "Special discounts for summer products", "stripePromotionCodeId": "promo_1NcL5GJ8oAJtHC9rnvmaJ4Hl", "code": "SUMMER20", "couponId": 1, "maxRedemptions": 100, "redemptionCount": 45, "validUntil": "2023-09-30T23:59:59Z", "isActive": true, "createdAt": "2023-05-15T00:00:00Z", "updatedAt": null, "coupon": { "id": 1, "stripeCouponId": "Q9Wpvk4o", "name": "Welcome Discount", "couponTypeCode": "DISC", "couponTypeName": "Discount Percentage", "discountPercentage": 20, "trialDays": null, "couponDurationTypeCode": "ONCE", "couponDurationTypeName": "Once", "durationInMonths": null, "isActive": true, "createdAt": "2023-01-15T08:30:00Z", "updatedAt": null } }
Error Responses:
{ "message": "Administrator access required" } { "message": "Invalid promotion ID" } { "message": "Promotion not found" }
Notes:
coupon
field is only included when includeCoupons=true
/api/promotions/:id/coupons
Get coupons associated with a promotion (requires administrator access)
Headers:
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
Response (200 OK):
[ { "id": 1, "stripeCouponId": "Q9Wpvk4o", "name": "Welcome Discount", "couponTypeCode": "DISC", "couponTypeName": "Discount Percentage", "discountPercentage": 20, "trialDays": null, "couponDurationTypeCode": "ONCE", "couponDurationTypeName": "Once", "durationInMonths": null, "isActive": true, "createdAt": "2023-01-15T08:30:00Z", "updatedAt": null, "timesRedeemed": 45 } ]
Error Responses:
{ "message": "Administrator access required" } { "message": "Invalid promotion ID" } { "message": "Promotion not found" }
Notes:
timesRedeemed
field comes from Stripe data/api/promotions/validate/:code
Validate a promotion code without applying it (requires authentication)
Headers:
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
Response (200 OK) - Valid Promotion:
{ "valid": true, "promotion": { "id": 1, "name": "Summer Sale", "description": "20% off summer products", "code": "SUMMER20", "coupon": { "type": "DISC", "name": "Summer Discount", "discountPercentage": 20, "trialDays": null } } }
Response (200 OK) - Invalid Promotion:
{ "valid": false, "message": "Promotion code not found" }
Error Responses:
{ "message": "Authentication required" } { "message": "Promotion code is required" } { "message": "Promotion code is inactive" } { "message": "Promotion code has expired" } { "message": "Maximum redemptions reached for this promotion code" }
Notes:
/api/promotions
Create a new promotion code for an existing coupon (requires administrator access and CSRF token)
Headers:
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr... x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3
Request Body:
{ "name": "Holiday Special", "description": "Special promotions for the holiday season", "code": "HOLIDAY25", "couponId": 1, "maxRedemptions": 500, "validUntil": "2023-12-31T23:59:59Z", "isActive": true }
Response (201 Created):
{ "message": "Promotion created successfully", "promotion": { "id": 3, "name": "Holiday Special", "description": "Special promotions for the holiday season", "stripePromotionCodeId": "promo_1NcL5GJ8oAJtHC9rnvmaJ4Hl", "code": "HOLIDAY25", "couponId": 1, "maxRedemptions": 500, "redemptionCount": 0, "validUntil": "2023-12-31T23:59:59Z", "isActive": true, "createdAt": "2023-11-01T00:00:00Z", "updatedAt": null } }
Error Responses:
{ "message": "Administrator access required" } { "message": "Missing required fields: name and description are required" } { "message": "Promotion code already exists" } { "message": "Coupon not found" } { "message": "Failed to create promotion", "error": "Error creating promotion in Stripe" }
Notes:
couponId
must reference an existing couponcode
must be unique across all promotions/api/promotions/apply
Apply a promotion code to the user's subscription (requires authentication and CSRF token)
Headers:
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr... x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3
Request Body:
{ "code": "SUMMER20" }
Response (200 OK) - Percentage Discount:
{ "message": "Promotion code applied successfully", "discount": { "type": "percentage", "percentOff": 20, "amountOff": null, "currency": null } }
Response (200 OK) - Amount Discount:
{ "message": "Promotion code applied successfully", "discount": { "type": "amount", "percentOff": null, "amountOff": 500, "currency": "usd" } }
Error Responses:
{ "message": "Authentication required" } { "message": "Promotion code is required" } { "message": "Promotion code not found" } { "message": "Promotion code is inactive" } { "message": "Promotion code has expired" } { "message": "Maximum redemptions reached for this promotion code" } { "message": "You must have an active subscription to apply a promotion code" } { "message": "Your subscription already has a promotion code applied" }
Notes:
/api/promotions/:id
Update a promotion's details (requires administrator access and CSRF token)
Note: Only certain fields can be updated. The code
, couponId
, and stripePromotionCodeId
cannot be changed once created.
Headers:
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr... x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3
Request Body:
{ "name": "Summer Sale 2023 Extended", "description": "Extended summer discounts for clearance", "isActive": true }
Response (200 OK):
{ "message": "Promotion updated successfully", "promotion": { "id": 1, "name": "Summer Sale 2023 Extended", "description": "Extended summer discounts for clearance", "stripePromotionCodeId": "promo_1NcL5GJ8oAJtHC9rnvmaJ4Hl", "code": "SUMMER20", "couponId": 1, "maxRedemptions": 200, "redemptionCount": 45, "validUntil": "2023-09-30T23:59:59Z", "isActive": true, "createdAt": "2023-05-15T00:00:00Z", "updatedAt": "2023-09-01T00:00:00Z" } }
Error Responses:
{ "message": "Administrator access required" } { "message": "Invalid promotion ID" } { "message": "No update parameters provided" } { "message": "Promotion not found" } { "message": "Failed to update promotion", "error": "Error updating promotion in Stripe" }
Notes:
name
, description
, maxRedemptions
, and isActive
can be updatedmaxRedemptions
is updated only in the local database, not sent to StripeisActive
updates are sent to Stripe as the active
field/api/promotions/:id
Delete a promotion (requires administrator access and CSRF token)
Headers:
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr... x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3
Response (200 OK):
{ "message": "Promotion deleted successfully" }
Error Responses:
{ "message": "Administrator access required" } { "message": "Invalid promotion ID" } { "message": "Promotion not found" } { "message": "Cannot delete promotion with associated coupons. Remove all coupons first." }
Notes: