Membership tiers and rename pricing
Define your own membership tiers, grant and revoke them per player, and set the price a player pays to change their nickname.
Membership tiers let you mirror your own plans inside Tanitar: free and paid levels, a VIP
programme, a season pass bought in your store. You define the tiers, you decide which player holds
which tier, and features such as missions can be restricted to chosen tiers. Tiers and memberships
live in the identity service (IDENTITY_API_URL).
Tiers
Each tier has a levelKey (lowercase slug, up to 128 characters, never changes), a displayName,
a status and a revision. Tiers follow the same lifecycle as currencies: draft to published
to archived.
Exactly one published tier is the default: the tier every player without an active membership
belongs to. Your tenant starts with a published default tier freemium ("Freemium"), created the
first time the registry is read.
| Route | Scope | Does |
|---|---|---|
GET /v1/membership-tiers | player-membership-tier:read | Every tier with the registry revision |
POST /v1/membership-tiers | player-membership-tier:author | Create a tier in draft: levelKey, displayName |
PATCH /v1/membership-tiers/:levelKey | player-membership-tier:author | Change displayName |
POST /v1/membership-tiers/:levelKey/publish | player-membership-tier:author | draft to published |
POST /v1/membership-tiers/:levelKey/archive | player-membership-tier:author | published to archived; the default tier cannot be archived |
POST /v1/membership-tiers/:levelKey/default | player-membership-tier:author | Make a published tier the default |
curl -X POST "$IDENTITY_API_URL/v1/membership-tiers" \
-H "Authorization: Bearer $TANITAR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"levelKey":"gold","displayName":"Gold"}'
curl -X POST "$IDENTITY_API_URL/v1/membership-tiers/gold/publish" \
-H "Authorization: Bearer $TANITAR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"A request body with a field that is not listed is refused with 400 PLAYER_INPUT_INVALID. A
duplicate key, an invalid transition, or a concurrent change is refused with
409 MEMBERSHIP_TIER_CONFLICT; an unknown key with 404 MEMBERSHIP_TIER_NOT_FOUND. The registry
list is not paginated.
Player memberships
A player holds at most one membership. It has a levelKey, a status (active, expired,
revoked, or inactive for a player who never had one), optional startsAt and endsAt, a
source, an optional externalReference, and a revision.
| Route | Scope | Does |
|---|---|---|
GET /v1/player-memberships/:playerId | player-membership:grant | The player's current membership |
POST /v1/player-memberships/grant | player-membership:grant | Grant or renew a tier |
POST /v1/player-memberships/revoke | player-membership:revoke | Revoke the membership |
These three routes accept only an API key. Reading a membership uses the player-membership:grant
scope, because every grant needs the current revision first.
Grant or renew
Both writes are compare-and-set: send the membership's current revision in If-Match. A player
who never had a membership reads with revision: 0, so their first grant sends If-Match: 0.
| Field | Rule |
|---|---|
playerId | The player |
levelKey | A published tier; otherwise 400 MEMBERSHIP_TIER_UNAVAILABLE |
startsAt | Optional ISO timestamp; defaults to the existing start or now |
endsAt | Optional ISO timestamp in the future; the membership expires then |
source | Optional label for where the grant came from; defaults to api-command |
externalReference | Optional, up to 256 characters, such as your order or subscription id |
curl -X POST "$IDENTITY_API_URL/v1/player-memberships/grant" \
-H "Authorization: Bearer $TANITAR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "If-Match: 0" \
-H "Content-Type: application/json" \
-d '{"playerId":"player-7f3a","levelKey":"gold","endsAt":"2026-12-31T23:59:59Z","source":"store","externalReference":"order-88412"}'The response is { "membership": { … }, "reason": "granted" }, or "renewed" when the player
already had a membership. Granting again replaces the tier and dates of the existing membership. When
endsAt passes, the membership reads as expired and the player falls back to the default tier.
Revoke
POST /v1/player-memberships/revoke with { "playerId": "…" } and the current revision in
If-Match. The membership becomes revoked and the player falls back to the default tier. A player
with no membership answers 404 PLAYER_MEMBERSHIP_NOT_FOUND.
Refusals
| Status | errorCode | Cause |
|---|---|---|
400 | PLAYER_INPUT_INVALID | Missing or malformed If-Match, an unknown field, or endsAt in the past |
400 | MEMBERSHIP_TIER_UNAVAILABLE | The tier is not published |
404 | PLAYER_MEMBERSHIP_NOT_FOUND | Revoking a player who has no membership |
409 | PLAYER_MEMBERSHIP_REVISION_CONFLICT | If-Match does not match the current revision; read again and retry |
503 | PLAYER_TENANT_UNAVAILABLE | Your tenant's storage could not be reached; retry later |
Every grant, renewal, revocation and expiry publishes player.membership.changed, which your webhook
endpoint can receive (see the event catalog). Players read their own tier
through the Player surface, GET /player/v1/membership.
Rename pricing
Players can change their nickname. The first change of the nickname Tanitar assigned them is free; every later change costs a price you set, in one of your currencies, debited from the player's wallet through the rewards ledger. You configure that price here.
| Route | Scope | Does |
|---|---|---|
GET /v1/nickname-pricing | player-rename:read | The current price: currencyKey, amount, revision |
PUT /v1/nickname-pricing | player-rename:configure | Set the price |
curl -X PUT "$IDENTITY_API_URL/v1/nickname-pricing" \
-H "Authorization: Bearer $TANITAR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"currencyKey":"red","amount":250}'currencyKeymust be a currency you have published in the currency registry; otherwise422 RENAME_CURRENCY_UNPUBLISHED.amountis an integer from 0 to 1,000,000,000.0makes every rename free.- Until you set a price,
GET /v1/nickname-pricinganswers409 RENAME_PRICE_UNCONFIGURED, and players can make only their free first rename. - If the currency registry cannot be reached, the write answers
503 RENAME_PRICING_UNAVAILABLErather than guessing.
When a player renames, the charge is taken before the name changes. A player who cannot afford it gets a typed refusal and nothing is debited; a rename that fails after the charge is refunded automatically through the ledger.