Guides

In-game economy

AdminUpdated Sep 22, 2026

In-game economy

You can sell items inside your game for coins, the platform-wide virtual currency players earn on Cool GPT Games. You define a small store of items (SKUs) for your game. Players buy them from inside the game with GameSDK.economy.purchase(sku), and the platform records a permanent entitlement: "this player owns this SKU in this game."

Coins are a virtual currency with no real-money value. Players can't buy coins with money, and neither players nor creators can cash coins out, exchange them, or redeem them for anything outside the platform. Don't describe coins, items or prizes as having monetary value in your game.

For exact signatures and failure modes, see GameSDK reference: ads, economy & tournaments.

How it fits together

Piece

Who

How

Define items

You (creator)

Your game's dashboard page (Store), the REST API, or the Publisher SDK

Show the store

Your game

GameSDK.economy.listItems()

Buy an item

Signed-in player, from your game

GameSDK.economy.purchase(sku)

Check what a player owns

Your game

GameSDK.economy.getEntitlements()

Receive the coins

You

Each paid sale credits the item's price in coins to your coin balance

Where players get coins

Players earn coins across the whole platform, not per game. For example, they earn coins when XP is granted, from level-up bonuses and daily rewards, from friends' gifts, and from tournament prizes. See XP, achievements & social and Tournaments. Your game can't grant coins directly. It can only spend a player's coins through purchase.

There's no coin top-up or coin purchase. If a player is short of coins, tell them so, and don't send them to buy coins.

Items and SKUs

Each item belongs to one game and is identified by its SKU.

Field

Type

Rules

sku

string

1–64 characters: letters, digits, _, ., -, :. It's unique per game and is your stable identifier, for example skin_gold or level_pack:2.

name

string

1–80 characters

description

string

Optional, up to 300 characters

priceCoins

integer

0 to 1,000,000. 0 makes a free item.

active

boolean

Optional, defaults to true. Only active items are listed and purchasable.

A game can have up to 200 items. Editing an existing SKU always works, even at the limit. Only new SKUs count toward it.

Durable only: there are no consumables

Every item is a permanent, one-time unlock per player. Buying a SKU the player already owns doesn't charge them again. It returns alreadyOwned: true. There's no quantity, no stacking and no "use up" operation.

Good fits: skins, cosmetic themes, level packs, a permanent ability, "remove a feature limit".

Not supported: "5 extra lives", "100 gems", repeatable boosts. You can't sell the same SKU to the same player twice.

Defining items

Dashboard: open your game in the creator dashboard and use the Store section to add, edit, deactivate or delete items.

REST (API key with the economy scope; you must own the game):

Method and path

Body

Returns

POST /v1/dev/games/:id/items

{ sku, name, description?, priceCoins, active? }

{ ok: true, sku }. Creates the item, or replaces it if the SKU exists.

GET /v1/dev/games/:id/items

none

{ items: [{ id, gameId, sku, name, description, priceCoins, active, createdAt }] }, including inactive items

DELETE /v1/dev/games/:id/items/:sku

none

{ ok: true }

:id is your game's id, not its slug.

curl -X POST https://api.coolgptgames.com/v1/dev/games/$GAME_ID/items \
  -H "authorization: Bearer $API_KEY" \
  -H "content-type: application/json" \
  -d '{"sku":"skin_gold","name":"Gold Skin","description":"Shiny.","priceCoins":250}'

Publisher SDK:

await client.upsertItem(gameId, { sku: "skin_gold", name: "Gold Skin", priceCoins: 250 });
const items = await client.listItems(gameId);      // all items, including inactive
await client.deleteItem(gameId, "skin_gold");

See Publisher SDK for client setup and API keys & scopes for the economy scope.

Upserts replace the whole item. A POST for an existing SKU overwrites name, description, priceCoins and active:

  • Leaving out description clears it.

  • Leaving out active sets the item back to active.

Always send every field you want to keep.

Changing, deactivating and deleting items

  • Price changes take effect for the next purchase. Players who already own the item aren't affected. - Deactivating (active: false) hides the item from listItems and makes purchase fail with not_found. Players who already own it keep the entitlement. It still appears in getEntitlements.

  • Deleting removes the item definition only. Existing entitlements aren't removed. Players keep owning the SKU. If you later re-create the same SKU, those players already own it.

Use deactivation, not deletion, to retire an item, and keep honouring owned SKUs in your game code.

Using the store in your game

async function openStore() {
  const [items, ent] = await Promise.all([
    GameSDK.economy.listItems(),
    GameSDK.economy.getEntitlements(),
  ]);
  const owned = new Set((ent && ent.owned) || []);
  renderStore((items || []).map(function (it) {
    return { sku: it.sku, name: it.name, price: it.priceCoins, owned: owned.has(it.sku) };
  }));
}

async function buy(sku, button) {
  button.disabled = true;                          // never fire two purchases at once
  const r = await GameSDK.economy.purchase(sku);
  button.disabled = false;

  if (r && r.ok) {
    unlock(sku);                                   // purchased or alreadyOwned
    showBalance(r.coins);
  } else if (r && r.error === "sign_in_required") {
    showMessage("Sign in to buy items.");
  } else if (r && r.error === "insufficient_coins") {
    showMessage("Not enough coins yet. Keep playing to earn more!");
  } else if (r === null) {
    // Timed out or no answer. The purchase MAY have gone through, so re-check ownership.
    const ent = await GameSDK.economy.getEntitlements();
    if (ent && ent.owned.indexOf(sku) !== -1) unlock(sku);
  } else {
    showMessage("Purchase failed. Please try again.");
  }
}

Unlocking on load

Call getEntitlements() when the game starts and unlock everything in owned. That way, ownership follows the player across devices and sessions without any save data. Don't store "owned" flags in your own save as the source of truth. The entitlement list is authoritative.

Purchase semantics

When a signed-in player calls purchase(sku):

  1. The platform looks up an active item with that SKU on this game. If there isn't one, the result is { error: "not_found" }.

  2. Everything else happens in one atomic transaction: - The entitlement is recorded first. If the player already owns the SKU, nothing else happens and the result is { ok: true, alreadyOwned: true, coins }. They aren't charged.

    • If the price is above 0, the price is debited from the player's coins and the same number of coins is credited to the game's creator. If the balance is too low, the result is { error: "insufficient_coins" } and the whole transaction is undone: nothing is charged and no entitlement is recorded.

    • If the price is 0, there's no coin movement.

    Either all of it happens or none of it does.

  3. The result is { ok: true, purchased: sku, coins }, where coins is the player's balance after the purchase.

The SDK fires the purchase event only when the purchase succeeded (ok: true, including alreadyOwned: true). It doesn't fire for { error } results or a null timeout. See the reference.

Purchases need the player's own signed-in session. Spending a player's coins is only allowed from the player's signed-in session on Cool GPT Games. Inside the game page that's automatic: the page makes the request with the player's session, and your game never handles it. The purchase endpoint refuses API keys (403 session_required), so you can't buy items on a player's behalf from your own server.

Double-purchase protection and idempotency

  • Repeats are safe. Buying an owned SKU returns alreadyOwned: true and charges nothing, so a "Buy" button that the player taps again after success is harmless.

  • Concurrent purchases of the same SKU can't double-charge. Only one of them records the entitlement and is charged. The others resolve alreadyOwned: true.

  • Disable the Buy button until the promise settles anyway, as in the example above, so the player sees one clear result. - There's no idempotency key for purchases. A null result (timeout) doesn't mean the purchase failed. Check getEntitlements() before offering a retry. Retrying is safe: if the first attempt went through, the retry returns alreadyOwned: true without charging again.

Refunds

There's no refund or revoke operation for item purchases, neither for players nor through the creator API. Deleting or deactivating an item doesn't return coins to anyone. Describe items accurately before sale.

Limits

Limit

Value

Items per game

200

Purchases

60 per minute per player (exceeding it returns rate_limited)

SDK response timeout

6 seconds per call; after that the promise resolves null

SDK messages to the page

30 per second across all SDK calls; extras are silently dropped

Guests

Call

Guest result

listItems()

Works normally, since the store is public

getEntitlements()

{ owned: [] }

purchase(sku)

{ error: "sign_in_required" }, with no charge and no entitlement

Show guests the store with prices, but prompt them to sign in when they try to buy.

What you earn from sales

Each paid sale credits the item's full price, in coins, to your own coin balance. That's the same balance you can spend as a player or use to fund tournament prize pools (see Tournaments). Coins from sales are not money and aren't part of your ad-revenue earnings or payouts. See Ads and rewarded ads for how creator payouts work.

Related

Was this page helpful?