Custom Widget UI Integration Guide
Build your own loyalty widget frontend by calling the Optimove Loyalty System API directly.
Build your own loyalty widget frontend by calling the Optimove Loyalty System API directly.
Environments
| Environment | Base URL |
|---|---|
| Production US | https://opti-ls-api-us.optimove.net/api |
| Production EU | https://opti-ls-api-eu.optimove.net/api |
All examples below use {baseUrl}. Substitute the URL for your environment.
CORSRequests from certain origins are permitted by default. If your widget runs on a domain outside that set, raise a request with the Optimove team to have it added before starting development.
AuthAuthentication is not yet enforced on widget endpoints. No token is required at this time. Token-based auth will be added in a future release. When it ships, request headers and the widget initialization flow will change. This guide will be updated accordingly.
Prerequisites
You need two identifiers:
| Identifier | Type | Source |
|---|---|---|
tenantId | integer | Your Optimove tenant number |
playerId | string | Your internal player/customer identifier |
Data model overview
Brand (brandId)
|-- widget-theme
|-- player-profile
|-- player-level
| \-- rewards[].rewardId ----------+
|-- missions | look up in
| \-- rewards[].rewardTypeId ------+-> Rewards Catalog (by .id)
|-- player-rewards |
| \-- rewardTypeId ----------------+
|-- badges |
\-- leaderboards |
\-- prizes[].items[].rewardId ---+
brandId is the root key for every endpoint. Reward references in missions, level rewards, player-rewards, and leaderboard prizes all resolve back to the rewards catalog id.
Typical page load sequence
1. GET /api/brands/default/{tenantId} -> brandId
2. GET /api/widget-theme/brand/{brandId} -> colors/CSS
3. GET /api/player-profiles/brand/{brandId}/player/{playerId} -> name, avatar
4. GET /api/levels/brand/{brandId}/player/{playerId} -> XP bar
5. GET /api/missions/brand/{brandId}/player/{playerId} -> mission list
6. GET /api/rewards/brand/{brandId} -> rewards catalog
7. GET /api/player-rewards/brand/{brandId}/player/{playerId} -> balances
8. GET /api/player-rewards/brand/{brandId}/player/{playerId}/badges -> badges
9. GET /api/leaderboards/brand/{brandId} -> leaderboard list
Requests 2 through 9 can be made in parallel. Index the rewards catalog by id for O(1) lookups when rendering missions, level rewards, and leaderboard prizes:
const BASE_URL = 'https://opti-ls-api-us.optimove.net/api';
async function initWidget(tenantId: number, playerId: string) {
// Step 1: resolve brandId (cache this; it never changes per tenant)
const { id: brandId } = await fetch(`${BASE_URL}/brands/default/${tenantId}`).then((r) => r.json());
// Steps 2 to 9: all independent, fire in parallel
const [theme, profile, level, missions, catalog, rewards, badges, leaderboards] = await Promise.all([
fetch(`${BASE_URL}/widget-theme/brand/${brandId}`).then((r) => r.json()),
fetch(`${BASE_URL}/player-profiles/brand/${brandId}/player/${playerId}`).then((r) => r.json()),
fetch(`${BASE_URL}/levels/brand/${brandId}/player/${playerId}`).then((r) => r.json()),
fetch(`${BASE_URL}/missions/brand/${brandId}/player/${playerId}`).then((r) => r.json()),
fetch(`${BASE_URL}/rewards/brand/${brandId}`).then((r) => r.json()),
fetch(`${BASE_URL}/player-rewards/brand/${brandId}/player/${playerId}`).then((r) => r.json()),
fetch(`${BASE_URL}/player-rewards/brand/${brandId}/player/${playerId}/badges`).then((r) => r.json()),
fetch(`${BASE_URL}/leaderboards/brand/${brandId}`).then((r) => r.json()),
]);
// Build a lookup map for reward metadata. Used by missions, level rewards, and leaderboard prizes.
const rewardById = Object.fromEntries(catalog.map((r: { id: string }) => [r.id, r]));
return { brandId, theme, profile, level, missions, catalog, rewards, badges, leaderboards, rewardById };
}Step 1: Resolve your brandId (one-time)
All widget endpoints use brandId (UUID), not tenantId. Fetch it once at startup and cache it.
Reference: getDefaultBrand
GET /api/brands/default/{tenantId}curl {baseUrl}/brands/default/1234Response:
{
"id": "aebb6a6a-bdef-480f-8096-e13f9c3d4969",
"tenantId": 1234,
"name": "Acme Loyalty",
"isDefault": true,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z"
}| Field | Description |
|---|---|
id | Your brandId. Use this in all subsequent calls |
tenantId | Mirrors the path parameter. Confirms you got the right tenant |
name | Human-readable brand name |
isDefault | true if this is the default brand for the tenant (typically always true) |
Step 2: Load widget data
Fetch these on widget initialization. All take brandId and most take playerId.
Theme
Branding colors and CSS variables configured by the brand owner in the Optimove dashboard. Apply these to style your widget to match the brand's identity.
Reference: getWidgetTheme
GET /api/widget-theme/brand/{brandId}curl {baseUrl}/widget-theme/brand/aebb6a6a-bdef-480f-8096-e13f9c3d4969Response:
{
"themeColors": {
"primary": {
"50": "#f0f9ff",
"500": "#6366f1",
"900": "#1e1b4b"
},
"secondary": {
"500": "#f59e0b"
}
},
"globalCss": {
"border-radius": "8px",
"font-family": "Inter, sans-serif"
},
"customCss": {
".widget-header": "background: linear-gradient(135deg, #6366f1, #8b5cf6);"
},
"customVariables": {
"badge-size": "48px",
"card-shadow": "0 2px 8px rgba(0,0,0,0.12)"
}
}| Field | Description |
|---|---|
themeColors | Nested color palette. Keys are color names (e.g. primary), values are shade maps (50 to 900) |
globalCss | CSS property/value pairs to apply globally (e.g. border-radius, fonts) |
customCss | CSS selector/rule pairs for specific widget elements |
customVariables | Named values (e.g. sizes, shadows) for use in your CSS as variables or constants |
CSS safety
globalCssandcustomCssare raw values from the API. Before injecting them as live styles, allow-list the CSS properties and selectors your widget accepts to prevent unintended style injection.
Player profile
Display name and avatar for the logged-in player.
Reference: getPlayerProfile
GET /api/player-profiles/brand/{brandId}/player/{playerId}curl "{baseUrl}/player-profiles/brand/aebb6a6a-bdef-480f-8096-e13f9c3d4969/player/player-123"Response:
{
"id": "f4e3c2b1-a0d9-4e8f-b7c6-d5e4f3a2b1c0",
"name": "Jane Doe",
"icon": "https://cdn.example.com/avatars/jane.png"
}| Field | Description |
|---|---|
id | Internal UUID for this player profile (distinct from playerId) |
name | Display name shown in the widget header and leaderboards |
icon | URL to the player's avatar image. Render as-is or proxy through your CDN |
Player level
Current level, XP progress, and rewards for reaching the next level. Use this to render the XP progress bar and level badge.
Reference: getPlayerLevel
GET /api/levels/brand/{brandId}/player/{playerId}Response:
{
"currentLevel": 3,
"levelName": "Silver",
"levelIcon": "https://cdn.example.com/levels/silver.png",
"nextLevelName": "Gold",
"nextLevelIcon": "https://cdn.example.com/levels/gold.png",
"totalXp": 1500,
"xpForCurrentLevel": 1000,
"xpForNextLevel": 2000,
"xpProgressInCurrentLevel": 500,
"maxLevel": 10,
"isMaxLevel": false,
"currentLevelRewards": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"levelId": "b2c3d4e5-f6a7-8901-bcde-f01234567891",
"rewardId": "c3d4e5f6-a7b8-9012-cdef-012345678912",
"amount": 100,
"createdAt": "2025-01-01T00:00:00.000Z"
}
],
"nextLevelRewards": [
{
"id": "d4e5f6a7-b8c9-0123-defa-123456789013",
"levelId": "e5f6a7b8-c9d0-1234-efab-234567890134",
"rewardId": "c3d4e5f6-a7b8-9012-cdef-012345678912",
"amount": 250,
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}| Field | Description |
|---|---|
currentLevel | Current level number (1-based) |
levelName | Display name for the current level (e.g. "Silver", "Gold") |
levelIcon | URL to the current level badge icon. null if no icon configured |
nextLevelName | Display name for the next level. null if player is at max level |
nextLevelIcon | URL to the next level badge icon. null if no icon or at max level |
totalXp | Cumulative XP earned across all levels |
xpForCurrentLevel | Total XP threshold required to reach the current level |
xpForNextLevel | Total XP threshold required to reach the next level. null if at max level |
xpProgressInCurrentLevel | XP earned since entering the current level. Use this to draw the progress bar (progress / (xpForNextLevel - xpForCurrentLevel)) |
maxLevel | Highest level in the program |
isMaxLevel | true when the player has reached the top level. Hide "next level" UI when this is true |
currentLevelRewards | Rewards the player received for reaching the current level. Each item has a rewardId. Look it up in the rewards catalog by matching against id to get name and icon |
nextLevelRewards | Rewards the player will receive upon reaching the next level. Show as incentive. Same rewardId to catalog id lookup applies |
For all level tiers in the active config (useful for a full progress roadmap UI):
Reference: getAllPlayerLevels
GET /api/levels/brand/{brandId}/player/{playerId}/levelsReturns an array of the same shape above, one entry per level, plus:
| Field | Description |
|---|---|
levelConfigId | UUID of the level configuration this tier belongs to |
configName | Name of the level configuration |
isMain | true for the active/main config. Filter by this if needed |
Missions
Active and completed missions for the player. Each item pairs an objective (the task definition) with the player's mission progress record.
Reference: getPlayerMissions
Render filteringOnly render items where
objective.status === "active". The list may include objectives with statusdraft,archived, ordeletedthat should not be displayed to players.
GET /api/missions/brand/{brandId}/player/{playerId}Response (array of mission items):
[
{
"objective": {
"id": "obj-11111111-1111-1111-1111-111111111111",
"name": "Make 5 purchases",
"description": "Complete 5 transactions in any category",
"type": "task",
"goal": 5,
"icon": "https://cdn.example.com/missions/purchase.png",
"status": "active",
"isRepeatable": true,
"repeatLimit": 3,
"cooldownPeriodSeconds": 86400
},
"mission": {
"status": "active",
"progress": 3,
"objectiveId": "obj-11111111-1111-1111-1111-111111111111",
"playerId": "player-123",
"repeatCount": 0,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-05T00:00:00.000Z"
},
"rewards": [
{
"rewardTypeId": "c3d4e5f6-a7b8-9012-cdef-012345678912",
"amount": 100,
"repeatable": false
}
]
}
]objective fields:
| Field | Description |
|---|---|
id | UUID of the objective. Use as objectiveId in unlock/claim calls |
name | Title shown to the player |
description | Optional longer description. null if not set |
type | task (standalone) or mission (part of a mission group) |
goal | Target value the player must reach (e.g. 5 purchases) |
icon | URL to the mission icon. null if not configured. Before using in src/href, validate it is an https URL from a trusted host to prevent mixed-content or open-redirect issues |
status | Lifecycle of the objective definition: active, draft, archived, deleted |
isRepeatable | Whether the mission can be completed more than once |
repeatLimit | Maximum number of times the mission can be repeated. null = unlimited |
cooldownPeriodSeconds | Seconds the player must wait between repeats (e.g. 86400 = 24h). null = no cooldown |
unlockCurrencyId | UUID of the currency required to unlock this mission. null if no unlock cost |
unlockAmount | Amount of that currency required to unlock. null if no unlock cost |
mission fields (player's progress record):
| Field | Description |
|---|---|
status | Player's current state for this mission. Drive your UI from this value (see statuses below) |
progress | How far the player has gotten toward objective.goal (e.g. 3 out of 5 purchases) |
repeatCount | How many times the player has already completed and claimed this repeatable mission |
lastRepeated | ISO 8601 timestamp of the last completion for a repeatable mission. Use with cooldownPeriodSeconds to show next-available time. null if never repeated |
objectiveId | Links back to the parent objective |
version | Internal optimistic-lock counter. Not for display |
mission is null when the player has never started this objective. Render as "Not started" or inactive state. When mission is present, status is one of:
Mission statuses:
| Status | Meaning | UI action |
|---|---|---|
active | In progress; player is working toward the goal | Show progress bar |
completed | Goal reached, reward not yet claimed | Show "Claim" button |
claimed | Reward has been collected | Show completion state |
locked | Requires spending currency to unlock before progress can begin | Show "Unlock" button with cost |
failed | Mission expired or failed condition triggered | Show failed state |
rewards fields:
| Field | Description |
|---|---|
rewardTypeId | UUID of the reward type. Look up name and icon from the rewards catalog using this value |
amount | Quantity of the reward granted on completion |
repeatable | Whether the reward is granted on every repeat of a repeatable mission, or only on first claim |
To fetch a single mission by objective (e.g. after unlock or claim):
Reference: getPlayerMissionByObjective
GET /api/missions/brand/{brandId}/objective/{objectiveId}/player/{playerId}Response: a single mission item object, not wrapped in an array. Same shape as one entry above.
Rewards catalog
All reward types configured for the brand. Cross-reference rewardTypeId from player rewards against this list to get names and icons. The catalog changes infrequently. Cache it for the widget session lifetime and re-fetch on the next page load.
Reference: getRewardsCatalog
GET /api/rewards/brand/{brandId}Response (array):
[
{
"id": "c3d4e5f6-a7b8-9012-cdef-012345678912",
"name": "Gold Coin",
"description": "Premium in-app currency",
"type": "currency",
"icon": "https://cdn.example.com/rewards/coin.png",
"tag": "common",
"shortCode": null,
"tenantId": 1234,
"brandId": "aebb6a6a-bdef-480f-8096-e13f9c3d4969",
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z"
},
{
"id": "f7a8b9c0-d1e2-3456-7890-abcdef123456",
"name": "Free Shipping Voucher",
"description": "Custom reward fulfilled by an external system",
"type": "additional",
"icon": "https://cdn.example.com/rewards/shipping.png",
"tag": "rare",
"shortCode": "FREESHIP-2025",
"tenantId": 1234,
"brandId": "aebb6a6a-bdef-480f-8096-e13f9c3d4969",
"createdAt": "2025-03-15T00:00:00.000Z",
"updatedAt": "2025-03-15T00:00:00.000Z"
}
]| Field | Description |
|---|---|
id | UUID of this reward type. Matches rewardTypeId in player rewards and mission reward items |
name | Display name (e.g. "Gold Coin", "VIP Badge") |
description | Optional description of the reward. May be empty |
type | currency (spendable balance), progression (XP/level point), badge (collectible), or additional (custom reward identified by shortCode for downstream fulfillment) |
icon | URL to the reward icon. May be empty |
tag | Rarity tier: common, rare, epic, legendary. Use to apply visual styling |
shortCode | Optional short code. Populated for additional-type rewards and sent in the webhook payload when an additional reward is claimed. null for other reward types |
tenantId | Optimove tenant number |
brandId | Brand this reward type belongs to |
createdAt | ISO 8601 creation timestamp |
updatedAt | ISO 8601 last-updated timestamp |
Additional rewardsUse
type = "additional"for custom rewards that are fulfilled outside the loyalty system (for example, free shipping, a partner discount, a physical item). TheshortCodeis the identifier your fulfillment system uses to recognize the reward. Display them in your widget like any other reward; redemption happens via the webhook payload, not in the widget itself.
Player rewards
Aggregated totals per reward type for the player. Use rewardTypeId to look up names and icons from the rewards catalog.
Reference: getPlayerRewards
GET /api/player-rewards/brand/{brandId}/player/{playerId}Response (array):
[
{
"rewardTypeId": "c3d4e5f6-a7b8-9012-cdef-012345678912",
"totalAmount": 350
},
{
"rewardTypeId": "d4e5f6a7-b8c9-0123-defa-123456789013",
"totalAmount": 1200
}
]| Field | Description |
|---|---|
rewardTypeId | Matches id in the rewards catalog. Join to get name, icon, type |
totalAmount | Current balance of this reward type for the player |
Player badges (rewards with type = "badge"):
Reference: getPlayerBadges
GET /api/player-rewards/brand/{brandId}/player/{playerId}/badgesResponse (array):
[
{
"id": "e5f6a7b8-c9d0-1234-efab-234567890134",
"name": "First Purchase",
"description": "Awarded for completing your first transaction",
"type": "badge",
"icon": "https://cdn.example.com/badges/first-purchase.png",
"tag": "rare",
"shortCode": null,
"tenantId": 1234,
"brandId": "aebb6a6a-bdef-480f-8096-e13f9c3d4969"
}
]| Field | Description |
|---|---|
id | UUID of the badge (matches reward catalog id) |
name | Badge display name |
type | Always "badge" for this endpoint |
description | What the player did to earn this badge. May be empty |
icon | URL to the badge image |
tag | Rarity tier. Use to apply visual treatment |
shortCode | Present on the underlying reward record. Typically null for badges |
tenantId | Optimove tenant number |
brandId | Brand this badge belongs to |
Leaderboards
All leaderboards configured for the brand.
Reference: listLeaderboards
GET /api/leaderboards/brand/{brandId}Response (array):
[
{
"id": "lb-11111111-1111-1111-1111-111111111111",
"brandId": "aebb6a6a-bdef-480f-8096-e13f9c3d4969",
"name": "Weekly High Scores",
"description": "Top spenders this week",
"type": "leaderboard",
"status": "active",
"objectivesActivated": false,
"startDate": "2025-05-05T00:00:00.000Z",
"endDate": "2025-05-11T23:59:59.000Z",
"isRepeatable": true,
"resetEveryHours": 168,
"rewards": [
{
"id": "rw-22222222-2222-2222-2222-222222222222",
"leaderboardId": "lb-11111111-1111-1111-1111-111111111111",
"rankFrom": 1,
"rankTo": 3,
"items": [
{
"id": "rwi-33333333",
"leaderboardRewardId": "rw-22222222-2222-2222-2222-222222222222",
"rewardId": "c3d4e5f6-a7b8-9012-cdef-012345678912",
"amount": 500,
"rewardName": "Gold Coin",
"rewardType": "currency",
"rewardIcon": "https://cdn.example.com/rewards/coin.png",
"rewardTag": "common"
}
],
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z"
}
],
"objectives": [],
"participationCostCurrencyId": null,
"participationCostAmount": null,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z"
}
]| Field | Description |
|---|---|
id | UUID. Use as leaderboardId in statistics and enrollment calls |
name | Display name shown in the widget |
description | Optional longer description. null if not set |
type | leaderboard (auto-enrolled, rank by score) or tournament (opt-in, may have entry cost) |
status | See status table below |
objectivesActivated | true when the leaderboard score is driven by in-app objectives |
startDate | ISO 8601. When the current period started |
endDate | ISO 8601. When the current period ends |
isRepeatable | Whether the leaderboard resets and restarts automatically |
resetEveryHours | Reset interval in hours (e.g. 168 = weekly). null if not repeatable |
rewards | Prize tiers by rank range. Show these as the incentive to compete |
rewards[].rankFrom | Start of the rank range eligible for this prize tier (inclusive) |
rewards[].rankTo | End of the rank range (inclusive) |
rewards[].items | Actual reward items in this tier, with name, icon, and amount for display |
objectives | Objective IDs that drive the leaderboard score. Empty array when objectivesActivated is false |
participationCostCurrencyId | UUID of the currency required to enter (tournaments only). null if free |
participationCostAmount | Amount of that currency required to enter (tournaments only). null if free |
Leaderboard statuses:
| Status | Meaning | Widget behavior |
|---|---|---|
draft | Created, not yet published by the admin | Hide. Not visible to players |
active | Running. Players are scoring and enrolling | Show live rankings; allow tournament enrollment |
rewarding | Period ended. Backend is distributing prizes (transient, typically resolves within 5 to 20 minutes) | Show as read-only with final standings; label as "Ended" |
archived | Terminal state. Prizes distributed and leaderboard closed | Show as read-only with final standings so players can see their result and what they won |
Repeatable leaderboardsFor repeatable leaderboards, when a period ends the current instance moves to
rewarding, thenarchived, and a newactiveinstance is created automatically. The new active one will appear in your list naturally.
Player's enrollment and score for a specific leaderboard:
Reference: getPlayerLeaderboardEnrollment
GET /api/leaderboards/{brandId}/{leaderboardId}/player/{playerId}Response:
{
"leaderboardId": "lb-11111111-1111-1111-1111-111111111111",
"playerId": "player-123",
"totalAmount": 840,
"isEnrolled": true,
"enrolledAt": "2025-05-05T08:30:00.000Z",
"costCurrencyId": null,
"costAmount": null
}| Field | Description |
|---|---|
totalAmount | Player's current score on this leaderboard |
isEnrolled | Whether the player is participating. For regular leaderboards this is always true once active |
enrolledAt | When the player joined. null if not enrolled |
costCurrencyId | UUID of the currency spent to enter (tournaments only). null if free |
costAmount | Amount of currency spent to enter (tournaments only). null if free |
Tournament enrollment status only (no score):
Reference: getTournamentEnrollmentStatus
GET /api/leaderboards/{brandId}/{leaderboardId}/enrollment/{playerId}Returns the same shape as above minus totalAmount. Use this as a lightweight check when you only need to know whether the player is enrolled.
Leaderboard statistics
Player's current rank:
Reference: getPlayerLeaderboardRank
GET /api/leaderboard-statistics/brand/{brandId}/leaderboard/{leaderboardId}/player/{playerId}Response: null if the player has no recorded position, otherwise:
{
"playerId": "player-123",
"playerName": "Jane Doe",
"rank": 7,
"score": 840
}| Field | Description |
|---|---|
rank | Player's current position (1 = first place) |
score | Player's accumulated score on this leaderboard |
playerName | Display name at the time of last score update. null if not set |
Top N players (use to render the leaderboard podium or full table):
Reference: getLeaderboardTop
GET /api/leaderboard-statistics/brand/{brandId}/leaderboard/{leaderboardId}/top?limit=10limit is optional (range 1 to 100, default 100). Response (array ordered by score descending). The response has no rank field; derive rank from the array position (index + 1):
[
{ "playerId": "player-001", "playerName": "Alice", "score": 2400 },
{ "playerId": "player-002", "playerName": "Bob", "score": 1950 },
{ "playerId": "player-123", "playerName": "Jane Doe", "score": 840 }
]Players in a rank range (use for pagination or "show players around me"):
Reference: getLeaderboardRankRange
GET /api/leaderboard-statistics/brand/{brandId}/leaderboard/{leaderboardId}/rank-range?rankFrom=1&rankTo=20rankFrom and rankTo are required (minimum 1 each).
Response (array with rank included):
[
{ "playerId": "player-001", "playerName": "Alice", "rank": 1, "score": 2400 },
{ "playerId": "player-002", "playerName": "Bob", "rank": 2, "score": 1950 },
{ "playerId": "player-123", "playerName": "Jane Doe", "rank": 7, "score": 840 }
]Step 3: Handle player actions
Update player display name
Reference: updatePlayerName
PUT /api/player-profiles/brand/{brandId}/player/{playerId}/name
Content-Type: application/json
{ "name": "New Name" }| Field | Constraints |
|---|---|
name | 1 to 50 characters, required |
Returns 200 with empty body on success. Returns 409 if the name is already taken by another player in the same tenant.
Unlock a mission (spend currency)
Some missions are locked behind an unlock cost. Call this when the player taps "Unlock". It spends the required currency and activates the mission so progress can begin.
Reference: unlockMission
PATCH /api/missions/objective/{objectiveId}/player/{playerId}/unlockNo request body. Returns the updated mission record.
After a successful unlock, re-fetch GET /api/player-rewards/.../player/{playerId}. The unlock cost is deducted from the player's currency balance.
Claim a mission reward
Once a mission reaches completed status, the player must explicitly claim it to receive the reward. Show a "Claim" button when mission.status === "completed".
Reference: claimMissionReward
POST /api/missions/objective/{objectiveId}/player/{playerId}/claimNo request body. Returns 200 with empty body on success.
After a successful claim, re-fetch GET /api/player-rewards/.../player/{playerId}. The claimed reward is added to the player's balance.
Enroll in a tournament
Enrollment applies to tournament-type leaderboards only. Calling this on a regular leaderboard returns 400 "Only tournaments require enrollment".
Reference: enrollInTournament
POST /api/leaderboards/{brandId}/{leaderboardId}/enroll/{playerId}Response 201:
{
"leaderboardId": "lb-11111111-1111-1111-1111-111111111111",
"playerId": "player-123",
"isEnrolled": true,
"enrolledAt": "2025-05-11T10:00:00.000Z",
"costCurrencyId": "c3d4e5f6-a7b8-9012-cdef-012345678912",
"costAmount": 50
}| Field | Description |
|---|---|
isEnrolled | Confirms enrollment was successful |
enrolledAt | Timestamp of enrollment |
costCurrencyId | UUID of the currency that was deducted. null if free to enter |
costAmount | Amount deducted from the player's balance. null if free to enter |
If costAmount is non-null, re-fetch GET /api/player-rewards/.../player/{playerId} after enrollment. The entry fee is deducted from the player's currency balance.
Error responses
| Status | Meaning |
|---|---|
400 | Invalid request body or parameters |
404 | Resource not found (brand, player, mission, etc.) |
500 | Internal server error |
All error responses follow the standard NestJS shape:
{ "statusCode": 404, "message": "...", "error": "Not Found" }API reference
Every endpoint described here has a full field-level reference page in the API Reference section, including request and response schemas, query parameters, and a "Try It" panel for live calls.
Updated about 18 hours ago