Catalog ManagementReal-time Catalog

Endpoints

The Catalog, Purchase, and Player API endpoints your game backend implements for a Real-time Catalog integration, and how Stash calls them.

Stash calls these endpoints on your game backend. Implement them as described in Getting Started.

Catalog API

Retrieve dynamic, player-specific product catalogs.

GetCatalog

GET /api/v1/catalog: returns the complete product catalog for a given platform, region, language, and playerId. The response is structured as sections (grids or carousels) containing PurchasableItem, OfferChainItem, or NonPurchasableItem entries.

For the full request parameters and response schema, see the GetCatalog API Reference.

GetOfferDetails

GET /api/v1/catalog/offer: returns detailed offer information including drop rates for loot boxes, required for legal compliance in many jurisdictions.

For the full request parameters and response schema, see the GetOfferDetails API Reference.


Purchase API

Handle the complete purchase lifecycle with three endpoints.

Purchase Flow

For where the player's money is at each step, why ConfirmPayment is the point where your backend can still decline a purchase, and which checks only your backend can enforce, see Payment Flow.

Register and cancel are optional

RegisterPayment and CancelPayment are on or off together. By default Stash calls RegisterPayment before ConfirmPayment, and CancelPayment when a registered web shop cart purchase does not complete.

If your backend does not need a reserve step, ask your Stash engineering partner to turn both off for your shop. Stash then calls neither, and ConfirmPayment is the first request that carries the purchase's transaction ID. Stash can also turn them off only for checkout link purchases (Stash Pay and Buy Now), so web shop carts keep them.

RegisterPayment

POST /api/v1/purchase/register (Step 1): registers a purchase intent. Your backend should reserve inventory for the player and respond with one of the inline status codes (e.g. SUCCESS_REGISTERED, FAILED_INSUFFICIENT_INVENTORY, FAILED_PURCHASE_LIMIT_EXCEEDED, FAILED_INVALID_PRICE).

For the full list of status codes and the complete request/response schema, see the RegisterPayment API Reference.

ConfirmPayment

POST /api/v1/purchase/confirm (Step 2A): confirms and completes the purchase after successful payment. Your backend grants items to the player and acknowledges the transaction.

Unified Integration: This endpoint is the same API used for StashPay integrations. Implement once to power both your WebShop and StashPay checkout links.

Declining a confirm. To refuse the purchase, respond with an HTTP 4xx status and a body that carries a decline object naming a PurchaseConfirmationStatus, and omit results. Stash never charges the player for a declined confirm. Where the checkout supports it, Stash uses the status to pick the message the player sees (for example PURCHASE_CONFIRMATION_STATUS_FAILED_INSUFFICIENT_INVENTORY, PURCHASE_CONFIRMATION_STATUS_FAILED_PURCHASE_LIMIT_EXCEEDED, PURCHASE_CONFIRMATION_STATUS_FAILED_INVALID_PRICE); otherwise the player sees a generic failure message. A 4xx without a decline still declines the purchase, but carries no structured reason.

declined ConfirmPayment response (HTTP 4xx)
{
  "decline": {
    "status": "PURCHASE_CONFIRMATION_STATUS_FAILED_PURCHASE_LIMIT_EXCEEDED",
    "productId": "gold_1000",
    "message": "player has reached the purchase limit for this item"
  }
}

Use PURCHASE_CONFIRMATION_STATUS_FAILED_PACK_LIMIT_EXCEEDED when the purchase allowance shared by every item of a purchase pack is exhausted, and PURCHASE_CONFIRMATION_STATUS_FAILED_PURCHASE_LIMIT_EXCEEDED when only the item's own limit is. Return exactly one of results or decline; a body carrying both is treated as declined.

For the full request/response schema (including the decline object, its status values, and optional fields like extraInGameCurrency, extraLoyaltyPoints, and emailMarketingOptIn), see the ConfirmPayment API Reference.

CancelPayment

POST /api/v1/purchase/cancel (Step 2B, failure path): cancels a pending purchase. Your backend should release reserved inventory so the player can retry.

See the CancelPayment API Reference for the full request/response schema.

Loyalty snapshot on purchase requests

When the shop runs a live loyalty program, the RegisterPayment and ConfirmPayment request bodies carry a loyalty snapshot (type PlayerLoyaltyState): the same object your backend already receives on loyalty webhooks. This gives your backend the player's XP balance, tier, and tier-relative earn multiplier at purchase time, in-band with the register/confirm calls, so it can apply tier-aware logic (for example bonus sizing) without a separate lookup.

loyalty snapshot on the purchase request body
"loyalty": {
  "campaignId": "spring_2026",
  "totalPoints": 1250,
  "currentTierId": "silver",
  "pointsMultiplierPermille": 2000,
  "pointsToNextTier": 750,
  "pointsToNextMilestone": 250,
  "nextMilestone": {
    "milestoneId": "milestone_uuid",
    "rewards": [
      { "itemId": "gold-coins", "quantity": 500, "type": "IN_GAME_CURRENCY" }
    ]
  }
}
  • The snapshot reflects state as of the request. On RegisterPayment it is pre-purchase; on ConfirmPayment it reflects the XP the confirmed purchase awards.
  • It is present only when the shop has a published, enabled loyalty program and a live campaign. Otherwise the field is omitted and the request body is byte-identical to a non-loyalty shop's.
  • Treat it as read-only context. It never replaces the authoritative loyalty state your backend receives on the loyalty webhooks; grant XP-linked effects from the webhook, not from this snapshot.

The snapshot fields match the shared PlayerLoyaltyState payload documented in Loyalty webhooks. The signed pointsDelta and previousTierId are event-specific and meaningful only on webhooks; the purchase-request snapshot otherwise carries the same fields, including the balance, tier, progression, and nextMilestone. When the purchase's XP gain crossed one or more milestone thresholds, the snapshot also carries unlockedMilestones, with the same semantics and de-duplication guidance as on the webhook payload.


Player API

Retrieve player profile information for personalized experiences.

GetPlayer

GET /api/v1/player: returns player profile data (name, avatar, language, in-game level, in-game currency balances) for display in the web shop. Stash calls this endpoint with the playerId you supplied to the catalog request.

See the GetPlayer API Reference for the full request/response schema.

Player level

Return the player's level in inGameLevel as a positive integer. The web shop can show it on the player's profile, for example as a power level. Omit it when your game has no player level; the web shop then shows none. Stash treats 0 as an omitted level.

In-game currency balances

Return the player's balances in wallet, one entry per in-game currency. Stash reads the first 100 entries and ignores the rest.

FieldValue
bonusIdThe ID of the reward type configured on the Reward Types surface in Studio. Matched exactly and case-sensitively. Up to 256 characters: letters, digits, ., _ and -. Required; an entry without it is ignored. If two entries share a bonusId, Stash keeps the first.
bonusTypeWALLET_BONUS_TYPE_IN_GAME_CURRENCY. Stash ignores entries of any other type.
balanceThe amount the player owns, a non-negative 64-bit integer, sent as a JSON integer or a decimal string. Required; an entry without it is ignored. Use a string for values above 9007199254740991. 0 is a real balance. A negative, fractional or non-numeric value fails the player lookup.

Stash attaches the name and icon of the matching reward type in Studio to each balance. Studio supports one in-game currency reward type per shop today, so a balance for a bonusId that is not a reward type in Studio comes back without a name or icon. An entry whose bonusId names a reward type of another kind, such as loyalty points, is ignored. See Reward types.

GetPlayer response with a wallet
{
  "player": {
    "playerId": "player-123",
    "name": "PlayerOne",
    "wallet": [
      { "bonusId": "gold-coins", "bonusType": "WALLET_BONUS_TYPE_IN_GAME_CURRENCY", "balance": "12000000000000000" }
    ]
  }
}

inGameCurrency is deprecated but keeps working, so an existing integration does not need to change now. Stash shows a nonzero value as the balance of the shop's in-game currency reward type in Studio, and omits it when the shop has none. It holds a single currency, and a value above 4294967295 fails the player lookup, so use wallet for new integrations and for large balances. Whenever wallet has at least one entry, Stash uses it for currency balances and ignores inGameCurrency, even if every wallet entry is ignored.

How is this guide?

On this page