Skip to main content
Turntable games can reward players with items and achievements as they play — the Steam/Roblox-style progression layer on top of the economy. Because game code runs on the player’s device, the platform treats everything a game reports as a claim, not a fact. The system is designed so that claims can’t mint value:
Events, client grants, and achievement awards only ever affect the calling player’s own account. Client-grantable items can never carry a price. Grant rules cap what any event is worth — server-side.
Your game emits semantic events; the creator attaches rules that the platform evaluates:
// In the game: report what happened.
Turntable.events.emit('boss_defeated').then(function (r) {
  if (!r) return;
  r.granted.forEach(function (g) { showReward(g.name, g.quantity); });      // items rules granted
  r.achievements.forEach(function (a) { showUnlock(a.title); });            // achievements rules awarded
});
# Creator-side (once, via the REST API): the rule that makes it mean something.
POST /api/games/:id/rules
{ "eventName": "boss_defeated", "action": "grant_item",
  "itemId": "<dragon-scale>", "quantity": 1,
  "maxPerPlayer": 3, "cooldownSeconds": 3600 }
Rule constraints — minCount (cumulative events required), maxPerPlayer (total firings), cooldownSeconds — are enforced by the platform, so even a player who fakes events is capped at what an honest player could earn. Event names are slugs (a-z, 0-9, -, _); emit a small set of meaningful events (level_complete), not one per tap. Events are rate-limited at 600/hour per player per game.

Achievements

Creators define achievements per game (POST /api/games/:id/achievements with { key, title, description?, secret? }). Games award and display them:
Turntable.achievements.award('first-win');     // idempotent; resolves { alreadyEarned } on repeats
Turntable.achievements.list();                  // [{ key, title, secret, earned, earnedAt? }]
Awarding is client-trusted — the same tier as submitScore — which is fine because achievements carry no economic value. Secret achievements are masked in list() until earned. Prefer an award_achievement rule when the trigger is an event you already emit.

Free item grants

Items the creator marks grantable can be handed out directly from game code:
Turntable.inventory.grant('participation-badge');
The platform enforces the boundary: a grantable item can never have a price and can never appear in a loot table, so the worst a cheater can do is give themselves the free cosmetics they could earn anyway. Anything with real purchase value moves only through player-approved purchases or the creator grant endpoint below.

Consuming items

Destroying value is always safe, so any owned item can be consumed:
Turntable.inventory.consume('health-potion').then(function (r) {
  if (r) { heal(); updatePotionCount(r.remaining); }
  else { showNeedMorePotions(); }                    // null = not enough owned
});

Authoritative grants (creator API)

For rewards that must not be spoofable at all — priced items included — the creator (or their server) grants directly, authenticated as the creator:
POST /api/games/:id/players/:handle/grants
{ "itemId": "<golden-skin>", "quantity": 1, "idempotencyKey": "tournament-2026-07-winner" }
Every grant and consume, from any path, lands in an append-only item ledger, so anomalies are auditable and reversible.