TANITARDevelopers
GuidesRewards

Grants

How earned value reaches a player's wallet exactly once, why a rejected grant is a settled outcome, and how to read a player's grants.

A grant is the record of value delivered to one player: which currencies, how much, into which wallet, and why. Grants are how missions, chests, battle-pass tiers and awards pay out. Each grant has a stable grantId chosen by whatever produced it, and it settles once.

How a grant settles

sequenceDiagram
  participant Source as Mission, chest, tier or award
  participant Spine as Grant spine
  participant Ledger as Wallet ledger
  participant Hooks as Your webhook endpoint
  Source->>Spine: grant request (grantId, player, amounts, provenance)
  alt admissible
    Spine->>Ledger: one balanced posting
    Spine->>Spine: record grant as applied
  else not admissible
    Spine->>Spine: record grant as rejected (code, reason)
  end
  Spine-->>Hooks: reward.grant-applied (outcome applied or rejected)

The ledger posting, the grant record and the outcome event commit together in one transaction. Three rules follow:

  • Once per grantId. A repeated request for the same grant (a redelivered message, a retried mission completion) reaches the grant that already exists. The wallet moves once.
  • A rejection is an answer, not a failure. A request that can never succeed settles as rejected with a code and a reason, and publishes its outcome like an applied grant does. It is not retried and not dropped, so whatever requested it (a battle-pass tier claim, for example) can release what it was holding.
  • No pending state. A grant that has not settled has no record. If a grant is not in the list, it has not settled yet.
Rejection codeCause
GRANT_CURRENCY_UNAVAILABLEA currency in the grant is not published in your registry
GRANT_PROVENANCE_UNRECOGNIZEDThe request did not state a recognised source
GRANT_REQUEST_INVALIDThe request does not satisfy its published contract

A grant made on behalf of an award can also carry the award's own rejection code.

Where grants come from

There is no API route that creates a grant directly. Grants are produced by the features that decide a player has earned something:

SourceprovenanceHow you set it up
Mission completiongamification-missionA mission whose reward is a currency, see Gamification catalog
Battle-pass tier claimgamification-battle-passA season tier that pays currency, see Seasons and chests
Chest openinggamification-chestA drop table entry that pays currency
Tournament awardtournament-awardAn award definition with a value, bound to a tournament and rank
Award you grantoperator-grantPOST /v1/awards/grants for a definition with a value, see Award kinds and awards

The full provenance vocabulary also contains gamification-leaderboard, player-action, shop-purchase and unknown. A provenance tells you, on every grant and ledger entry, which part of your programme produced the value.

To credit or debit a wallet from your own system without a grant, use a direct wallet posting.

Read a player's grants

GET /v1/reward-grants/:playerId with scope rewards:read.

Query parameterRule
statusOptional: applied or rejected
limit1 to 100, default 50

The list is ordered by settledAt, newest first, and returns at most limit grants. It takes no cursor. An invalid status or limit is refused with 400 REWARD_GRANT_QUERY_INVALID.

curl "$REWARD_API_URL/v1/reward-grants/player-7f3a?status=rejected&limit=20" \
  -H "Authorization: Bearer $TANITAR_API_KEY"
[
  {
    "grantId": "mission:daily-3-wins:player-7f3a:2026-09-30",
    "playerId": "player-7f3a",
    "walletScope": "platform",
    "gameSlug": null,
    "awards": [{ "currencyKey": "gems", "amount": 50 }],
    "provenance": "gamification-mission",
    "reason": "Mission daily-3-wins completed",
    "status": "rejected",
    "transferId": null,
    "rejection": { "code": "GRANT_CURRENCY_UNAVAILABLE", "reason": "Published currency gems is unavailable" },
    "settledAt": "2026-09-30T09:12:44.501Z"
  }
]

An applied grant has a transferId (the ledger transfer it produced) and rejection: null. A rejected grant has transferId: null and a rejection. A player with no settled grants returns [], and another tenant's player reads exactly the same way.

Players read their own grants with GET /player/v1/reward-grants.

Receive grant outcomes

Subscribe your webhook endpoint to reward.grant-applied. It is published for both outcomes: its data.outcome is applied or rejected, with the same fields as the read above. See the event catalog.

On this page