Server SDK
Promo Codes
Redeem promotional codes for points or rewards
The Promo Codes API lets members redeem promotional codes for instant points or rewards. Use it for marketing campaigns, customer appreciation, or special promotions.
List Promo Codes
Fetch all active promo codes for your program.
GET /api/v1/promo-codesSDK
const codes = await useLoyalty.promoCodes.list();Response
[
{
"id": "pc_summer24",
"code": "SUMMER24",
"description": "Summer campaign — 200 bonus points",
"pointsAwarded": 200,
"maxRedemptions": 500,
"currentRedemptions": 143,
"expiresAt": "2024-08-31T23:59:59Z",
"isActive": true
}
]Validate Promo Code
Check if a code is valid before redeeming — useful for inline form validation.
POST /api/v1/promo-codes/validateRequest Body
{ "code": "SUMMER24" }SDK
const result = await useLoyalty.promoCodes.validate("SUMMER24");
if (!result.valid) {
console.error(result.reason); // e.g. "Code has expired"
} else {
console.log(`Valid! Awards ${result.pointsAwarded} points`);
}Response
{
"valid": true,
"code": "SUMMER24",
"pointsAwarded": 200,
"reason": null
}reason is populated when valid is false (e.g. "Code has expired", "Already redeemed", "Code not found").
Redeem Promo Code
Redeem a promo code for a member.
POST /api/v1/promo-codes/redeemRequest Body
{
"externalId": "user_123",
"code": "SUMMER50"
}Parameters
| Field | Type | Required | Description |
|---|---|---|---|
externalId | string | Yes | Member's external ID |
code | string | Yes | The promo code to redeem |
Response
{
"redemption": {
"id": "red_abc123",
"code": "SUMMER50",
"rewardType": "POINTS",
"pointsAwarded": 50,
"reward": null,
"redeemedAt": "2024-01-20T16:00:00Z"
},
"member": {
"id": "clx123abc",
"externalId": "user_123",
"points": 1550,
"totalPointsEarned": 2550
}
}Reward Types
Promo codes can grant different types of rewards:
| Type | Description | Response Field |
|---|---|---|
POINTS | Awards points directly | pointsAwarded |
REWARD | Grants a specific reward | reward |
Points Reward Response
{
"redemption": {
"code": "BONUS100",
"rewardType": "POINTS",
"pointsAwarded": 100,
"reward": null
}
}Reward Grant Response
{
"redemption": {
"code": "FREEDISCOUNT",
"rewardType": "REWARD",
"pointsAwarded": 0,
"reward": {
"id": "reward_456",
"name": "10% Discount"
}
}
}Code Examples
Basic Redemption
async function redeemPromoCode(userId: string, code: string) {
const response = await fetch(
"https://app.useloyalty.app/api/v1/promo-codes/redeem",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.USELOYALTY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
externalId: userId,
code,
}),
},
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.error);
}
return response.json();
}
// Usage
const result = await redeemPromoCode("user_123", "SUMMER50");
console.log(`Awarded ${result.redemption.pointsAwarded} points!`);Redemption with UI Feedback
async function handlePromoCodeSubmit(userId: string, code: string) {
try {
const result = await redeemPromoCode(userId, code);
if (result.redemption.rewardType === "POINTS") {
showNotification({
type: "success",
title: "Code Redeemed!",
message: `You earned ${result.redemption.pointsAwarded} points!`,
});
} else if (result.redemption.reward) {
showNotification({
type: "success",
title: "Reward Unlocked!",
message: `You received: ${result.redemption.reward.name}`,
});
}
return result;
} catch (error) {
showNotification({
type: "error",
title: "Invalid Code",
message: error.message,
});
throw error;
}
}Integration with Next.js API Route
// pages/api/redeem-promo.ts
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}
const { code } = req.body;
const userId = req.session?.userId;
if (!userId) {
return res.status(401).json({ error: "Not authenticated" });
}
try {
const result = await redeemPromoCode(userId, code);
res.json(result);
} catch (error) {
res.status(400).json({ error: error.message });
}
}Error Handling
| Status | Error | Solution |
|---|---|---|
400 | Promo code is not yet active | Wait until start date |
400 | Promo code has expired | Code is past expiration date |
400 | Maximum redemptions reached | Code has hit global limit |
400 | Already redeemed maximum times | User hit per-user limit |
404 | Invalid promo code | Code doesn't exist or is inactive |
{
"error": "Promo code has expired"
}Promo Code Limits
Promo codes support multiple limit types:
| Limit | Description |
|---|---|
maxRedemptions | Total redemptions across all users |
maxPerUser | Redemptions per individual user |
startsAt | Code activation date |
expiresAt | Code expiration date |
Best Practices
1. Validate Before Submission
Show clear feedback for invalid codes:
async function validateAndRedeem(userId: string, code: string) {
if (!code || code.trim().length === 0) {
throw new Error("Please enter a promo code");
}
// Codes are case-insensitive (auto-uppercased by API)
return redeemPromoCode(userId, code.trim());
}2. Handle Duplicate Redemptions
async function safeRedeem(userId: string, code: string) {
try {
return await redeemPromoCode(userId, code);
} catch (error) {
if (error.message.includes("already redeemed")) {
return {
alreadyRedeemed: true,
message: "You've already used this code",
};
}
throw error;
}
}3. Track Redemption Sources
Use metadata on the server side to track where codes are redeemed:
// Your API route
const result = await redeemPromoCode(userId, code);
// Log for analytics
await analytics.track("promo_code_redeemed", {
userId,
code,
source: req.headers["x-source"] || "web",
pointsAwarded: result.redemption.pointsAwarded,
});