useLoyalty
Server SDK

Badges

Award achievements and milestones to members

Badges are achievements that recognize member accomplishments. Use them to celebrate milestones, encourage specific behaviors, and add visual flair to your gamification system.

List All Badges

Retrieve all badge definitions in your program.

GET /api/v1/badges

SDK

const badges = await useLoyalty.badges.list();

Get Badge

Fetch a single badge definition by ID.

GET /api/v1/badges/:badgeId

SDK

const badge = await useLoyalty.badges.get("badge_early_adopter");

Response

{
  "id": "badge_early_adopter",
  "name": "Early Adopter",
  "description": "Joined during the beta period",
  "imageUrl": "https://cdn.example.com/badges/early-adopter.png",
  "rarity": "RARE",
  "criteria": { "joinedBefore": "2024-03-01" }
}

Get Single Earned Badge

Retrieve a specific badge that a member has earned, including the earnedAt timestamp.

GET /api/v1/members/:externalId/badges/:badgeId

SDK

const earned = await useLoyalty.badges.getMemberBadge(
  "user_123",
  "badge_early_adopter",
);
// { ...badge, earnedAt: "2024-01-20T10:30:00Z" }

Get Member Badges

Retrieve all badges earned by a member.

GET /api/v1/members/:externalId/badges

Response

{
  "badges": [
    {
      "id": "badge_early_adopter",
      "name": "Early Adopter",
      "description": "Joined during the beta period",
      "imageUrl": "https://cdn.example.com/badges/early-adopter.png",
      "rarity": "RARE",
      "awardedAt": "2024-01-01T00:00:00Z"
    },
    {
      "id": "badge_first_purchase",
      "name": "First Purchase",
      "description": "Made your first purchase",
      "imageUrl": "https://cdn.example.com/badges/first-purchase.png",
      "rarity": "COMMON",
      "awardedAt": "2024-01-15T10:30:00Z"
    }
  ],
  "count": 2
}

Award Badge

Award a badge to a member.

POST /api/v1/members/:externalId/badges

Request Body

{
  "badgeId": "badge_vip_member"
}

Response

{
  "success": true,
  "badge": {
    "id": "badge_vip_member",
    "name": "VIP Member",
    "description": "Reached VIP status",
    "imageUrl": "https://cdn.example.com/badges/vip.png",
    "rarity": "LEGENDARY",
    "awardedAt": "2024-01-20T16:00:00Z"
  },
  "member": {
    "externalId": "user_123",
    "badgeCount": 3
  }
}

If the member already has this badge, the API returns a 409 Conflict error.

Remove Badge

Remove a badge from a member.

DELETE /api/v1/members/:externalId/badges

Request Body

{
  "badgeId": "badge_vip_member"
}

Response

{
  "success": true,
  "message": "Badge removed successfully"
}

Badge Rarity Levels

RarityDescriptionExample
COMMONEasy to obtainFirst login, profile complete
UNCOMMONModerate effort10 purchases, 30-day streak
RARESignificant achievement100 purchases, early adopter
EPICExceptional accomplishmentTop 10% of users, annual VIP
LEGENDARYUltimate achievementFounding member, 1000 referrals

Code Examples

Award Badge on Achievement

async function awardBadge(userId: string, badgeId: string) {
  const response = await fetch(
    `https://app.useloyalty.app/api/v1/members/${userId}/badges`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.USELOYALTY_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ badgeId }),
    },
  );

  if (response.status === 409) {
    // Member already has this badge
    return { alreadyAwarded: true };
  }

  return response.json();
}

// Award milestone badges
async function checkMilestones(userId: string) {
  const member = await getMember(userId);

  // Purchase milestones
  if (member.purchaseCount >= 10 && !hasBadge(member, "badge_10_purchases")) {
    await awardBadge(userId, "badge_10_purchases");
  }

  // Points milestones
  if (
    member.totalPointsEarned >= 10000 &&
    !hasBadge(member, "badge_10k_points")
  ) {
    await awardBadge(userId, "badge_10k_points");
  }

  // Streak milestones
  if (member.longestStreak >= 30 && !hasBadge(member, "badge_30_day_streak")) {
    await awardBadge(userId, "badge_30_day_streak");
  }
}

Display Badge Collection

async function getBadgeCollection(userId: string) {
  const response = await fetch(
    `https://app.useloyalty.app/api/v1/members/${userId}/badges`,
    {
      headers: {
        Authorization: `Bearer ${process.env.USELOYALTY_API_KEY}`,
      },
    },
  );

  const data = await response.json();

  // Group by rarity for display
  const grouped = {
    legendary: data.badges.filter((b) => b.rarity === "LEGENDARY"),
    epic: data.badges.filter((b) => b.rarity === "EPIC"),
    rare: data.badges.filter((b) => b.rarity === "RARE"),
    uncommon: data.badges.filter((b) => b.rarity === "UNCOMMON"),
    common: data.badges.filter((b) => b.rarity === "COMMON"),
  };

  return grouped;
}

Automatic Badge Awards

// Integrate with your event system
async function onUserAction(userId: string, action: string, data: any) {
  switch (action) {
    case "purchase":
      await checkPurchaseBadges(userId, data);
      break;
    case "review":
      await checkReviewBadges(userId, data);
      break;
    case "referral_converted":
      await checkReferralBadges(userId, data);
      break;
  }
}

async function checkPurchaseBadges(userId: string, purchase: any) {
  const stats = await getUserPurchaseStats(userId);

  const milestones = [
    { count: 1, badge: "badge_first_purchase" },
    { count: 10, badge: "badge_10_purchases" },
    { count: 50, badge: "badge_50_purchases" },
    { count: 100, badge: "badge_100_purchases" },
  ];

  for (const milestone of milestones) {
    if (stats.totalPurchases >= milestone.count) {
      await awardBadge(userId, milestone.badge).catch(() => {
        // Already awarded, ignore
      });
    }
  }
}

Badge Notification System

async function awardBadgeWithNotification(userId: string, badgeId: string) {
  try {
    const result = await awardBadge(userId, badgeId);

    if (result.success) {
      // Send notification
      await sendNotification(userId, {
        type: "badge_earned",
        title: "New Badge Earned!",
        message: `You earned the "${result.badge.name}" badge!`,
        imageUrl: result.badge.imageUrl,
        data: {
          badgeId: result.badge.id,
          rarity: result.badge.rarity,
        },
      });

      // Track analytics
      await trackAnalytics("badge_earned", {
        userId,
        badgeId: result.badge.id,
        badgeName: result.badge.name,
        rarity: result.badge.rarity,
      });
    }

    return result;
  } catch (error) {
    if (error.status === 409) {
      // Already has badge, not an error
      return { alreadyAwarded: true };
    }
    throw error;
  }
}

Badge Suggestions

Engagement Badges

BadgeCriteria
WelcomeCreated account
Profile ProCompleted profile
Social ButterflyConnected social accounts
Newsletter FanSubscribed to newsletter

Activity Badges

BadgeCriteria
Early BirdLogged in before 6 AM
Night OwlLogged in after midnight
Weekend WarriorActive on weekends
Daily Devotee7-day login streak

Purchase Badges

BadgeCriteria
First TimerFirst purchase
Regular10 purchases
Loyal Customer50 purchases
VIP100 purchases

Community Badges

BadgeCriteria
ReviewerFirst review
Influencer10 referrals converted
Ambassador50 referrals converted
Legend100 referrals converted

Error Handling

StatusErrorSolution
400Badge not foundCheck badge ID exists
404Member not foundCreate member first
409Badge already awardedMember already has badge
{
  "error": "Conflict",
  "message": "Member already has this badge"
}

Best Practices

1. Award Badges Idempotently

// Safe to call multiple times
async function ensureBadge(userId: string, badgeId: string) {
  try {
    await awardBadge(userId, badgeId);
  } catch (error) {
    if (error.status !== 409) {
      throw error; // Re-throw non-duplicate errors
    }
  }
}

2. Use Descriptive Badge Names

// Good
const badges = {
  badge_first_purchase: "First Purchase",
  badge_loyal_customer: "Loyal Customer",
  badge_review_champion: "Review Champion",
};

// Avoid
const badges = {
  badge_1: "Badge 1",
  badge_a: "Achievement A",
};

3. Create Badge Progression

// Track badge tiers
const purchaseBadges = [
  { threshold: 1, id: "badge_purchase_1", name: "Shopper" },
  { threshold: 10, id: "badge_purchase_10", name: "Regular" },
  { threshold: 50, id: "badge_purchase_50", name: "VIP" },
  { threshold: 100, id: "badge_purchase_100", name: "Legend" },
];

async function awardPurchaseBadges(userId: string, purchaseCount: number) {
  for (const badge of purchaseBadges) {
    if (purchaseCount >= badge.threshold) {
      await ensureBadge(userId, badge.id);
    }
  }
}

On this page