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
| Type | Description | Use Case |
|---|---|---|
MANUAL | Admin triggers completion | Special promotions, offline events |
SELF_REPORT | Member submits proof for approval | Social sharing, UGC content |
AUTOMATED | Triggered by tracking events | Purchases, logins, actions |
List Quests
Fetch all quests configured for your program.
GET /api/v1/questsSDK
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/:questIdSDK
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/questsSDK
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/:externalIdSDK
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/completeRequest Body
{
"externalId": "user_123",
"metadata": {
"completedBy": "admin",
"notes": "Verified social media post"
}
}Parameters
| Field | Type | Required | Description |
|---|---|---|---|
externalId | string | Yes | Member's external ID |
metadata | object | No | Additional 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/progressRequest Body
{
"externalId": "user_123",
"increment": 1,
"metadata": {
"action": "purchase",
"orderId": "order_456"
}
}Parameters
| Field | Type | Required | Description |
|---|---|---|---|
externalId | string | Yes | Member's external ID |
increment | number | No | Progress increment (default: 1) |
metadata | object | No | Additional 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:
| Setting | Description |
|---|---|
name | Quest display name |
description | What the member needs to do |
pointsReward | Points awarded on completion |
type | MANUAL, SELF_REPORT, or AUTOMATED |
action | Event name for AUTOMATED quests |
targetCount | Steps required (default: 1) |
maxPerUser | Max completions per member |
maxCompletions | Global completion limit |
isRepeatable | Allow multiple completions |
cooldownMinutes | Time between completions |
startsAt / endsAt | Availability 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
| Status | Error | Solution |
|---|---|---|
400 | Quest not found | Check quest ID exists |
400 | Quest not active | Quest outside time window |
400 | Maximum completions reached | Member hit completion limit |
400 | Cooldown not expired | Wait for cooldown period |
404 | Member not found | Create 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");
}
}
}