/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"]
name, description, code, isActive, createdAt, updatedAt, maxRedemptions, redemptionCount, validUntil, couponName, couponType, couponTypeName, percentOff, trialDays
sort=["couponName","ASC"] - Sort by coupon 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={"couponType":"DISC","couponName":"Summer"} - Filter by coupon type and name
q - Global search across promotion name, description, code, and coupon namepromotionName - Filter by promotion name (partial match, case-insensitive)couponType - Filter by coupon type code (DISC, TRIA, NONE). Use NONE to filter promotions without couponscouponName - Filter by coupon name (partial match, case-insensitive)isActive - Filter by active status (true/false)q in the filter will search across all text columns:
filter={"q":"summer"} - Find promotions with "summer" in name, description, code, or coupon name
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={"isActive":true}&syncWithStripe=true
GET /api/promotions?page=1&perPage=20&filter={"q":"summer","couponType":"DISC"}
GET /api/promotions?filter={"couponType":"NONE"}
GET /api/promotions?filter={"promotionName":"summer"}
GET /api/promotions?filter={"promotionName":"welcome","isActive":true}
Response (200 OK):
{
"data": [
{
"id": 1,
"name": "Summer Sale",
"description": "20% off summer products",
"code": "SUMMER20",
"couponId": 1,
"maxRedemptions": 100,
"redemptionCount": 45,
"validUntil": "2023-09-30T23:59:59Z",
"isActive": true,
"createdAt": "2023-07-15T14:25:00Z",
"updatedAt": null,
"coupon": {
"couponId": 1,
"couponName": "Welcome Discount",
"couponType": "DISC",
"couponTypeName": "Discount Percentage",
"percentOff": 20,
"trialDays": 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"]
appliedAt, promotionName, promotionCode, username, email, planName, couponTypeName, percentOff, trialDays
sort=["appliedAt","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={"q":"summer","startDate":"2023-06-01","endDate":"2023-08-31"}
q - Global search across promotion names, promotion codes, usernames, email addresses, and plan namesuserId - Filter by user ID (number)promotionId - Filter by promotion ID (number)promotionName - Filter by promotion name (partial match, case-insensitive)couponId - Filter by coupon ID (number)subscriptionId - Filter by subscription ID (number)couponType - Filter by coupon type code (DISC, TRIA, NONE). Use NONE to filter applied promotions without couponsstartDate - Filter by application date from this date onwards (YYYY-MM-DD format)endDate - Filter by application date up to this date (YYYY-MM-DD format)q in the filter will search across key text columns:
filter={"q":"summer"} - Find applied promotions with "summer" in promotion name, promotion code, username, email, or plan name
Example Requests:
GET /api/promotions/applied?page=1&perPage=10&sort=["appliedAt","DESC"]&filter={"q":"summer"}
GET /api/promotions/applied?page=1&perPage=20&filter={"q":"john","startDate":"2023-06-01","endDate":"2023-08-31"}
GET /api/promotions/applied?filter={"startDate":"2023-01-01","endDate":"2023-12-31"}
GET /api/promotions/applied?filter={"userId":42,"promotionId":5}
GET /api/promotions/applied?filter={"q":"discount"}
GET /api/promotions/applied?sort=["promotionName","ASC"]&filter={"startDate":"2023-06-01"}
GET /api/promotions/applied?sort=["couponTypeName","ASC"]&filter={"couponId":3}
GET /api/promotions/applied?sort=["percentOff","DESC"]&filter={"subscriptionId":18}
GET /api/promotions/applied?filter={"couponType":"NONE"}
GET /api/promotions/applied?filter={"promotionName":"welcome"}
GET /api/promotions/applied?filter={"promotionName":"summer","couponType":"DISC"}
Response (200 OK) - with subscription:
{
"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"
}
],
"total": 42
}
Response (200 OK) - without subscription (NONS promotion):
{
"data": [
{
"id": 3,
"appliedAt": "2023-08-01T10:30:00Z",
"promotionId": 7,
"promotionName": "Welcome Offer",
"promotionCode": "WELCOME10",
"couponId": null,
"couponType": null,
"couponTypeName": null,
"percentOff": null,
"trialDays": null,
"subscriptionId": null,
"planName": null,
"planInterval": null,
"planAmount": null,
"planCurrency": null,
"userId": 45,
"username": "newuser",
"email": "newuser@example.com"
}
],
"total": 15
}
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:
user_id column in promotion_redemptionssubscriptionId, planName, etc.) and coupon-related fields will be nullapplied_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, default: true)Response (200 OK):
{
"id": 1,
"name": "Summer Sale 2023",
"description": "Special discounts for summer products",
"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": {
"couponId": 1,
"couponName": "Welcome Discount",
"couponType": "DISC",
"couponTypeName": "Discount Percentage",
"percentOff": 20,
"trialDays": null
}
}
Error Responses:
{
"message": "Administrator access required"
}
{
"message": "Invalid promotion ID"
}
{
"message": "Promotion not found"
}
Notes:
coupon field is included by default with associated coupon details/api/promotions/:id/coupons
Get coupons associated with a promotion (requires administrator access)
Headers:
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
Response (200 OK):
[
{
"id": 1,
"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 (for subscribed users):
{
"name": "Holiday Special",
"description": "Special promotions for the holiday season",
"code": "HOLIDAY25",
"userTypeCode": "SUBS",
"couponId": 1,
"maxRedemptions": 500,
"validUntil": "2023-12-31T23:59:59Z",
"isActive": true
}
Request Body (for non-subscribed users):
{
"name": "Welcome Offer",
"description": "Special promotion for new users",
"code": "WELCOME10",
"userTypeCode": "NONS",
"maxRedemptions": 1000,
"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",
"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, description, and userTypeCode are required"
}
{
"message": "Invalid userTypeCode. Must be either 'SUBS' or 'NONS'"
}
{
"message": "couponId is required when userTypeCode is 'SUBS'"
}
{
"message": "Promotion code already exists"
}
{
"message": "Coupon not found"
}
{
"message": "Failed to create promotion",
"error": "Error creating promotion in Stripe"
}
Notes:
userTypeCode parameter determines the target user type: "SUBS" (subscribed) or "NONS" (non-subscribed)userTypeCode is "SUBS", the couponId is required and the promotion is created in both Stripe and the local databaseuserTypeCode is "NONS", the couponId should be omitted and the promotion is created only in the local databasecode must be unique across all promotions/api/promotions/apply
Apply a promotion code to the user's subscription or grant subscription exemption (requires authentication and CSRF token)
Headers:
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr... x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3
Request Body:
{
"code": "SUMMER20"
}
Response (200 OK) - Percentage Discount (Subscribed Users):
{
"message": "Promotion code applied successfully",
"discount": {
"type": "percentage",
"percentOff": 20,
"amountOff": null,
"currency": null
}
}
Response (200 OK) - Amount Discount (Subscribed Users):
{
"message": "Promotion code applied successfully",
"discount": {
"type": "amount",
"percentOff": null,
"amountOff": 500,
"currency": "usd"
}
}
Response (200 OK) - Subscription Exemption with End Date (Non-Subscribed Users):
{
"message": "Promotion code applied successfully. Your account has been updated with extended access.",
"discount": {
"type": "exemption",
"exemptionEndsAt": "2023-12-31T23:59:59Z"
}
}
Response (200 OK) - Unlimited Subscription Exemption (Non-Subscribed Users):
{
"message": "Promotion code applied successfully. Your account has been granted unlimited access.",
"discount": {
"type": "exemption",
"exemptionEndsAt": null
}
}
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:
subscription_exemption_ends_at date to the promotion's validUntil datevalidUntil date, the subscription_exemption_ends_at is set to null, granting unlimited access/api/promotions/apply-to-user
Apply a promotion code to a specified user's subscription or grant subscription exemption (requires administrator access and CSRF token)
Headers:
Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr... x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3
Request Body:
{
"userId": 42,
"code": "SUMMER20"
}
Response (200 OK) - Percentage Discount (Subscribed Users):
{
"message": "Promotion code applied successfully",
"discount": {
"type": "percentage",
"percentOff": 20,
"amountOff": null,
"currency": null
}
}
Response (200 OK) - Amount Discount (Subscribed Users):
{
"message": "Promotion code applied successfully",
"discount": {
"type": "amount",
"percentOff": null,
"amountOff": 500,
"currency": "usd"
}
}
Response (200 OK) - Subscription Exemption with End Date (Non-Subscribed Users):
{
"message": "Promotion code applied successfully. Your account has been updated with extended access.",
"discount": {
"type": "exemption",
"exemptionEndsAt": "2023-12-31T23:59:59Z"
}
}
Response (200 OK) - Unlimited Subscription Exemption (Non-Subscribed Users):
{
"message": "Promotion code applied successfully. Your account has been granted unlimited access.",
"discount": {
"type": "exemption",
"exemptionEndsAt": null
}
}
Error Responses:
{
"message": "Administrator access required"
}
{
"message": "User ID is 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:
subscription_exemption_ends_at date to the promotion's validUntil datevalidUntil date, the subscription_exemption_ends_at is set to null, granting unlimited access/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",
"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 - Stripe does not allow updating this field after promotion code creationisActive updates are sent to Stripe as the active fieldname and description updates are stored in Stripe metadata/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: