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/badgesSDK
const badges = await useLoyalty.badges.list();Get Badge
Fetch a single badge definition by ID.
GET /api/v1/badges/:badgeIdSDK
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/:badgeIdSDK
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/badgesResponse
{
"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/badgesRequest 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/badgesRequest Body
{
"badgeId": "badge_vip_member"
}Response
{
"success": true,
"message": "Badge removed successfully"
}Badge Rarity Levels
| Rarity | Description | Example |
|---|---|---|
COMMON | Easy to obtain | First login, profile complete |
UNCOMMON | Moderate effort | 10 purchases, 30-day streak |
RARE | Significant achievement | 100 purchases, early adopter |
EPIC | Exceptional accomplishment | Top 10% of users, annual VIP |
LEGENDARY | Ultimate achievement | Founding 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
| Badge | Criteria |
|---|---|
| Welcome | Created account |
| Profile Pro | Completed profile |
| Social Butterfly | Connected social accounts |
| Newsletter Fan | Subscribed to newsletter |
Activity Badges
| Badge | Criteria |
|---|---|
| Early Bird | Logged in before 6 AM |
| Night Owl | Logged in after midnight |
| Weekend Warrior | Active on weekends |
| Daily Devotee | 7-day login streak |
Purchase Badges
| Badge | Criteria |
|---|---|
| First Timer | First purchase |
| Regular | 10 purchases |
| Loyal Customer | 50 purchases |
| VIP | 100 purchases |
Community Badges
| Badge | Criteria |
|---|---|
| Reviewer | First review |
| Influencer | 10 referrals converted |
| Ambassador | 50 referrals converted |
| Legend | 100 referrals converted |
Error Handling
| Status | Error | Solution |
|---|---|---|
400 | Badge not found | Check badge ID exists |
404 | Member not found | Create member first |
409 | Badge already awarded | Member 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);
}
}
}