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

EnvironmentBase URL
Production UShttps://opti-ls-api-us.optimove.net/api
Production EUhttps://opti-ls-api-eu.optimove.net/api

All examples below use {baseUrl}. Substitute the URL for your environment.

⚠️

CORS

Requests 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.

📘

Auth

Authentication 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:

IdentifierTypeSource
tenantIdintegerYour Optimove tenant number
playerIdstringYour 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/1234

Response:

{
  "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"
}
FieldDescription
idYour brandId. Use this in all subsequent calls
tenantIdMirrors the path parameter. Confirms you got the right tenant
nameHuman-readable brand name
isDefaulttrue 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-e13f9c3d4969

Response:

{
  "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)"
  }
}
FieldDescription
themeColorsNested color palette. Keys are color names (e.g. primary), values are shade maps (50 to 900)
globalCssCSS property/value pairs to apply globally (e.g. border-radius, fonts)
customCssCSS selector/rule pairs for specific widget elements
customVariablesNamed values (e.g. sizes, shadows) for use in your CSS as variables or constants
⚠️

CSS safety

globalCss and customCss are 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"
}
FieldDescription
idInternal UUID for this player profile (distinct from playerId)
nameDisplay name shown in the widget header and leaderboards
iconURL 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"
    }
  ]
}
FieldDescription
currentLevelCurrent level number (1-based)
levelNameDisplay name for the current level (e.g. "Silver", "Gold")
levelIconURL to the current level badge icon. null if no icon configured
nextLevelNameDisplay name for the next level. null if player is at max level
nextLevelIconURL to the next level badge icon. null if no icon or at max level
totalXpCumulative XP earned across all levels
xpForCurrentLevelTotal XP threshold required to reach the current level
xpForNextLevelTotal XP threshold required to reach the next level. null if at max level
xpProgressInCurrentLevelXP earned since entering the current level. Use this to draw the progress bar (progress / (xpForNextLevel - xpForCurrentLevel))
maxLevelHighest level in the program
isMaxLeveltrue when the player has reached the top level. Hide "next level" UI when this is true
currentLevelRewardsRewards 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
nextLevelRewardsRewards 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}/levels

Returns an array of the same shape above, one entry per level, plus:

FieldDescription
levelConfigIdUUID of the level configuration this tier belongs to
configNameName of the level configuration
isMaintrue 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 filtering

Only render items where objective.status === "active". The list may include objectives with status draft, archived, or deleted that 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:

FieldDescription
idUUID of the objective. Use as objectiveId in unlock/claim calls
nameTitle shown to the player
descriptionOptional longer description. null if not set
typetask (standalone) or mission (part of a mission group)
goalTarget value the player must reach (e.g. 5 purchases)
iconURL 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
statusLifecycle of the objective definition: active, draft, archived, deleted
isRepeatableWhether the mission can be completed more than once
repeatLimitMaximum number of times the mission can be repeated. null = unlimited
cooldownPeriodSecondsSeconds the player must wait between repeats (e.g. 86400 = 24h). null = no cooldown
unlockCurrencyIdUUID of the currency required to unlock this mission. null if no unlock cost
unlockAmountAmount of that currency required to unlock. null if no unlock cost

mission fields (player's progress record):

FieldDescription
statusPlayer's current state for this mission. Drive your UI from this value (see statuses below)
progressHow far the player has gotten toward objective.goal (e.g. 3 out of 5 purchases)
repeatCountHow many times the player has already completed and claimed this repeatable mission
lastRepeatedISO 8601 timestamp of the last completion for a repeatable mission. Use with cooldownPeriodSeconds to show next-available time. null if never repeated
objectiveIdLinks back to the parent objective
versionInternal 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:

StatusMeaningUI action
activeIn progress; player is working toward the goalShow progress bar
completedGoal reached, reward not yet claimedShow "Claim" button
claimedReward has been collectedShow completion state
lockedRequires spending currency to unlock before progress can beginShow "Unlock" button with cost
failedMission expired or failed condition triggeredShow failed state

rewards fields:

FieldDescription
rewardTypeIdUUID of the reward type. Look up name and icon from the rewards catalog using this value
amountQuantity of the reward granted on completion
repeatableWhether 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"
  }
]
FieldDescription
idUUID of this reward type. Matches rewardTypeId in player rewards and mission reward items
nameDisplay name (e.g. "Gold Coin", "VIP Badge")
descriptionOptional description of the reward. May be empty
typecurrency (spendable balance), progression (XP/level point), badge (collectible), or additional (custom reward identified by shortCode for downstream fulfillment)
iconURL to the reward icon. May be empty
tagRarity tier: common, rare, epic, legendary. Use to apply visual styling
shortCodeOptional short code. Populated for additional-type rewards and sent in the webhook payload when an additional reward is claimed. null for other reward types
tenantIdOptimove tenant number
brandIdBrand this reward type belongs to
createdAtISO 8601 creation timestamp
updatedAtISO 8601 last-updated timestamp
📘

Additional rewards

Use type = "additional" for custom rewards that are fulfilled outside the loyalty system (for example, free shipping, a partner discount, a physical item). The shortCode is 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
  }
]
FieldDescription
rewardTypeIdMatches id in the rewards catalog. Join to get name, icon, type
totalAmountCurrent balance of this reward type for the player

Player badges (rewards with type = "badge"):

Reference: getPlayerBadges

GET /api/player-rewards/brand/{brandId}/player/{playerId}/badges

Response (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"
  }
]
FieldDescription
idUUID of the badge (matches reward catalog id)
nameBadge display name
typeAlways "badge" for this endpoint
descriptionWhat the player did to earn this badge. May be empty
iconURL to the badge image
tagRarity tier. Use to apply visual treatment
shortCodePresent on the underlying reward record. Typically null for badges
tenantIdOptimove tenant number
brandIdBrand 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"
  }
]
FieldDescription
idUUID. Use as leaderboardId in statistics and enrollment calls
nameDisplay name shown in the widget
descriptionOptional longer description. null if not set
typeleaderboard (auto-enrolled, rank by score) or tournament (opt-in, may have entry cost)
statusSee status table below
objectivesActivatedtrue when the leaderboard score is driven by in-app objectives
startDateISO 8601. When the current period started
endDateISO 8601. When the current period ends
isRepeatableWhether the leaderboard resets and restarts automatically
resetEveryHoursReset interval in hours (e.g. 168 = weekly). null if not repeatable
rewardsPrize tiers by rank range. Show these as the incentive to compete
rewards[].rankFromStart of the rank range eligible for this prize tier (inclusive)
rewards[].rankToEnd of the rank range (inclusive)
rewards[].itemsActual reward items in this tier, with name, icon, and amount for display
objectivesObjective IDs that drive the leaderboard score. Empty array when objectivesActivated is false
participationCostCurrencyIdUUID of the currency required to enter (tournaments only). null if free
participationCostAmountAmount of that currency required to enter (tournaments only). null if free

Leaderboard statuses:

StatusMeaningWidget behavior
draftCreated, not yet published by the adminHide. Not visible to players
activeRunning. Players are scoring and enrollingShow live rankings; allow tournament enrollment
rewardingPeriod ended. Backend is distributing prizes (transient, typically resolves within 5 to 20 minutes)Show as read-only with final standings; label as "Ended"
archivedTerminal state. Prizes distributed and leaderboard closedShow as read-only with final standings so players can see their result and what they won
📘

Repeatable leaderboards

For repeatable leaderboards, when a period ends the current instance moves to rewarding, then archived, and a new active instance 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
}
FieldDescription
totalAmountPlayer's current score on this leaderboard
isEnrolledWhether the player is participating. For regular leaderboards this is always true once active
enrolledAtWhen the player joined. null if not enrolled
costCurrencyIdUUID of the currency spent to enter (tournaments only). null if free
costAmountAmount 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
}
FieldDescription
rankPlayer's current position (1 = first place)
scorePlayer's accumulated score on this leaderboard
playerNameDisplay 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=10

limit 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=20

rankFrom 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" }
FieldConstraints
name1 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}/unlock

No 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}/claim

No 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
}
FieldDescription
isEnrolledConfirms enrollment was successful
enrolledAtTimestamp of enrollment
costCurrencyIdUUID of the currency that was deducted. null if free to enter
costAmountAmount 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

StatusMeaning
400Invalid request body or parameters
404Resource not found (brand, player, mission, etc.)
500Internal 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.