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
rejectedwith 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 code | Cause |
|---|---|
GRANT_CURRENCY_UNAVAILABLE | A currency in the grant is not published in your registry |
GRANT_PROVENANCE_UNRECOGNIZED | The request did not state a recognised source |
GRANT_REQUEST_INVALID | The 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:
| Source | provenance | How you set it up |
|---|---|---|
| Mission completion | gamification-mission | A mission whose reward is a currency, see Gamification catalog |
| Battle-pass tier claim | gamification-battle-pass | A season tier that pays currency, see Seasons and chests |
| Chest opening | gamification-chest | A drop table entry that pays currency |
| Tournament award | tournament-award | An award definition with a value, bound to a tournament and rank |
| Award you grant | operator-grant | POST /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 parameter | Rule |
|---|---|
status | Optional: applied or rejected |
limit | 1 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.
Wallets and the ledger
Read a player's balances and post value to their wallet directly, with every change recorded as one balanced ledger entry.
Award kinds and awards
The six system award kinds, your own award-kind registry, award definitions, and how awards are granted, read, resolved and optionally minted on a testnet.