> ## Documentation Index
> Fetch the complete documentation index at: https://docs.turntable.games/llms.txt
> Use this file to discover all available pages before exploring further.

# Progression

> Events, grant rules, achievements, and free item grants.

Turntable games can reward players with **items** and **achievements** as
they play — the Steam/Roblox-style progression layer on top of the
[economy](/sdk/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.

## Events + grant rules (the recommended path)

Your game emits semantic events; the creator attaches **rules** that the
platform evaluates:

```js theme={null}
// 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
});
```

```bash theme={null}
# 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:

```js theme={null}
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:

```js theme={null}
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](/sdk/economy) or the creator grant endpoint below.

## Consuming items

Destroying value is always safe, so any owned item can be consumed:

```js theme={null}
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:

```bash theme={null}
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.
