GameSDK progression & social reference
This is the reference for the GameSDK methods that feed player progression (XP, levels, achievements) and social features (friends, profiles, the in-game leaderboard). For concepts, caps, how to declare achievements, and what players see, read XP, achievements & social first. For loading the SDK, ready(), start() and on(), see GameSDK core reference.
Common behaviour
These rules apply to every method on this page.
Runtimes. Available wherever
window.GameSDKis loaded: HTML5 games that include the SDK script, and the cart runtimes (pico8,twine,bitsy), where it's added to the served page for you (see How games run). Calls work only while your game is running inside the Cool GPT Games player. Opened any other way, the page doesn't answer, so every method resolves its timeout fallback. - Signed-in vs guest. The player's sign-in state decides what happens. Guests always get the "guest" result listed for each method. Games embedded on other websites run as guests (see Embedding & the score bridge). - Play session.awardXpandunlockAchievementcount only inside a live play session that has run for at least 30 seconds with at least 2 heartbeats. The session opens when your game callsGameSDK.start()(orGameSDK.replay.ready()). Without one, they resolve with the "nothing landed" result.Never rejects, always settles. No method on this page rejects its promise or throws, and every promise has a built-in timeout. Failures come back as resolved values: zeros, empty arrays,
unlocked: falsewith areason,status: "error".Message budget. The player page accepts at most 30 SDK messages per second from your game, across all methods. Extra messages are silently dropped, and a dropped request resolves its timeout fallback. - Replies are matched by request id. Each call gets the answer to its own request, so parallel and repeated calls are safe.
Method summary
Method | Resolves | Needs sign-in | Timeout / fallback |
|---|---|---|---|
|
| Yes (guests get 0) | 8 s → |
|
| Yes (guests: | 8 s → |
|
| No | 5 s → last known player info |
| returns | No | n/a |
|
| Yes (guests get | 6 s → |
|
| Yes (guests get | 6 s → |
|
| No | n/a |
|
| Only for | 6 s → |
GameSDK.awardXp(amount, reason?)
awardXp(amount: number, reason?: string): Promise<{
granted: number;
total: number;
level: number;
leveledUp: boolean;
}>Asks the platform to give the signed-in player XP for genuine gameplay. The platform decides how much actually lands.
Parameters
Name | Type | Required | Constraints |
|---|---|---|---|
| number | yes | Converted with |
| string | no | Converted with |
Resolved value
Field | Type | Meaning |
|---|---|---|
| number | XP that actually landed. |
| number | The player's lifetime XP after this grant. |
| number | The player's site-wide level after this grant. |
| boolean |
|
Caps applied, in order: 100 per award → diminishing returns max(0.15, 1 − xpFromThisGameToday / 500) → 200 per game per session → 500 per game per player per UTC day → 3,000 per player per UTC day across the whole site. Achievement, first-play and personal-best XP from your game count toward the session and per-game daily totals. See XP, achievements & social for a worked example.
Guests and failures
Situation | Resolves |
|---|---|
XP landed |
|
All caps reached, or a non-positive amount |
|
Guest |
|
No play session ( |
|
Session under 30 s / 2 heartbeats, ended, or flagged |
|
Rate-limited, invalid |
|
No answer within 8 s (for example, the request was dropped over 30 messages/s) |
|
<last known> is the player's level as last seen by the page or the SDK (from getPlayer() or an earlier awardXp answer), or 1 if it isn't known yet. In every "nothing landed" case except "caps reached", total is a placeholder 0, not the player's real total. Show total only when granted > 0. These placeholder answers never change the level the SDK remembers for getPlayer(). The SDK doesn't tell you why nothing landed.
Side effects
The
xpevent fires with the same object on every answer from the page, includinggranted: 0. It doesn't fire on the 8 s timeout.If
leveledUp, thelevelUpevent fires once with{ level }.The site shows a "+N XP" toast when
granted > 0, and a "Level N!" toast on level-up. The player also gets a site notification and 100 bonus coins per level gained.Every landed XP point also gives the player 1 coin (virtual currency).
Limits
120 requests per minute per player per game. Over the limit, it resolves as "nothing landed".
The caps above.
Upload checks reject ungated grants on load or in
setInterval, and flag grants in loops/timers and literal amounts of 10,000 or more for review.
Example
function onRoundWon(stars) {
GameSDK.awardXp(10 + stars * 10, "Round won").then(function (r) {
if (r.granted > 0) hud.flash("+" + r.granted + " XP");
if (r.leveledUp) hud.flash("Level " + r.level + "!");
});
}Pitfalls
It can take up to 8 s to resolve if the page doesn't answer. Don't block gameplay on it.
Grants in the first ~30 s after
start()return 0.reasonis visible to players, and over 120 characters the grant fails.
GameSDK.unlockAchievement(key)
unlockAchievement(key: string): Promise<{
key: string;
name: string | undefined;
alreadyHad: boolean;
unlocked: boolean;
reason?: string;
}>Unlocks one of your game's declared achievements for the signed-in player. It's safe to repeat: a player holds each achievement once. The XP reward is set by the achievement's tier (common 10, uncommon 25, rare 50, epic 100, legendary 200) and goes through the same caps as awardXp, including the 100-per-grant limit.
Parameters
Name | Type | Required | Constraints |
|---|---|---|---|
| string | yes | Converted with |
Achievements are declared in a version's gamification block (sent with the upload-init or versions request, or through the CLI, publisher SDK or MCP server) and become active when that version is approved. See XP, achievements & social.
Resolved value
Field | Type | Meaning |
|---|---|---|
| string | The key you passed. |
| string | undefined | The achievement's display name. Set when the key matched an active achievement for a signed-in player in a valid session. |
| boolean |
|
| boolean |
|
| string (optional) | Present only when neither |
Outcomes
Situation | Resolves | Toast |
|---|---|---|
New unlock |
| Achievement toast (plus level-up toast if one happened) |
Already unlocked |
| None |
Unknown or deactivated key |
| None |
Guest |
| One-time sign-in toast |
No play session ( |
| None |
Session under 30 s / 2 heartbeats |
| None |
Session ended or flagged |
| None |
Rate-limited |
| None |
Other API or network error |
| None |
No answer within 8 s (for example, the request was dropped over 30 messages/s) |
| None |
A new unlock is unlocked === true. Unlocking doesn't return the XP granted, but the levelUp event fires if the achievement's XP caused a level-up.
XP paid. An achievement pays its tier's XP in full (legendary = 200), skipping the 100-per-grant cap, the diminishing-returns curve and the per-session cap that apply to awardXp. It is still bounded by your game's 500 XP per player per UTC day and the player's 3,000 XP per day across the site, so a late unlock on a heavy day can pay less, or nothing. The player keeps the achievement regardless. To read what an unlock actually paid, use GET /v1/games/:slug/achievements/mine, whose xpGranted is the ledgered amount.
Side effects
The
achievementevent fires only whenunlockedistrue.The unlock appears on the player's account page and in their public-profile showcase, and counts toward the site's daily "earn an achievement" quests.
Limits
60 requests per minute per player per game.
At most 30 achievements per game.
A literal key in your code that the uploaded version doesn't declare gets the version rejected automatically. A version uploaded without a
gamificationblock keeps the game's current achievements, so their keys count as declared.
Example
function onLevelComplete(level, hitsTaken, timeSec) {
if (level === 1) GameSDK.unlockAchievement("first_clear");
if (hitsTaken === 0) {
GameSDK.unlockAchievement("untouchable").then(function (r) {
if (r.unlocked) showBadgePopup(r.name);
});
}
if (timeSec < 60) GameSDK.unlockAchievement("speedrunner");
}Pitfalls
It can take up to 8 s to resolve if the page doesn't answer. Don't block gameplay on it.
A version approved with a
gamificationblock replaces the game's achievement list: keys it leaves out are deactivated, and an emptyachievementslist deactivates them all. Unlocking a deactivated key resolvesreason: "unknown_achievement". A version without a block leaves the list unchanged.namecan be set when nothing unlocked (the player already had it). Checkunlockedto decide whether to celebrate.
GameSDK.getPlayer()
getPlayer(): Promise<{
signedIn: boolean;
level: number;
handle: string | null;
}>Returns who is playing: whether they're signed in, their site-wide level, and their handle.
Parameters
None.
Resolved value
Field | Type | Meaning |
|---|---|---|
| boolean | Whether the player is signed in to Cool GPT Games. |
| number | Site-wide level (1–999). Always |
| string | null | The player's handle (without |
Situation | Resolves |
|---|---|
Signed in |
|
Guest |
|
Signed in, but looking up progress or profile failed |
|
No answer within 5 s | The SDK's last known player info (see pitfalls) |
Side effects
The player event fires with the same object when the answer arrives.
Limits
None beyond the 30-messages-per-second budget. The page looks the player up once (two API requests) and then answers from memory. Parallel calls share that one lookup, and a failed lookup is retried on the next call.
Example
GameSDK.getPlayer().then(function (p) {
if (p.signedIn) {
title.textContent = "Welcome back" + (p.handle ? ", @" + p.handle : "") + "!";
levelBadge.textContent = "Lv " + p.level;
} else {
title.textContent = "Playing as guest: sign in to earn XP";
}
});Pitfalls
The 5 s fallback returns the SDK's last known info. Before any answer, that's the guest shape. Don't treat a fallback as definitive.
No display name, avatar, XP or title is exposed. The remembered
levelis kept current byawardXpanswers and level-ups, but call again (or listen tolevelUp) if you display it.
GameSDK.on(event, callback)
on(event: string, callback: (payload: any) => void): typeof GameSDKSubscribes to SDK events. Chainable. Exceptions thrown in your callback are caught and ignored. Events for this area:
Event | Payload | Fires |
|---|---|---|
|
| After every |
|
| Once per level, when |
|
| Only when an |
|
| When a |
|
| Only when |
GameSDK
.on("levelUp", function (e) { confetti(); showLevel(e.level); })
.on("achievement", function (a) { log("Unlocked " + a.name); });The full event list (pause, resume, ads and more) is in GameSDK core reference.
GameSDK.social.getFriends()
social.getFriends(): Promise<Array<{
handle: string;
displayName: string | null;
avatarUrl: string | null;
level: number;
}>>Returns the signed-in player's accepted friends, most recently befriended first, up to 500.
Parameters
None.
Resolved value
An array of friend objects:
Field | Type | Meaning |
|---|---|---|
| string | Friend's handle. Use it with |
| string | null | Friend's display name, if set. |
| string | null | URL of the friend's avatar image, if any. |
| number | Friend's site-wide level. |
Pending requests (incoming or outgoing) aren't included.
Situation | Resolves |
|---|---|
Signed in | Array (may be empty) |
Guest |
|
Network/API error |
|
No answer within 6 s |
|
Limits
The call returns at most 500 friends. A player may hold up to 1,000, so treat this as their most recent friends rather than the complete list. There's no paging and no total count through the SDK; read the site's own friends endpoint for those. Otherwise, nothing beyond the 30-messages-per-second budget.
Example
GameSDK.social.getFriends().then(function (friends) {
friendsPanel.innerHTML = "";
friends.forEach(function (f) {
var li = document.createElement("li");
li.textContent = (f.displayName || "@" + f.handle) + " · Lv " + f.level;
li.onclick = function () { GameSDK.social.viewProfile(f.handle); };
friendsPanel.appendChild(li);
});
});Pitfalls
An empty array can mean "guest", "no friends" or "error". Use
getPlayer()to show the right empty state.A player at the 500-row ceiling has friends you can't see here. Don't present the list as complete, and don't infer "not a friend" from an absent handle.
Display names come from players. Insert them with
textContent, neverinnerHTML.
GameSDK.social.addFriend(handle)
social.addFriend(handle: string): Promise<{
handle: string;
status: "outgoing" | "friends" | "self" | "error";
}>Sends a friend request from the signed-in player to handle. If handle had already sent this player a request, the two become friends immediately.
Parameters
Name | Type | Required | Constraints |
|---|---|---|---|
| string | yes | Converted with |
Resolved value
Field | Type | Meaning |
|---|---|---|
| string | The handle you passed. |
| string | One of the values below. |
| Meaning |
|---|---|
| A request is pending from this player to |
| They're friends: already, or just now because a reciprocal request was accepted. |
|
|
| Nothing happened. See causes below. |
Causes of "error": guest, invalid handle format, no such player, either player has blocked the other, over the per-load or hourly limit, either player is at the 1,000-friend ceiling, this player already has 100 outgoing requests open, network/API error, or no answer within 6 s.
The SDK collapses all of these into "error", so your game can't tell them apart. Over REST the account ceilings are distinguishable: 409 friend_limit and 409 friend_request_limit.
Side effects
On
"outgoing", the other player gets a site notification (and possibly an email): "@you sent you a friend request".On a reciprocal accept, the original requester gets "@you accepted your friend request".
The site shows the player a toast: "Friend request sent to @handle" or "You're now friends with @handle".
Guests see the one-time sign-in toast.
The
friendAddedevent fires with the result only for"outgoing"and"friends".
Limits
Limit | Value | Past the limit |
|---|---|---|
Per game load | 8 requests (counted even when a request fails or targets yourself; invalid handles and guest calls aren't counted) |
|
Per player, site-wide | 30 per hour |
|
Friends per account | 1,000, checked for both players |
|
Open outgoing requests | 100 at once |
|
Example
addFriendBtn.onclick = function () {
addFriendBtn.disabled = true;
GameSDK.social.addFriend(row.handle).then(function (r) {
addFriendBtn.textContent =
r.status === "friends" ? "Friends ✓" :
r.status === "outgoing" ? "Requested" :
r.status === "self" ? "That's you" : "Try later";
addFriendBtn.disabled = r.status !== "error";
});
};Pitfalls
Call it only after the player explicitly asks. Never auto-friend.
Take handles from
getLeaderboard()/getFriends(). Typed handles often fail.The 8-per-load budget is shared by all your calls, including failed ones.
Since
"error"also covers a permanent refusal (a full friends list, too many open requests), don't word your failure message as "try again later". "Couldn't send" is safer, and don't retry automatically.
GameSDK.social.viewProfile(handle)
social.viewProfile(handle: string): voidOpens the public profile of handle (https://coolgptgames.com/u/<handle>) in a new browser tab. The profile shows level ring and title, XP, achievement count, achievement showcase, friends, published games, and add-friend / follow buttons.
Parameters
Name | Type | Required | Constraints |
|---|---|---|---|
| string | yes | 1–40 characters of |
Return value
undefined. There's no promise and no confirmation. If the handle doesn't exist, the tab shows a not-found profile.
Guests and limits
Works for guests. Counts toward the 30-messages-per-second budget.
Example
leaderboardRow.addEventListener("click", function () {
GameSDK.social.viewProfile(entry.handle);
});Pitfalls
Call it synchronously inside a real input handler (click, tap or key). The user gesture propagates from the game's sandboxed cross-origin frame to the page, and the tab opens in Chromium, Firefox and WebKit.
The gesture expires after about 5 seconds. A call made after an
awaiton a network request is blocked as a popup — measured failing at 6 s. Resolve any handle you need (for example fromgetLeaderboard()) before the click, then callviewProfile()straight from the handler.A blocked call is silent.
viewProfile()returnsundefinedeither way and there is no event or error to catch, so you can't detect or retry it.Don't call it on load, on a timer, or after your own animation or confirmation step — those spend the same 5-second budget.
GameSDK.social.getLeaderboard(scope?, opts?)
social.getLeaderboard(scope?: "global" | "friends", opts?: { verified?: boolean }): Promise<Array<{
rank: number;
handle: string;
displayName: string | null;
avatarUrl: string | null;
level: number;
score: number;
}>>Returns your game's top scores so you can draw an in-game leaderboard with profile and add-friend actions. This page covers the social side. For how scores are submitted, validated and ranked, see Scores, leaderboards & anti-cheat.
Parameters
Name | Type | Required | Constraints |
|---|---|---|---|
| string | no |
|
| boolean | no |
|
Resolved value
Up to 50 rows, highest score first, each player's all-time best on the board you asked for. avatarUrl is included even though some older examples leave it out.
rank is a competition rank: tied players share it and the next rank skips (1, 2, 2, 4), with the earliest-reached score listed first within a tie. getMyScore() ranks the same way, so the two agree. Banned and deleted players never appear and never count toward a rank.
Situation | Resolves |
|---|---|
| Array (anyone, including guests) |
| Array of the player plus friends who have a score |
|
|
|
|
Network/API error, or no answer within 6 s |
|
Weekly and monthly boards aren't available through this method. See Scores, leaderboards & anti-cheat.
Don't use rank as a unique key or list index: two rows can share it.
Example
GameSDK.social.getLeaderboard("friends").then(function (rows) {
rows.forEach(function (r) {
addRow("#" + r.rank, r.displayName || "@" + r.handle, r.score, {
onProfile: function () { GameSDK.social.viewProfile(r.handle); },
onAdd: function () { GameSDK.social.addFriend(r.handle); },
});
});
});Related
XP, achievements & social: concepts, caps, declaring achievements, what players see - GameSDK core reference