TANITARDevelopers
GuidesGamification

Progress, XP and levels

Read a player's level, XP, streak and mission progress, and replay retained history after publishing a mission.

Read a player's progress

RouteScopeRate class
GET /v1/gamification/players/:playerId/progressgamification-progress:readread

Set PLAYER_ID to your identifier for the player.

curl "$GAMIFICATION_API_URL/v1/gamification/players/$PLAYER_ID/progress" \
  -H "Authorization: Bearer $TANITAR_API_KEY"
{
  "playerId": "player-2231",
  "level": 4,
  "experience": 120,
  "totalExperience": 1320,
  "currentStreak": 3,
  "longestStreak": 9,
  "lastActivityDate": "2026-09-30",
  "curve": {
    "revision": 2,
    "earnedUnderRevision": 2,
    "source": "tenant",
    "maxLevel": 30,
    "atMaxLevel": false,
    "currentLevelXpRequired": 400,
    "nextLevelXpRequired": 550,
    "thresholds": [{ "level": 1, "xpRequired": 100 }]
  },
  "missions": [
    {
      "missionKey": "win-three-ranked",
      "currentCount": 2,
      "requiredCount": 3,
      "status": "active",
      "occurrence": 1,
      "periodStart": "2026-09-30T00:00:00.000Z",
      "completedAt": null
    }
  ]
}

A player you have never sent an event for reads as an empty progression (level 1, no XP, no missions); the read creates nothing. The read is always scoped to your tenant, so naming a player that belongs to another organizer returns your own empty record for that identifier, never theirs.

Fields

FieldMeaning
levelThe player's current level
experienceXP earned toward leaving the current level
totalExperienceAll XP the player has ever earned
currentStreak, longestStreakConsecutive UTC days with at least one mission completion, now and at best
lastActivityDateThe last UTC day that counted toward the streak
curve.revisionThe level-curve revision in effect now (0 is the default curve)
curve.earnedUnderRevisionThe revision under which the player's current level was reached. It differs from revision after you change the curve; that is expected, because curve changes apply forward only.
curve.sourcepinned (default curve) or tenant (your curve)
curve.currentLevelXpRequiredThe XP needed to leave the current level; null at the top
curve.nextLevelXpRequiredThe price of the level after that: a preview. It is already null one level below the top, so do not read it as "maximum level reached".
curve.atMaxLeveltrue only when the player stands on the highest level the curve reaches
curve.maxLevelThe highest reachable level, or null for the unbounded default curve
curve.thresholdsThe curve's table; for the default curve, its first 100 levels
missions[]The player's progress rows: count, target, status (active, completed, expired), the occurrence number, the UTC period start (or null for missions without a period) and completion time

XP and levels

XP reaches a player from three places: an xp mission reward, an XP drop from a chest, and an xp reward on a battle-pass tier. Each grant adds to experience and totalExperience; when experience passes the current level's price the player levels up, possibly by several levels at once. Each grant publishes gamification.xp.granted with the amount, the new total, the previous and new level and the levels gained.

You set the prices with the level curve.

Streaks

A player's streak advances on the first mission completion of each UTC day. Completing a mission on the next UTC day extends the streak; a gap of two days or more restarts it at 1 on the next completion. There is no nightly reset: a lapsed streak is noticed when the player next completes a mission. Each advance publishes gamification.streak.advanced.

Reaching a streak of 3, 7, 14 or 30 days is a milestone, recorded once per player. Each milestone pays a chest: common at 3 days, rare at 7, epic at 14 and legendary at 30.

Replay a player's history

When you publish a mission after the activity it counts has already happened, replay lets that activity count.

RouteScopeIdempotency
POST /v1/gamification/replaygamification-progress:replayIdempotency-Key required
curl -X POST "$GAMIFICATION_API_URL/v1/gamification/replay" -d '{ "playerId": "player-2231" }' \
  -H "Authorization: Bearer $TANITAR_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json"
{ "playerId": "player-2231", "replayed": 500, "completed": 2, "skipped": 0, "nextOffset": 500 }

Replay re-evaluates the player's events retained from the last 90 days, oldest first, through the same path as live ingestion, and can complete missions and pay their rewards.

  • It is safe to repeat. An event that already counted toward a mission never counts again, so re-running a replay over the same history completes nothing and pays nothing twice.
  • It is bounded. One call processes at most 500 events. When nextOffset is not null, call again with "offset": <nextOffset> under a new Idempotency-Key to continue.
  • skipped counts events that are not replayable: built-in events, which are consequences of work Tanitar already did.
  • Tier-restricted missions are not backfilled. A mission with allowedLevelKeys is never advanced by replay, because the tier a player held when the event happened cannot be reconstructed.

Replay is also the scope that authorizes granting a chest to a player, since both change a player's state directly.

On this page