Shop
Sell items to your players for your own currencies, with server-side pricing, stock, per-player limits, fulfilment and reversal.
The shop lets your players spend the currencies you run in Tanitar on items you publish. It lives in Reward Management, so every purchase debits the same ledger your grants credit, and every order is tenant-scoped: an order, a listing or a code never crosses from one organizer to another.
You manage the shop from your back-end with an API key (this guide), or from the dashboard (see Shop in the dashboard). Your players buy through the player plane, usually in your own Player client using a short-lived Player token.
The model
| Record | What it is |
|---|---|
| Listing | One sellable item, identified by a listingKey you choose. It has a title, a price in exactly one of your published currencies, an optional discount window, a fulfilment kind, a stock policy and an optional per-player limit. It moves draft → published → retired. |
| Stock | Per listing: unlimited, counted (a number of units left) or code-pool (a pool of codes you load). |
| Cart | One open cart per player, holding award and code lines. |
| Order | The durable record of a purchase. A purchase refused because of the catalog, the stock, the player's limit or their wallet is also an order, stored as rejected with its reason. A malformed request (for example an empty cart) is refused without creating one. |
Every listing delivers through one of three fulfilment kinds:
| Kind | What the player receives | Stock policy |
|---|---|---|
award | An award (for example a badge) from one of your published award definitions, granted in the purchase transaction | unlimited or counted |
code | One code per unit from the listing's code pool, delivered in the purchase transaction | code-pool only |
manual | Something you deliver outside Tanitar; the order waits for your team to claim and fulfil it | unlimited or counted |
How a purchase settles
sequenceDiagram
participant P as Your Player client
participant RM as Reward Management
participant DB as Tenant database
participant YOU as Your integration
participant WH as Your webhook receiver
P->>RM: Checkout with Idempotency-Key
RM->>RM: Recompute the price on the server, check currency, listing and award
RM->>DB: One transaction: debit, stock claim, per-player limit, award grants, order, outbox
alt refused
RM-->>P: Typed refusal (insufficient funds, sold out, limit reached, price changed)
else settled
RM-->>P: The order
DB-->>WH: shop.order.settled
opt code listing
P->>RM: Re-read the codes of the order at any time
end
opt manual listing
YOU->>RM: Claim, then record fulfilment
DB-->>WH: shop.order.fulfilled
end
opt reversal
YOU->>RM: Reverse with a reason
DB-->>WH: shop.order.reversed
end
end
Three guarantees hold for every purchase:
- The server decides the price. The price a player pays is recomputed from the listing and its
active discount at the moment of purchase. A player may send the total they expect; if it differs,
the purchase is refused with
SHOP_PRICE_CHANGEDrather than silently repriced. - All or nothing. The debit, the stock claim, the per-player limit, any award grant and the order
commit in one transaction. A refused purchase leaves no debit and no stock claimed; its
rejectedorder records why. - Charged once. A retried purchase with the same
Idempotency-Keyreturns the same order and never charges twice.
Scopes
The shop has its own scopes, separate from rewards:*, so each responsibility can be given to a
different key:
| Scope | Grants |
|---|---|
shop:read | Read listings and orders |
shop:mutate | Create, edit, publish and retire listings; load codes into a pool |
shop:fulfill | Claim and fulfil manual orders |
shop:reverse | Reverse an order and refund the player |
shop:disclose-code | Read the code delivered on an order (audited) |
None of these implies another. In particular, shop:mutate can load codes but never read one back.
Routes
All shop routes are on Reward Management ($REWARD_API_URL).
| Route | Scope | Idempotency-Key | Guide |
|---|---|---|---|
GET /v1/shop/listings | shop:read | None | Listings |
GET /v1/shop/listings/:listingKey | shop:read | None | Listings |
POST /v1/shop/listings | shop:mutate | Required | Listings |
PATCH /v1/shop/listings/:listingKey | shop:mutate | Required | Listings |
POST /v1/shop/listings/:listingKey/publish | shop:mutate | Required | Listings |
POST /v1/shop/listings/:listingKey/retire | shop:mutate | Required | Listings |
POST /v1/shop/listings/:listingKey/stock/codes | shop:mutate | Required | Stock |
GET /v1/shop/orders | shop:read | None | Orders |
GET /v1/shop/orders/:orderId | shop:read | None | Orders |
POST /v1/shop/orders/:orderId/claim | shop:fulfill | Required | Orders |
POST /v1/shop/orders/:orderId/fulfillment | shop:fulfill | Required | Orders |
POST /v1/shop/orders/:orderId/code-disclosures | shop:disclose-code | Not used | Orders |
POST /v1/shop/orders/:orderId/reversal | shop:reverse | Required | Reversal |
Events
The shop publishes six events you can subscribe a webhook endpoint to:
| Event | When |
|---|---|
shop.listing.published | A listing is published, or a published listing is edited (a new revision) |
shop.listing.retired | A listing is retired |
shop.stock.depleted | A published listing's counted stock or code pool reaches zero |
shop.order.settled | A purchase settles, including a refused purchase recorded as rejected |
shop.order.fulfilled | Your team records fulfilment of a manual order |
shop.order.reversed | An order is reversed by your team or cancelled by the player |
Payloads are in the event catalog.