Seasons and chests
Author battle-pass seasons with free and premium tiers, and design the chests players earn and open: drop tables, pity, currency and direct grants.
Battle-pass seasons
A season is a time-boxed ladder of tiers. Players earn battle-pass XP while the season is live;
each tier has an XP threshold and up to two rewards, one on the free track and one on the
premium track. Players claim the tiers they reach from the hosted experience.
| Route | Scope | What it does |
|---|---|---|
GET /v1/gamification/battle-pass/seasons | gamification-catalog:author | List your seasons, newest seasonNumber first (no pagination) |
POST /v1/gamification/battle-pass/seasons | gamification-catalog:author | Create a season, or replace the one with the same seasonKey |
PATCH /v1/gamification/battle-pass/seasons/:seasonKey | gamification-catalog:author | Replace an existing season; send the full definition. An unknown key is 404 BATTLE_PASS_SEASON_NOT_FOUND. |
POST /v1/gamification/battle-pass/seasons/:seasonKey/activate | gamification-catalog:author | Enable this season and disable every other one |
curl -X POST "$GAMIFICATION_API_URL/v1/gamification/battle-pass/seasons" \
-H "Authorization: Bearer $TANITAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"seasonKey": "season-4",
"name": "Season 4",
"seasonNumber": 4,
"startsAt": "2026-10-01T00:00:00Z",
"endsAt": "2026-12-31T23:59:59Z",
"tiers": [
{ "tier": 1, "xpRequired": 0, "freeReward": { "rewardType": "xp", "amount": 50 } },
{ "tier": 2, "xpRequired": 300,
"freeReward": { "rewardType": "chest", "key": "common" },
"premiumReward": { "rewardType": "currency", "amount": 100, "key": "gems" } },
{ "tier": 3, "xpRequired": 800, "premiumReward": { "rewardType": "badge", "key": "season-4-elite" } }
],
"eventExperience": [{ "eventKey": "match:won", "amount": 20 }]
}'| Field | Rules |
|---|---|
seasonKey | Lowercase letters, digits and hyphens |
seasonNumber | A positive integer. If more than one enabled season covers the current time, the highest number is live. |
startsAt, endsAt | ISO 8601; endsAt must be after startsAt. The season is live only between them. |
tiers[] | At least one. Numbered 1 to n with no gaps, xpRequired never decreasing from one tier to the next. |
freeReward, premiumReward | Optional, or null. rewardType is xp (needs amount), currency (needs amount and a published currency key), chest (tier in key) or badge (badge key). |
eventExperience[] | Optional. Flat battle-pass XP awarded each time a player sends this event. Each event must be published or built in, and listed once. |
A new season is stored disabled. Enabling it with activate disables every other season, so you
always have at most one enabled season.
A rule breach is a 422 (GAMIFICATION_CADENCE_INVALID for ordering and window,
GAMIFICATION_FIELD_INVALID for a reward, GAMIFICATION_EVENT_NOT_ADMISSIBLE for an unpublished
event, GAMIFICATION_REGISTRY_REFERENCE_INVALID for an unknown currency).
An enabled season is locked
Once a season is enabled, players hold tier progress and claims against its tiers, so the terms cannot change under them. While a season is enabled, a write to it may only:
- change
nameanddescription, and - move
endsAtlater.
Any change to seasonNumber, startsAt, tiers or eventExperience, or an earlier endsAt, is
refused with 409 BATTLE_PASS_SEASON_ACTIVE_IMMUTABLE, naming the field. The refusal is decided
before any registry is read. To change a live season's terms, create its successor and enable
that one.
How players earn battle-pass XP
All battle-pass XP goes through one funnel toward the live season:
- a mission reward of type
battlePassXP; - a battle-pass XP drop from a chest;
- the season's
eventExperienceamount for an event you send, once per event.
A player's tier only moves up. Each award publishes gamification.battle-pass.progressed with the
amount and the player's tier. With no
live season, battle-pass XP is not stored: the mission reward slot is recorded as rejected and the
mission still completes.
Tier claims
Players claim a reached tier on one track from the hosted experience; there is no API-key route to
claim for them. A claim is refused if there is no live season, the tier does not exist or has no
reward on that track, the player has not reached it, it is already claimed, or the premium track
is claimed by a player whose membership tier does not unlock it (BATTLE_PASS_PREMIUM_REQUIRED).
Which tiers unlock the premium track is your
membership-tier setting.
What happens to the reward depends on its type:
| Reward | Settles |
|---|---|
xp, chest | Immediately, in the claim itself |
currency | Through a Rewards grant. The claim stays pending until the grant settles. If Rewards rejects the grant, the tier is released and the player can claim it again. If no answer arrives before the grant timeout, the tier stays held, because the credit might still land. |
badge | Through a Rewards award |
Each claim publishes gamification.battle-pass.tier-claimed. See Grants.
Chests
A chest is an item in a player's inventory with a common, rare, epic or legendary tier. It
can also use one of your own chest types. Players get chests from:
- a mission reward of type
chest; - a battle-pass tier reward of type
chest; - streak milestones (3, 7, 14 and 30 days);
- a direct grant from your integration (below).
Each source creates the chest once, however often it is retried. Players open their chests from the
hosted experience. Opening is all-or-nothing: the chest is marked opened, the drops are rolled and
paid, and history is written in one transaction, or the chest stays unopened. Opening a chest also
counts as the built-in event gamification:chest-opened, which missions can react to, and
publishes gamification.chest.opened.
Drops and pity
Each open rolls the chest's guaranteed drops plus a chance of a bonus drop. Each drop rolls a
rarity, then a reward type (currency, xp, battlePassXP or badge), then a value from the
chest tier's range scaled by the rarity multiplier.
| Chest tier | Guaranteed drops | Bonus drop chance |
|---|---|---|
common | 1 | 5% |
rare | 1 | 10% |
epic | 2 | 15% |
legendary | 3 | 25% |
Pity is a bad-luck floor. Tanitar counts each player's opens since their last epic and last
legendary drop. When an open reaches its threshold, 10 opens for epic and 40 for legendary by
default, that open's first drop is raised to at least that rarity. A drop of that
rarity or better resets the counter. Players can see their pity meter in the hosted experience.
XP and battle-pass XP drops are paid inside the open. A currency drop is sent to Rewards as a grant; a badge drop as an award.
Chest economy
The chest economy is your version of the drop tables, pity thresholds, reward pools and custom chest
types. Until you author one, the default economy applies (revision 0). Like the level curve, each
change appends a revision, and every opening records the revision it was rolled under.
| Route | Scope | What it does |
|---|---|---|
GET /v1/gamification/chests/economy | gamification-catalog:author | The economy in effect |
GET /v1/gamification/chests/economy/:revision | gamification-catalog:author | A past revision; 0 is the default |
PUT /v1/gamification/chests/economy | gamification-catalog:author | Replace the economy |
curl -X PUT "$GAMIFICATION_API_URL/v1/gamification/chests/economy" \
-H "Authorization: Bearer $TANITAR_API_KEY" \
-H "If-Match: \"1\"" \
-H "Content-Type: application/json" \
-d '{
"pity": { "epicThreshold": 8, "legendaryThreshold": 30 },
"pools": {
"currency": [{ "key": "gems", "weight": 3 }, { "key": "coins", "weight": 1 }],
"badge": [{ "key": "lucky-opener", "weight": 1 }]
},
"definitions": [
{ "chestKey": "halloween", "displayName": "Halloween chest", "baseTier": "rare",
"tables": { "dropCounts": { "rare": { "guaranteedDrops": 2, "bonusDropChance": 0.2 } } } }
]
}'| Field | What it sets |
|---|---|
pity | epicThreshold and legendaryThreshold, each 1 to 10,000; the legendary threshold must be greater than the epic one |
tables | Overrides of rarityWeights, rewardTypeWeights, baseValues, rarityMultipliers and dropCounts, keyed by chest tier or rarity |
pools | Weighted currency and badge keys (up to 50 each) a drop picks from. Every key must be published in Rewards. |
definitions[] | Up to 50 custom chest types, each with a chestKey, a displayName (60 characters at most), a baseTier, and its own optional tables and pools |
Every rule is checked before any registry is read, and a breach is one
422 GAMIFICATION_CHEST_CONFIG_INVALID naming the field: an inverted range, a tier left with
nothing it can roll, an unknown key. Once you have stored an economy, If-Match must carry its
current revision (400 GAMIFICATION_PRECONDITION_REQUIRED when missing,
409 GAMIFICATION_CHEST_ECONOMY_REVISION_MISMATCH when stale).
Chest currency
| Route | Scope | What it does |
|---|---|---|
GET /v1/gamification/chests/settings | gamification-catalog:author | Read the chest currency |
PUT /v1/gamification/chests/settings | gamification-catalog:author | Set it: { "currencyKey": "gems" }, or null to clear |
The chest currency is the currency a currency drop pays when no currency pool picks one. It must be a currency you have published. With no chest currency and no currency pool, chests drop no currency at all. Clearing it is refused if that would leave a tier of your economy with nothing to roll.
Grant a chest
Your integration can hand a chest to a player directly, for example as a support gesture or a prize.
| Route | Scope | Idempotency |
|---|---|---|
POST /v1/gamification/chests/grants | gamification-progress:replay | Idempotency-Key required |
curl -X POST "$GAMIFICATION_API_URL/v1/gamification/chests/grants" -d '{ "playerId": "player-2231", "chestType": "rare", "chestKey": "halloween" }' \
-H "Authorization: Bearer $TANITAR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json"{ "inventoryId": "…", "created": true, "chestType": "rare", "chestKey": "halloween" }chestTypeis the tier the chest opens as.chestKeyis optional and must name a custom chest type in your stored economy (422 GAMIFICATION_CHEST_CONFIG_INVALIDotherwise).- The
Idempotency-Keyis the grant's identity: resending the same key never creates a second chest. It answers with the sameinventoryId(and"created": falseonce the request-level idempotency record has expired). - The grant records which principal made it.