useLoyalty
Server SDK

Quests

Create challenges and track member progress

Quests are challenges that members complete to earn points. They can be manual (admin-triggered), self-reported (member submits proof), or automated (triggered by events).

Quest Types

TypeDescriptionUse Case
MANUALAdmin triggers completionSpecial promotions, offline events
SELF_REPORTMember submits proof for approvalSocial sharing, UGC content
AUTOMATEDTriggered by tracking eventsPurchases, logins, actions

List Quests

Fetch all quests configured for your program.

GET /api/v1/quests

SDK

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

Each quest includes requiredProgress, startDate, endDate, and badgeReward fields.


Get Quest

Fetch a single quest by ID.

GET /api/v1/quests/:questId

SDK

const quest = await useLoyalty.quests.get("quest_first_review");

List Member Quests

Get all quests with per-member progress and completion state in one call.

GET /api/v1/members/:externalId/quests

SDK

const memberQuests = await useLoyalty.quests.listMemberQuests("user_123");
// also available via: useLoyalty.members.getQuests('user_123')

Response

[
  {
    "id": "quest_first_review",
    "name": "Write Your First Review",
    "pointsReward": 100,
    "requiredProgress": null,
    "progress": 1,
    "completed": true,
    "completionCount": 1,
    "completedAt": "2024-01-18T09:00:00Z"
  },
  {
    "id": "quest_loyal_buyer",
    "name": "Make 5 Purchases",
    "pointsReward": 500,
    "requiredProgress": 5,
    "progress": 3,
    "completed": false,
    "completionCount": 0,
    "completedAt": null
  }
]

Get Member Progress on a Quest

Fetch progress for a specific member on a specific quest.

GET /api/v1/quests/:questId/progress/:externalId

SDK

const progress = await useLoyalty.quests.getMemberProgress(
  "user_123",
  "quest_loyal_buyer",
);
// { memberId, questId, progress: 3, target: 5, completed: false }

Complete Quest

Mark a quest as complete for a member. Works for MANUAL and SELF_REPORT quest types.

POST /api/v1/quests/:questId/complete

Request Body

{
  "externalId": "user_123",
  "metadata": {
    "completedBy": "admin",
    "notes": "Verified social media post"
  }
}

Parameters

FieldTypeRequiredDescription
externalIdstringYesMember's external ID
metadataobjectNoAdditional completion data

Response

{
  "completion": {
    "id": "comp_abc123",
    "questId": "quest_social_share",
    "questName": "Share on Social Media",
    "memberId": "member_xyz",
    "pointsAwarded": 50,
    "basePoints": 50,
    "multiplier": 1,
    "completedAt": "2024-01-20T12:00:00Z"
  },
  "member": {
    "externalId": "user_123",
    "points": 1550,
    "totalPointsEarned": 2550
  }
}

Track Quest Progress

Update progress on multi-step quests. Automatically completes when target is reached.

POST /api/v1/quests/:questId/progress

Request Body

{
  "externalId": "user_123",
  "increment": 1,
  "metadata": {
    "action": "purchase",
    "orderId": "order_456"
  }
}

Parameters

FieldTypeRequiredDescription
externalIdstringYesMember's external ID
incrementnumberNoProgress increment (default: 1)
metadataobjectNoAdditional context

Response (Progress Updated)

{
  "progress": {
    "current": 3,
    "target": 5,
    "percentage": 60
  },
  "completed": false,
  "quest": {
    "id": "quest_5_purchases",
    "name": "Make 5 Purchases",
    "pointsReward": 500
  }
}

Response (Quest Completed)

{
  "progress": {
    "current": 5,
    "target": 5,
    "percentage": 100
  },
  "completed": true,
  "completion": {
    "id": "comp_def456",
    "pointsAwarded": 500,
    "completedAt": "2024-01-20T14:00:00Z"
  }
}

Quest Configuration

Quests are configured in the dashboard with these options:

SettingDescription
nameQuest display name
descriptionWhat the member needs to do
pointsRewardPoints awarded on completion
typeMANUAL, SELF_REPORT, or AUTOMATED
actionEvent name for AUTOMATED quests
targetCountSteps required (default: 1)
maxPerUserMax completions per member
maxCompletionsGlobal completion limit
isRepeatableAllow multiple completions
cooldownMinutesTime between completions
startsAt / endsAtAvailability window

Code Examples

Complete Manual Quest

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

  return response.json();
}

// Admin marks quest complete after verification
await completeQuest("quest_social_share", "user_123");

Track Multi-Step Progress

async function trackQuestProgress(
  questId: string,
  userId: string,
  increment = 1,
) {
  const response = await fetch(
    `https://app.useloyalty.app/api/v1/quests/${questId}/progress`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.USELOYALTY_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        externalId: userId,
        increment,
      }),
    },
  );

  const data = await response.json();

  if (data.completed) {
    console.log(
      `Quest completed! Awarded ${data.completion.pointsAwarded} points`,
    );
  } else {
    console.log(`Progress: ${data.progress.current}/${data.progress.target}`);
  }

  return data;
}

// Track each purchase towards "Make 5 Purchases" quest
await trackQuestProgress("quest_5_purchases", "user_123");

Handle Quest Limits

async function safeCompleteQuest(questId: string, userId: string) {
  try {
    const result = await completeQuest(questId, userId);
    return { success: true, data: result };
  } catch (error) {
    if (error.status === 400) {
      const body = await error.json();

      if (body.message.includes("maximum completions")) {
        return { success: false, reason: "max_reached" };
      }
      if (body.message.includes("cooldown")) {
        return { success: false, reason: "cooldown_active" };
      }
    }
    throw error;
  }
}

Automated Quests

For AUTOMATED quests, use the Events API instead of direct completion:

// Quest configured with action: "product_review"
// When user writes a review:
await trackEvent("user_123", "product_review", {
  productId: "prod_456",
  rating: 5,
});

The Events API automatically:

  • Finds quests matching the event name
  • Checks completion limits and cooldowns
  • Increments progress or completes the quest
  • Awards points with active multipliers

Error Handling

StatusErrorSolution
400Quest not foundCheck quest ID exists
400Quest not activeQuest outside time window
400Maximum completions reachedMember hit completion limit
400Cooldown not expiredWait for cooldown period
404Member not foundCreate member first
{
  "error": "Quest completion failed",
  "message": "Member has reached maximum completions for this quest"
}

Best Practices

1. Use Automated Quests When Possible

// Instead of manual tracking:
onPurchase((order) => {
  trackQuestProgress("purchase_quest", order.userId);
});

// Configure quest as AUTOMATED with action "purchase"
// Then just track the event:
onPurchase((order) => {
  trackEvent(order.userId, "purchase");
});

2. Include Meaningful Metadata

await completeQuest("quest_social_share", userId, {
  metadata: {
    platform: "twitter",
    postUrl: "https://twitter.com/...",
    verifiedBy: "admin_jane",
    verifiedAt: new Date().toISOString(),
  },
});

3. Handle Time-Limited Quests

async function checkQuestAvailable(questId: string) {
  // Quest availability is validated server-side
  // Handle the error gracefully
  try {
    await completeQuest(questId, userId);
  } catch (error) {
    if (error.message.includes("not active")) {
      showMessage("This quest has ended");
    }
  }
}

On this page