TANITARDevelopers
Guides

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.

RouteScopeDoes
GET /v1/membership-tiersplayer-membership-tier:readEvery tier with the registry revision
POST /v1/membership-tiersplayer-membership-tier:authorCreate a tier in draft: levelKey, displayName
PATCH /v1/membership-tiers/:levelKeyplayer-membership-tier:authorChange displayName
POST /v1/membership-tiers/:levelKey/publishplayer-membership-tier:authordraft to published
POST /v1/membership-tiers/:levelKey/archiveplayer-membership-tier:authorpublished to archived; the default tier cannot be archived
POST /v1/membership-tiers/:levelKey/defaultplayer-membership-tier:authorMake 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.

RouteScopeDoes
GET /v1/player-memberships/:playerIdplayer-membership:grantThe player's current membership
POST /v1/player-memberships/grantplayer-membership:grantGrant or renew a tier
POST /v1/player-memberships/revokeplayer-membership:revokeRevoke 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.

FieldRule
playerIdThe player
levelKeyA published tier; otherwise 400 MEMBERSHIP_TIER_UNAVAILABLE
startsAtOptional ISO timestamp; defaults to the existing start or now
endsAtOptional ISO timestamp in the future; the membership expires then
sourceOptional label for where the grant came from; defaults to api-command
externalReferenceOptional, 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

StatuserrorCodeCause
400PLAYER_INPUT_INVALIDMissing or malformed If-Match, an unknown field, or endsAt in the past
400MEMBERSHIP_TIER_UNAVAILABLEThe tier is not published
404PLAYER_MEMBERSHIP_NOT_FOUNDRevoking a player who has no membership
409PLAYER_MEMBERSHIP_REVISION_CONFLICTIf-Match does not match the current revision; read again and retry
503PLAYER_TENANT_UNAVAILABLEYour 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.

RouteScopeDoes
GET /v1/nickname-pricingplayer-rename:readThe current price: currencyKey, amount, revision
PUT /v1/nickname-pricingplayer-rename:configureSet 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}'
  • currencyKey must be a currency you have published in the currency registry; otherwise 422 RENAME_CURRENCY_UNPUBLISHED.
  • amount is an integer from 0 to 1,000,000,000. 0 makes every rename free.
  • Until you set a price, GET /v1/nickname-pricing answers 409 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_UNAVAILABLE rather 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.

On this page