Catalog Items and Prices
The catalog item types, how Stash prices each item, purchase packs, and web store bonuses in a Real-time Catalog response.
The item types and prices your GetCatalog response carries.
Catalog Items
Each catalog item is exactly one of five types (a protobuf oneof; the JSON key is in parentheses):
| Type | Use Case |
|---|---|
PurchasableItem (product) | Standard products and bundle offers with pricing |
PurchasePack (pack) | One offer sold at several prices; see Purchase packs |
OfferChainItem (chain) | Progressive offers that unlock sequentially with CLAIMED / UNLOCKED / LOCKED link statuses |
NonPurchasableItem (info) | Banners, promotions, and informational displays |
FreeItem (free) | Free-to-claim gifts with a claim status, an optional refreshAt window, and claim tracking |
For the full field-level structure of each type, including pricing, contents, expiration, attributes, and offer chain link details, see the GetCatalog API Reference.
Stash skips an item whose type it does not recognize and renders the rest of the catalog.
Prices
Each PurchasableItem carries one price, set in one of two ways:
price | What Stash does |
|---|---|
amount (currency, cents) | Shows and charges this price as sent. No Stash Studio price is looked up for the item. |
priceId | Looks up the price imported in Stash Studio for that ID and the player's platform and region. See Studio price lookup. |
| Omitted | The same Studio lookup, using the item's productId. |
When you send amount:
centsis an integer in the currency's minor units:199inUSDis $1.99, and300inJPYis ¥300. The number of decimal places per currency follows your shop's minor-unit standard.centsmust be greater than0. To give an item away, use aFreeItem.- Use one currency for all priced items in a response. Stash shows one currency per shop page and leaves out priced items in any other currency.
Minor-unit standard
Each shop has one minor-unit standard. It sets the number of decimal places Stash uses for a currency when it reads cents in your catalog prices and when it writes cents, tax, total and decimalPlaces in RegisterPayment, ConfirmPayment and CancelPayment requests.
| Standard | Decimal places per currency |
|---|---|
| Unicode CLDR release 32 (default) | The digits values under fractions in the CLDR release 32 supplemental data. A currency not listed there has 2. |
| Unicode CLDR release 47 | The digits values under fractions in the CLDR release 47 supplemental data. A currency not listed there has 2. |
| ISO 4217 | The minor unit in the ISO 4217 currency list. |
The standards disagree for some currencies, for example:
| Currency | CLDR release 32 | CLDR release 47 | ISO 4217 |
|---|---|---|---|
COP, IDR, PKR, TZS | 0 | 2 | 2 |
MMK, RSD | 0 | 0 | 2 |
IQD | 0 | 0 | 3 |
A price of 5,000 COP is cents: 5000 under CLDR release 32 and cents: 500000 under the other two.
To use a standard other than the default, contact your Stash engineering partner: they can configure it for your shop.
Studio price lookup
Stash looks up an imported price in the currency that is primary for the player's region, and takes the first match in this order:
- The player's region and platform.
- The player's region, from a file imported for all platforms (Universal in Studio).
USinUSD, the player's platform.USinUSD, Universal.
If none of these exists, the item is left out of the catalog. A missing country therefore shows the US price rather than hiding the item, and a row imported for a country in a currency other than its primary one is never matched. When no platform is resolved, only Universal rows match.
To return a different price per platform or country, choose the price from the request's platform and region:
platformcomes from the Unity SDK session, then from the platform stored on the player's account (theuser.platformyour backend sends on Approve custom login), then from the browser's user agent (iPhone, iPad and iPod are iOS; Android is Android). A desktop browser has no platform unless the account has one stored, so senduser.platformwhen you link the player if prices differ by platform.regionis the most recent region your backend sent for the player, asuser.regionCodeon Approve custom login or Generate authenticated URL when the player signs in. When you send none, it is the country of the player's IP address, orUSwhen that is unknown. It is not the country of the player's app store account.
Purchase packs
Use a pack when one offer is sold at several prices and the player buys one of them, for example the same reward at three price points. Each price point is one entry in the pack's items (a PurchasePackItem).
Declare the values the whole offer shares once, on the pack:
| Field | Meaning |
|---|---|
id | Required. Your stable identifier for the offer, unique within the response. A pack without an id is left out of the catalog. |
items | Required. The offer's price points, in display order. Each one has its own productId and price, and optional contents. |
maxPurchasable | Optional. The player's remaining purchases of this offer. Stash applies it to every item in the pack, together with each item's own maxPurchasable. |
expiration | Optional. After this time no item in the pack is available for purchase, except a purchase already in checkout within a checkout grace period agreed for your shop. |
refreshAt | Optional. When the offer's allowance next resets, for display such as a countdown. Purchase checks never use it. |
A PurchasePackItem has no expiration or refreshAt of its own, so the pack's values govern every item in it. It can have its own maxPurchasable: the player's remaining purchases of that price point. The lower of the item's and the pack's maxPurchasable applies to the item.
- Buy the whole offer. To sell one item that buys everything in the offer in a single transaction, give it
type: PURCHASE_PACK_ITEM_TYPE_WHOLE_PURCHASE_PACKand list the combined rewards in itscontents. Use this type on at most one item per pack. - Remaining count. Compute
maxPurchasableper player and send it fresh in every response. Stash keeps no purchase count of its own: it checks each purchase against the value from your catalog. - Purchase calls. For an item bought from a pack,
RegisterPayment,ConfirmPaymentandCancelPaymentcarry the pack'sidaspurchasePackIdon that item.productIdstill identifies the price point. - Unavailable offers. To withdraw an offer or a price point, leave it out of the response. To show a price point as sold out instead, keep it and send
maxPurchasable: 0on it: the player sees it but cannot buy it, and Stash refuses checkout for it.
See PurchasePack in the API reference for every field.
Web Store Bonuses
Give players extra content for buying on the web:
| Field | What it does |
|---|---|
bonusItems on a product, or on an item in a pack | The bonuses that come with the item, each a type, an id and an amount. On purchase, Stash sends them to your ConfirmPayment. |
contents[].bonusQuantity | A bonus amount shown next to a content item's quantity, for example +25. It is an amount, not a percentage. Display only: Stash does not send it to ConfirmPayment, and when the item also sets bonusItems, those decide what is granted. |
contents[].type: CONTENT_ITEM_TYPE_WEB_STORE_BONUS | Shows a content item as a web store bonus. |
Grant bonuses from ConfirmPayment. The bonusItems amounts on the ConfirmPayment request are the amounts to grant for the bonus IDs they name. Do not also grant your offer's own bonus for an ID listed there, or the player receives it twice.
How is this guide?
Payment Flow
Understand where the money moves in a Real-time Catalog purchase, why ConfirmPayment is required rather than optional, and which checks only your backend can enforce.
Free Items and Streaks
How Stash tracks claims of free items in a Real-time Catalog response, and how streaks tracked by Stash work.