useLoyalty
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-codes

SDK

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/validate

Request 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/redeem

Request Body

{
  "externalId": "user_123",
  "code": "SUMMER50"
}

Parameters

FieldTypeRequiredDescription
externalIdstringYesMember's external ID
codestringYesThe 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:

TypeDescriptionResponse Field
POINTSAwards points directlypointsAwarded
REWARDGrants a specific rewardreward

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

StatusErrorSolution
400Promo code is not yet activeWait until start date
400Promo code has expiredCode is past expiration date
400Maximum redemptions reachedCode has hit global limit
400Already redeemed maximum timesUser hit per-user limit
404Invalid promo codeCode doesn't exist or is inactive
{
  "error": "Promo code has expired"
}

Promo Code Limits

Promo codes support multiple limit types:

LimitDescription
maxRedemptionsTotal redemptions across all users
maxPerUserRedemptions per individual user
startsAtCode activation date
expiresAtCode 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,
});

On this page