GameSDK reference: ads, economy & tournaments
- Written for
- + Written for
- Deprecated
- + Deprecated
- Applies to
- + Applies to
GameSDK reference: ads, economy & tournaments
This page is the reference for these GameSDK calls:
Area | Calls |
|---|---|
Ads |
|
Economy |
|
Tournaments |
|
Events |
|
For concepts and walkthroughs, see Ads and rewarded ads, In-game economy and Tournaments. For loading the SDK, GameSDK.on, and the core lifecycle calls, see GameSDK core reference.
How these calls behave
All of these calls send a message from your sandboxed game to the Cool GPT Games page, which calls the API for you. Your game never handles the player's credentials. Keep these rules in mind:
No call rejects. Every method here returns a Promise that resolves, with an error object or
nullwhen something goes wrong. You don't needtry/catch, but you do need to check the value.Every call settles. Economy and tournament calls resolve
nullif the page doesn't answer within 6 seconds.requestAdresolves{ error: "timeout" }after 60 seconds. - Message rate limit. The page accepts at most 30 SDK messages per second from your game, counting every SDK call. Extra messages are silently dropped, so the call then times out. - Sign-in comes from the site. "Signed in" means the player is signed in to Cool GPT Games in that browser. Your game can check withGameSDK.getPlayer()(see GameSDK core reference). - Only on Cool GPT Games. Outside the Cool GPT Games player, for example when you open yourindex.htmllocally, nobody answers. Economy and tournament calls resolvenullafter 6 seconds, andrequestAdresolves{ error: "timeout" }after 60 seconds. Guard for this in local development.
Ads
GameSDK.requestAd(placement)
GameSDK.requestAd(placement?: string): Promise<{ rewarded: boolean } | { error: string }>Asks the page to show an ad over your game. The promise settles exactly once.
Parameters
Name | Type | Required | Notes |
|---|---|---|---|
| string | No |
|
Resolves
Value | When |
|---|---|
| A rewarded ad was watched to the end of its 5-second countdown, and the ad server confirmed the reward |
| A rewarded ad was closed early or the ad server didn't confirm the reward, or a midroll finished (midrolls never reward) |
| No ad was available |
| The visitor hasn't allowed ads (banner unanswered, rejected, or the consent tool couldn't load) |
| Midroll only: requested before |
| Another ad is still in progress. That earlier ad and its promise carry on unaffected. |
| The page didn't answer within 60 seconds |
| Network or other failure |
Events fired
Case | Events |
|---|---|
Ad shown |
|
|
|
|
|
|
|
Failure behaviour
The promise never rejects, and it settles exactly once: with the ad's result, or with one error.
60-second timeout. If the page never answers (the message was dropped, or you're not running on Cool GPT Games), the promise resolves
{ error: "timeout" }andadError { reason: "timeout" }fires. If the page's answer arrives later anyway, itsadStarted/adComplete/adErrorevents still fire, but the promise has already settled.One ad at a time. Calling
requestAdwhile an earlier ad is in progress resolves the new call with{ error: "busy" }straight away. The earlier call isn't affected and still settles with its own result. (An ad that has been in progress for more than 90 seconds no longer blocks a new one.)
Sign-in: not required. Guests get ads and rewarded results the same way as signed-in players.
Limits: ad requests and ad events are limited to 60 per minute per play session (or per game when there's no session). There's no enforced per-session cap or minimum interval between ads.
Example
async function watchForReward() {
button.disabled = true;
const r = await GameSDK.requestAd("rewarded"); // always settles, at most 60 s
button.disabled = false;
if (r.rewarded === true) grantBonus();
else if (r.error === "no_consent") toast("Allow ads to earn this reward.");
}Pitfalls
Grant rewards on
rewarded === trueonly.adCompletealso fires for closed, unconfirmed and midroll ads.The platform doesn't pause your game. Pause on
adStartedand resume onadCompleteoradError, and make resuming safe to run twice. Note thatadError { reason: "busy" }fires while another ad is still showing, so don't resume onbusy. Disabling your ad button until the promise settles avoids it entirely.The
pauseandresumeevents are about tab visibility, not ads.The platform grants nothing for a rewarded ad. The reward is whatever your game gives.
Midroll ads may not fill at all. Never make progress depend on one.
Economy
Items are defined per game by the creator. See In-game economy. Prices are in coins, a virtual currency with no real-money value.
GameSDK.economy.listItems()
GameSDK.economy.listItems(): Promise<Item[] | null>
interface Item {
sku: string;
name: string;
description: string | null;
priceCoins: number; // integer ≥ 0
}Returns this game's active store items.
Parameters: none.
Resolves: an array of Item. The array order isn't guaranteed, so sort it yourself.
Failure behaviour: resolves [] if the API returns an error, and null on a network error or after the 6-second timeout.
Sign-in: not required. Guests see the same list.
Example
const items = (await GameSDK.economy.listItems()) || [];
items.sort(function (a, b) { return a.priceCoins - b.priceCoins; });
items.forEach(function (it) { addRow(it.name, it.priceCoins + " coins", it.sku); });Pitfalls: inactive or deleted items aren't listed, but players may still own them. Always combine this list with getEntitlements().
GameSDK.economy.purchase(sku)
GameSDK.economy.purchase(sku: string): Promise<PurchaseResult | { error: string } | null>
type PurchaseResult =
| { ok: true; purchased: string; coins: number } // newly bought
| { ok: true; alreadyOwned: true; coins: number }; // already owned, not chargedBuys one item for the signed-in player with their coins. On success the player owns the SKU in this game permanently.
Parameters
Name | Type | Required | Constraints |
|---|---|---|---|
| string | Yes | Must match an active item on this game. 1–64 characters. The value is converted with |
Resolves
Value | Meaning |
|---|---|
| Bought. |
| Already owned. No charge. |
| The player is a guest. Nothing happened. |
| The balance is too low. Nothing was charged. |
| No active item with that SKU on this game, or the game wasn't found |
| More than 60 purchases per minute by this player |
| The SKU was longer than 64 characters |
| The player's sign-in is no longer valid |
| Other failure |
| Timeout (6 s), network error, or |
Events: fires purchase only for a successful answer (ok: true, including alreadyOwned), with that answer as the payload. Errors, null and the timeout never fire it. A successful answer that arrives after the 6-second timeout has already resolved your promise with null still fires the event, so listening for purchase catches late successes.
Idempotency:
Repeat purchases of an owned SKU are free no-ops that return
alreadyOwned.A purchase is atomic: the item is granted, the player charged and the creator credited together, or not at all. Two purchases of the same SKU in flight at the same time can't both charge. At most one is charged, and the other resolves
alreadyOwned(or fails on its own, for example withinsufficient_coins).There's no idempotency key.
Sign-in: required. Guests get { error: "sign_in_required" }.
Limits: 60 purchases per minute per player.
Example
buyBtn.onclick = async function () {
buyBtn.disabled = true;
const r = await GameSDK.economy.purchase("skin_gold");
buyBtn.disabled = false;
if (r && r.ok) { equip("skin_gold"); setBalance(r.coins); return; }
if (r === null) { // unknown outcome: reconcile
const e = await GameSDK.economy.getEntitlements();
if (e && e.owned.indexOf("skin_gold") !== -1) equip("skin_gold");
return;
}
toast(r.error === "insufficient_coins" ? "Not enough coins" :
r.error === "sign_in_required" ? "Sign in to buy" : "Purchase failed");
};Pitfalls
A
nullresult doesn't mean "not purchased". Reconcile withgetEntitlements().Items are durable only. You can't buy a SKU twice, so there are no consumables.
There's no refund operation.
Coins can't be bought. Don't show a "buy coins" prompt.
GameSDK.economy.getEntitlements()
GameSDK.economy.getEntitlements(): Promise<{ owned: string[] } | null>The SKUs the signed-in player owns in this game.
Parameters: none.
Resolves: { owned: ["skin_gold", …] }. This includes SKUs whose items have since been deactivated or deleted.
Failure behaviour: resolves { owned: [] } for guests and when the API returns an error, and null on a network error or timeout.
{ owned: [] }doesn't prove the player owns nothing. It's also what you get for a guest or an API error. Don't remove unlocks based on an empty result. Only add unlocks based onowned.
Sign-in: needed for a meaningful result. Guests always get { owned: [] }.
Example
GameSDK.economy.getEntitlements().then(function (e) {
((e && e.owned) || []).forEach(unlock);
});Tournaments
Tournaments are created server-side by the creator. See Tournaments. Scores reach them automatically through GameSDK.submitScore() and GameSDK.replay.submit(). There's no tournament-specific score call.
The Tournament objects below share this shape:
interface Tournament {
id: string;
name: string;
description: string | null;
metric: "high_score" | "total_score";
status: "scheduled" | "live" | "ended" | "settled" | "canceled";
prizePoolCoins: number; // coins (virtual currency)
maxWinners: number;
startsAt: string; // ISO 8601
endsAt: string; // ISO 8601
}GameSDK.tournaments.list()
GameSDK.tournaments.list(): Promise<ListedTournament[] | null>
interface ListedTournament extends Tournament {
entries: number; // number of players entered
joined: boolean; // this player has an entry (joined or scored)
myRank: number | null; // this player's current rank; null until they've scored
}This game's tournaments:
scheduled, live and ended ones
settled ones for 7 days after settlement
up to 20, latest
endsAtfirstnever canceled ones
Status changes (scheduled → live, and settlement of tournaments past endsAt) happen in the background about every 30 seconds. Reading the list also applies any that are due.
Parameters: none.
Resolves: an array (possibly empty).
myRankuses the same ranking as the standings and settlement: higher score first, then whoever reached that score first. Players in an exact tie (same score reached at the same moment) share a rank, and the next rank is skipped (1, 2, 2, 4). See Tournaments.joinedistruefor any entry. A player who joined but hasn't scored hasjoined: trueandmyRank: null.
Failure behaviour: resolves [] if the API returns an error, and null on a network error or timeout.
Sign-in: not required. For guests, joined is false and myRank is null.
Example
const ts = (await GameSDK.tournaments.list()) || [];
ts.filter(function (t) { return t.status === "scheduled"; })
.forEach(function (t) { addUpcoming(t.name, new Date(t.startsAt)); });myRank always matches the player's rank in getStandings(). Use it when the player is outside the top 100 that getStandings() returns.
GameSDK.tournaments.getActive()
GameSDK.tournaments.getActive(): Promise<ListedTournament | null>A convenience wrapper over list(). It returns the first tournament with status === "live", which is the live one with the latest endsAt, or null.
Parameters: none.
Resolves: one ListedTournament, or null if none is live.
Failure behaviour: null on any failure or timeout. That's indistinguishable from "no live tournament".
Sign-in: not required. Guest behaviour is the same as for list().
Example
GameSDK.tournaments.getActive().then(function (t) {
if (t) showBanner(t.name + " ends " + new Date(t.endsAt).toLocaleString());
});GameSDK.tournaments.join(id)
GameSDK.tournaments.join(id: string): Promise<{ ok: true; joined: true } | null>Enters the signed-in player into a tournament with a score of 0 and no rank, so they appear on the board before scoring. Joining is optional. The first accepted score enters them anyway.
Parameters
Name | Type | Required | Constraints |
|---|---|---|---|
| string | Yes | A tournament |
Resolves: { ok: true, joined: true }. That's also the result when the player had already joined.
Failure behaviour: resolves null for any of these:
the player is a guest
the tournament isn't open (
closed)the tournament doesn't exist
any other error, or a timeout
The specific reason isn't passed to the game.
Events: fires tournamentJoined with { ok: true, joined: true } only when the join succeeded. Failures and timeouts never fire it. A success that arrives after the 6-second timeout still fires the event.
Sign-in: required. Guests get null.
Example
joinBtn.onclick = async function () {
const r = await GameSDK.tournaments.join(currentTournament.id);
if (r && r.joined) joinBtn.textContent = "You're in!";
else toast("Sign in to join, or this tournament has closed.");
};Pitfall: joining alone never wins anything. A player who joined but never scored has no rank and gets no prize, however few players there are.
GameSDK.tournaments.getStandings(id)
GameSDK.tournaments.getStandings(id: string): Promise<{
tournament: Tournament & { game: { slug: string; title: string } };
standings: Standing[];
} | null>
interface Standing {
rank: number | null; // null for an entrant who hasn't scored
handle: string;
displayName: string | null;
avatarUrl: string | null;
score: number;
prizeCoins: number; // 0 until settled
}The full detail and the top 100 standings of any tournament, by id. Reading it settles the tournament straight away if its window has closed and the background job hasn't settled it yet.
Parameters
Name | Type | Required | Constraints |
|---|---|---|---|
| string | Yes | Any tournament id. It doesn't have to belong to this game. |
Resolves:
Standings are ordered by score, highest first, then by who reached that score first. Entrants who haven't scored come last, with
rank: null.rankis a competition rank: players in an exact tie share it and the next rank is skipped (1, 2, 2, 4). It's the same rankmyRankinlist()reports and settlement uses.Before settlement,
prizeCoinsis 0. After settlement,rankandprizeCoinsare the final stamped values. Tied winners show their equal split of the prize.
Failure behaviour: null if the tournament doesn't exist, on an API error, or on a timeout.
Sign-in: not required. The result is the same for everyone.
Example
const r = await GameSDK.tournaments.getStandings(t.id);
if (r) r.standings.slice(0, 10).forEach(function (s) {
addRow(s.rank ? "#" + s.rank : "–", s.displayName || s.handle, s.score,
s.prizeCoins ? s.prizeCoins + " coins" : "");
});Pitfalls
Only the top 100 are returned. A player ranked lower won't find themselves here, so use
myRankfromlist().Two rows can share a rank. Don't use
rankas a unique key or list index.
Events
Subscribe with GameSDK.on(name, callback). Callbacks that throw are ignored and don't affect other listeners.
Event | Payload | Fired by |
|---|---|---|
| none | An ad overlay appeared |
|
| An ad ended or was closed |
|
| No ad was shown for that request |
| The successful | A successful |
|
| A successful |
| none | The browser tab was hidden or shown. Not fired for ads. |