triggair /docs
API

API reference

Every endpoint, its auth scope, and a request/response example. Most games use the @triggair/sdk client instead of raw HTTP — this is the underlying contract. Machine-readable: /openapi.json (OpenAPI 3.1). Auth scopes: a publishable key goes in X-Triggair-Key; a player token and the developer/operator sessions go in Authorization: Bearer.

Players

POST /v1/players/anonymous publishable key

Mint an anonymous player token

Exchanges a stable device id for a 24h player token. The player is created on first use. This is what the SDK's tg.login() calls; every player-scoped call sends the returned token as `Authorization: Bearer`.

request
{
  "device_id": "a-stable-device-uuid",
  "turnstile_token": "optional-if-challenge-on"
}
response · A banned account or device is refused with 403 player_banned.
{
  "player_id": "p_1a2b3c",
  "token": "eyJ…",
  "expires_in": 86400
}
POST /v1/players/recover publishable key

Recover a player on a new device

Consumes a recovery code (from /v1/players/me/recovery-code) to rebind the same player to a new device, returning a fresh token.

request
{
  "code": "RECOVERY-CODE",
  "device_id": "new-device-uuid"
}
response
{
  "player_id": "p_1a2b3c",
  "token": "eyJ…",
  "expires_in": 86400
}
GET /v1/players/me player token

Get the current player

response
{
  "player_id": "p_1a2b3c",
  "display_name": null,
  "created_at": "2026-07-01T00:00:00Z"
}
POST /v1/players/me/recovery-code player token

Mint a cross-device recovery code

Returns a single-use code the player saves/shares once; redeem it later with /v1/players/recover on another device.

response
{
  "code": "RECOVERY-CODE",
  "expires_at": "2026-08-01T00:00:00Z"
}
GET /v1/players/me/moderation player token

Get the caller's own ban/mute status

The player's active bans and restrictions (shadow actions are hidden from self-view).

response
{
  "banned": false,
  "restrictions": []
}
GET /v1/players/me/stats player token

Get the player's own stats

response
{
  "stats": [
    {
      "key": "kills",
      "value": 12
    }
  ]
}
POST /v1/players/me/stats player token

Update stats (set / increment)

Applies one or more stat operations atomically. Stats back leaderboards, achievements, and segment targeting.

request
{
  "ops": [
    {
      "key": "kills",
      "op": "increment",
      "value": 1
    }
  ]
}
response
{
  "stats": [
    {
      "key": "kills",
      "value": 13
    }
  ]
}
PATCH /v1/players/me player token

Update the player's profile (e.g. display name)

request
{
  "display_name": "AceRunner"
}
response
{
  "player_id": "p_1a2b3c",
  "display_name": "AceRunner"
}
GET /v1/players player token

Look up players (public fields)

  • ids (query) — Comma-separated player ids.
response
{
  "players": [
    {
      "player_id": "p_9",
      "display_name": "Rival"
    }
  ]
}
GET /v1/players/:id player token

Get another player's public profile

  • id (path) — Player id.
response
{
  "player_id": "p_9",
  "display_name": "Rival"
}

Saves

GET /v1/saves player token

List the player's save slots

response
{
  "saves": [
    {
      "slot": "slot1",
      "version": 3,
      "updated_at": "2026-07-01T00:00:00Z"
    }
  ]
}
GET /v1/saves/:slot player token

Read a save slot

  • slot (path) — Slot name (game-defined).
response
{
  "slot": "slot1",
  "data": {
    "level": 4,
    "coins": 120
  },
  "version": 3
}
PUT /v1/saves/:slot player token

Write a save slot

Last-write-wins by default. Send `If-Match: <version>` for optimistic concurrency — a stale version returns 409 save_conflict. Returns 201 on first write, 200 on update.

  • slot (path) — Slot name.
request
{
  "data": {
    "level": 5,
    "coins": 90
  }
}
response
{
  "slot": "slot1",
  "data": {
    "level": 5,
    "coins": 90
  },
  "version": 4
}
DELETE /v1/saves/:slot player token

Delete a save slot

  • slot (path) — Slot name.
response · 204 No Content.
null

Leaderboards

POST /v1/leaderboards/:board/scores player token

Submit a score

Submits a score to a configured board. The board's aggregation (best/last/sum) and period (all-time/daily/weekly) are set by the developer; the response reports the player's kept score for the current period.

  • board (path) — Board key.
request
{
  "score": 9000
}
response
{
  "ok": true,
  "best_score": 9000,
  "period_key": "2026-07"
}
GET /v1/leaderboards/:board/top player token

Read the top entries

  • board (path) — Board key.
  • limit (query) — Max entries (default 10).
response
{
  "board": "high_scores",
  "period_key": "2026-07",
  "entries": [
    {
      "rank": 1,
      "player_id": "p_9",
      "score": 12000
    }
  ]
}
GET /v1/leaderboards/:board/around-me player token

Read entries around the player

  • board (path) — Board key.
response
{
  "board": "high_scores",
  "period_key": "2026-07",
  "me": {
    "rank": 42,
    "score": 8000
  },
  "entries": []
}
GET /v1/leaderboards/:board/friends player token

Read the friends-only board

  • board (path) — Board key.
response
{
  "board": "high_scores",
  "period_key": "2026-07",
  "me": {
    "rank": 2,
    "score": 8000
  },
  "entries": []
}

Achievements

GET /v1/achievements player token

List achievements + the caller's progress

Definitions plus this player's progress; secret achievements are hidden until unlocked.

response
{
  "achievements": [
    {
      "key": "first_win",
      "unlocked": true,
      "progress": 1,
      "target": 1
    }
  ]
}
POST /v1/achievements/:key/progress player token

Report achievement progress

The server clamps progress, unlocks exactly once, and escrows any reward into the inbox. The client reports progress but never grants the reward.

  • key (path) — Achievement key.
request
{
  "amount": 1
}
response
{
  "key": "first_win",
  "unlocked": true,
  "progress": 1
}

Daily

GET /v1/daily player token

Get daily-reward status

response
{
  "claimable": true,
  "streak": 3,
  "next_claim_at": "2026-07-11T00:00:00Z"
}
POST /v1/daily/claim player token

Claim today's reward

Server-day gated; the reward is escrowed into the inbox. Re-claiming the same day → 409 conflict.

response
{
  "claimed": true,
  "streak": 4
}

Inbox

GET /v1/inbox player token

List inbox items

  • limit (query) — Max items.
response
{
  "items": [
    {
      "id": "in_1",
      "kind": "reward",
      "claimable": true,
      "read": false
    }
  ]
}
POST /v1/inbox/:id/read player token

Mark an inbox item read

  • id (path) — Inbox item id.
response
{
  "ok": true
}
POST /v1/inbox/:id/claim player token

Claim an inbox item's reward

The single hardened grant path — exactly-once (idempotent per item). Grants currency, items, or stat rewards.

  • id (path) — Inbox item id.
response
{
  "claimed": true,
  "granted": {
    "currency": [
      {
        "code": "gold",
        "amount": 100
      }
    ]
  }
}

Social

GET /v1/friends player token

List friends

response
{
  "friends": [
    {
      "player_id": "p_9",
      "display_name": "Rival",
      "state": "accepted"
    }
  ]
}
GET /v1/friends/requests player token

List incoming friend requests

response
{
  "requests": [
    {
      "player_id": "p_7",
      "display_name": "Newcomer"
    }
  ]
}
POST /v1/friends/:id player token

Send or accept a friend request

  • id (path) — The other player's id.
response
{
  "state": "pending"
}
DELETE /v1/friends/:id player token

Remove a friend / cancel a request

  • id (path) — The other player's id.
response · 204 No Content.
null
POST /v1/friends/:id/block player token

Block a player

  • id (path) — The player to block.
response
{
  "blocked": true
}
POST /v1/share player token

Mint a share / invite link

Creates a short code carrying an opaque context blob (a level, a challenge, a referral). Resolve it with GET /v1/share/:code on the recipient's device.

request
{
  "context": {
    "level": 7
  },
  "expires_in_seconds": 604800
}
response
{
  "code": "SH4RE",
  "expires_at": "2026-07-18T00:00:00Z"
}
GET /v1/share/:code player token

Resolve a share link

  • code (path) — Share code.
response
{
  "context": {
    "level": 7
  },
  "from": "p_1a2b3c"
}

Moderation

POST /v1/moderate/check player token

Pre-check text before submit

Runs the same moderation the write paths use, so you can validate a chosen name/message without a round-trip failure. Stateless (writes nothing).

request
{
  "surface": "username",
  "text": "player name to check"
}
response
{
  "verdict": "mask",
  "masked_text": "player ****",
  "categories": [
    "profanity"
  ],
  "severity": 2
}
POST /v1/reports player token

Report a player / message / UGC

request
{
  "target_type": "player",
  "target_id": "p_9",
  "reason": "harassment",
  "note": "optional"
}
response
{
  "id": "cr_1",
  "state": "open"
}
POST /v1/appeals player token

Appeal a ban

request
{
  "ban_id": "bn_1",
  "body": "why the ban should be lifted"
}
response
{
  "id": "ap_1",
  "state": "pending"
}

Compliance

POST /v1/players/me/age player token

Set the player's age bracket

Neutral age screen. A birth year is mapped to a bracket and DISCARDED (no DOB stored). Returns the compliance view with the gated-feature map.

request
{
  "birth_year": 2013
}
response
{
  "bracket": "13_15",
  "consent_state": "pending",
  "gated": {
    "open_chat": true,
    "lootbox": true
  }
}
GET /v1/players/me/compliance player token

Get the compliance view (bracket + gated map)

response
{
  "bracket": "13_15",
  "consent_state": "pending",
  "gated": {
    "open_chat": true
  }
}
GET /v1/compliance/policy publishable key

Get the game's gate policy (pre-token)

pk-only so the client can pre-disable regulated UI before a player token exists.

response
{
  "gates": {
    "open_chat": "13_15",
    "lootbox": "adult"
  },
  "coppa_mode": false,
  "default_jurisdiction": "US"
}
POST /v1/players/me/consent/request player token

Request parental consent (emails the parent)

request
{
  "parent_email": "parent@example.com"
}
response
{
  "id": "pc_1",
  "state": "pending",
  "expires_at": "2026-07-24T00:00:00Z"
}
GET /v1/players/me/consent player token

Get the player's consent record

response
{
  "consent": {
    "id": "pc_1",
    "state": "pending"
  }
}
GET /v1/consent/:token no auth

Parent consent landing page (HTML)

The signed link a parent receives by email; renders Approve/Decline. No pk/token — the signed token IS the credential.

  • token (path) — Signed consent token from the email.
POST /v1/consent/:token/decide no auth

Record the parent's decision

  • token (path) — Signed consent token.
request
{
  "grant": true,
  "note": "optional"
}
response
{
  "state": "granted"
}

Crashes

POST /v1/crashes player token

Report a crash

Grouped server-side by a normalized-stack fingerprint into a handful of issues with a crash-free-users %.

request
{
  "message": "TypeError: x is undefined",
  "stack": "at play (game.js:42)",
  "platform": "web",
  "appVersion": "1.4.0"
}
response
{
  "ok": true,
  "group_id": "cg_1"
}

RNG

GET /v1/rng/:stream player token

Deterministic server-seeded random values

A verifiable per-player, per-period stream — the same request returns the same values, so loot/crit rolls can't be client-forged.

  • stream (path) — Stream name (e.g. 'loot').
  • count (query) — How many values.
response
{
  "stream": "loot",
  "period_key": "2026-07",
  "values": [
    0.42,
    0.88
  ]
}

Realtime

GET /v1/realtime/rooms/:room player token

Join a realtime room (WebSocket upgrade)

A WebSocket upgrade. Because a browser WS can't send headers, the pk + player token go in the query (?key=&token=). Presence + broadcast; chat is moderated in transit. Rooms named team:<id> / match:<id> require membership. Use tg.realtime.join(room) from the SDK.

  • room (path) — Room name; team:/match: prefixes enforce membership.
  • key (query) — Publishable key (query, since a WS can't set headers).
  • token (query) — Player token.

Economy

GET /v1/wallet player token

Get all currency balances

response
{
  "balances": [
    {
      "currency": "gold",
      "amount": 250
    }
  ]
}
GET /v1/wallet/history player token

Get currency transaction history

  • limit (query) — Max lines.
response
{
  "lines": [
    {
      "currency": "gold",
      "delta": -100,
      "reason": "store_buy",
      "created_at": "2026-07-01T00:00:00Z"
    }
  ]
}
GET /v1/wallet/:currency player token

Get one currency's balance

  • currency (path) — Currency code.
response
{
  "currency": "gold",
  "amount": 250
}
GET /v1/stores player token

List stores

response
{
  "stores": [
    {
      "key": "main_store",
      "name": "Shop"
    }
  ]
}
GET /v1/stores/:key player token

Get a store's listings

  • key (path) — Store key.
response
{
  "key": "main_store",
  "listings": [
    {
      "id": "l_1",
      "item": "sword",
      "price": {
        "gold": 100
      }
    }
  ]
}
POST /v1/stores/:key/buy player token

Buy a store listing

Server-authoritative price + balance check; idempotent (pass idem). Fails with insufficient_funds / out_of_stock / store_limit_reached.

  • key (path) — Store key.
request
{
  "listing_id": "l_1",
  "idem": "uuid"
}
response
{
  "ok": true,
  "granted": {
    "items": [
      {
        "item": "sword",
        "qty": 1
      }
    ]
  }
}
GET /v1/inventory player token

List the player's inventory

response
{
  "items": [
    {
      "item": "sword",
      "qty": 1,
      "equipped": true
    }
  ]
}
POST /v1/inventory/:item/consume player token

Consume an item

  • item (path) — Item id.
request
{
  "qty": 1,
  "idem": "uuid"
}
response
{
  "ok": true,
  "remaining": 0
}
POST /v1/inventory/:item/equip player token

Equip an item

  • item (path) — Item id.
response
{
  "ok": true,
  "equipped": true
}
POST /v1/inventory/:item/unequip player token

Unequip an item

  • item (path) — Item id.
response
{
  "ok": true,
  "equipped": false
}
GET /v1/loot/:key/odds player token

Get a loot box's disclosed odds

  • key (path) — Loot box key.
response
{
  "key": "bronze_box",
  "odds": [
    {
      "item": "common",
      "weight": 0.9
    },
    {
      "item": "rare",
      "weight": 0.1
    }
  ]
}
POST /v1/loot/:key/open player token

Open a loot box

Server-rolled (verifiable RNG), idempotent. loot_not_enabled if the box isn't configured; age-gated where required.

  • key (path) — Loot box key.
request
{
  "idem": "uuid"
}
response
{
  "rolled": [
    {
      "item": "rare",
      "qty": 1
    }
  ]
}
GET /v1/energy player token

List energy meters

response
{
  "meters": [
    {
      "meter": "stamina",
      "current": 4,
      "max": 5,
      "refill_at": "2026-07-10T01:00:00Z"
    }
  ]
}
GET /v1/energy/:meter player token

Get one energy meter

  • meter (path) — Meter name.
response
{
  "meter": "stamina",
  "current": 4,
  "max": 5
}
POST /v1/energy/:meter/spend player token

Spend energy

out_of_energy if the balance is insufficient. Idempotent.

  • meter (path) — Meter name.
request
{
  "amount": 1,
  "idem": "uuid"
}
response
{
  "meter": "stamina",
  "current": 3
}
POST /v1/energy/:meter/refill player token

Refill energy (e.g. with a currency)

  • meter (path) — Meter name.
request
{
  "idem": "uuid"
}
response
{
  "meter": "stamina",
  "current": 5
}

Teams

POST /v1/teams player token

Create a team

request
{
  "name": "Alpha Squad",
  "tag": "ALPHA",
  "privacy": "open"
}
response
{
  "id": "tm_1",
  "name": "Alpha Squad",
  "tag": "ALPHA"
}
GET /v1/teams player token

List / search teams

  • q (query) — Search query.
response
{
  "teams": [
    {
      "id": "tm_1",
      "name": "Alpha Squad",
      "member_count": 4
    }
  ]
}
GET /v1/teams/mine player token

Get the caller's team

response
{
  "team": {
    "id": "tm_1",
    "role": "owner"
  }
}
GET /v1/teams/leaderboards/:board player token

Team leaderboard

  • board (path) — Board key.
response
{
  "entries": [
    {
      "team_id": "tm_1",
      "score": 5000
    }
  ]
}
GET /v1/teams/:id player token

Get a team + members

  • id (path) — Team id.
response
{
  "id": "tm_1",
  "name": "Alpha Squad",
  "members": [
    {
      "player_id": "p_1",
      "role": "owner"
    }
  ]
}
POST /v1/teams/:id/join player token

Join a team (open teams)

  • id (path) — Team id.
response
{
  "ok": true,
  "role": "member"
}
POST /v1/teams/:id/leave player token

Leave a team

  • id (path) — Team id.
response
{
  "ok": true
}
POST /v1/teams/:id/members/:pid/kick player token

Kick a member (admin/owner)

  • id (path) — Team id.
  • pid (path) — Member player id.
response
{
  "ok": true
}
POST /v1/teams/:id/members/:pid/role player token

Change a member's role

  • id (path) — Team id.
  • pid (path) — Member player id.
request
{
  "role": "admin"
}
response
{
  "ok": true
}
POST /v1/teams/:id/transfer player token

Transfer ownership

  • id (path) — Team id.
request
{
  "to": "p_2"
}
response
{
  "ok": true
}
POST /v1/teams/:id/disband player token

Disband a team (owner)

  • id (path) — Team id.
response
{
  "ok": true
}
GET /v1/teams/mine/invites player token

List the caller's team invites

response
{
  "invites": [
    {
      "id": "ti_1",
      "team_id": "tm_1"
    }
  ]
}
POST /v1/teams/:id/invites player token

Invite a player

  • id (path) — Team id.
request
{
  "player_id": "p_7"
}
response
{
  "id": "ti_1",
  "state": "pending"
}
POST /v1/teams/invites/:iid/accept player token

Accept an invite

  • iid (path) — Invite id.
response
{
  "ok": true
}
POST /v1/teams/invites/:iid/reject player token

Reject an invite

  • iid (path) — Invite id.
response
{
  "ok": true
}
POST /v1/teams/:id/requests player token

Request to join (closed teams)

  • id (path) — Team id.
response
{
  "id": "tr_1",
  "state": "pending"
}
GET /v1/teams/:id/requests player token

List join requests (admin)

  • id (path) — Team id.
response
{
  "requests": [
    {
      "id": "tr_1",
      "player_id": "p_7"
    }
  ]
}
POST /v1/teams/:id/requests/:rid/approve player token

Approve a join request

  • id (path) — Team id.
  • rid (path) — Request id.
response
{
  "ok": true
}
POST /v1/teams/:id/requests/:rid/reject player token

Reject a join request

  • id (path) — Team id.
  • rid (path) — Request id.
response
{
  "ok": true
}
POST /v1/teams/:id/members/:pid/ban player token

Ban a member from the team

  • id (path) — Team id.
  • pid (path) — Player id.
response
{
  "ok": true
}
POST /v1/teams/:id/members/:pid/unban player token

Unban a player

  • id (path) — Team id.
  • pid (path) — Player id.
response
{
  "ok": true
}
GET /v1/teams/:id/bans player token

List team bans

  • id (path) — Team id.
response
{
  "bans": [
    {
      "player_id": "p_9"
    }
  ]
}

Competition

GET /v1/tournaments player token

List tournaments

response
{
  "tournaments": [
    {
      "id": "to_1",
      "title": "Weekend Cup",
      "state": "live"
    }
  ]
}
GET /v1/tournaments/mine player token

Tournaments the player has joined

response
{
  "tournaments": []
}
GET /v1/tournaments/:id player token

Get a tournament

  • id (path) — Tournament id.
response
{
  "id": "to_1",
  "title": "Weekend Cup",
  "starts_at": "2026-07-12T00:00:00Z"
}
GET /v1/tournaments/:id/standings player token

Get tournament standings

  • id (path) — Tournament id.
response
{
  "entries": [
    {
      "rank": 1,
      "player_id": "p_9",
      "score": 12000
    }
  ]
}
POST /v1/tournaments/:id/join player token

Join a tournament

tournament_not_open if entry is closed; may require an entry fee.

  • id (path) — Tournament id.
response
{
  "ok": true
}
GET /v1/tournaments/:id/me player token

The player's tournament entry

  • id (path) — Tournament id.
response
{
  "rank": 42,
  "score": 8000
}
POST /v1/leagues/:key/join player token

Join a league

  • key (path) — League key.
response
{
  "division": "bronze",
  "tier": 3
}
GET /v1/leagues/:key/me player token

The player's league standing

  • key (path) — League key.
response
{
  "division": "bronze",
  "tier": 3,
  "rank": 5
}
GET /v1/leagues/:key/divisions/:tier/top player token

Top of a league division

  • key (path) — League key.
  • tier (path) — Division tier.
response
{
  "entries": [
    {
      "rank": 1,
      "player_id": "p_9"
    }
  ]
}
POST /v1/boards/:board/submit player token

Submit to a keyed board (team / UGC / custom entity)

  • board (path) — Keyed board key.
request
{
  "entity_id": "tm_1",
  "score": 5000
}
response
{
  "ok": true,
  "best_score": 5000
}
GET /v1/boards/:board/top player token

Top entries of a keyed board

  • board (path) — Keyed board key.
response
{
  "entries": [
    {
      "rank": 1,
      "entity_id": "tm_1",
      "score": 5000
    }
  ]
}
GET /v1/boards/:board/entries/:entity_id player token

One entity's keyed-board entry

  • board (path) — Keyed board key.
  • entity_id (path) — Entity id (team/UGC/…).
response
{
  "entity_id": "tm_1",
  "score": 5000,
  "rank": 1
}

Progression

GET /v1/quests player token

List quests + the caller's progress

response
{
  "quests": [
    {
      "key": "daily_login",
      "progress": 1,
      "target": 1,
      "claimable": true
    }
  ]
}
POST /v1/quests/:key/claim player token

Claim a completed quest's reward

quest_not_complete if objectives aren't met; the reward is escrowed into the inbox.

  • key (path) — Quest key.
response
{
  "claimed": true
}
GET /v1/battle-pass/:season player token

Get battle-pass tiers + the caller's progress

  • season (path) — Season key.
response
{
  "season": "s1",
  "tier": 4,
  "xp": 1200,
  "premium": false
}
POST /v1/battle-pass/:season/claim player token

Claim a battle-pass tier reward

tier_not_earned if not reached; premium_required for a premium lane without the pass.

  • season (path) — Season key.
request
{
  "tier": 4,
  "lane": "free"
}
response
{
  "claimed": true
}
GET /v1/progression player token

Get XP / level curve progress

response
{
  "level": 7,
  "xp": 3400,
  "next_level_xp": 4000
}

Storage

GET /v1/storage/:collection player token

List keys in the player's collection

  • collection (path) — Collection key.
response
{
  "keys": [
    "settings",
    "deck"
  ]
}
GET /v1/storage/:collection/:key player token

Read a collection entry

  • collection (path) — Collection key.
  • key (path) — Entry key.
response
{
  "key": "settings",
  "data": {
    "sfx": true
  },
  "version": 2
}
PUT /v1/storage/:collection/:key player token

Write a collection entry

Optional If-Match for OCC (storage_conflict on a stale version).

  • collection (path) — Collection key.
  • key (path) — Entry key.
request
{
  "data": {
    "sfx": false
  }
}
response
{
  "key": "settings",
  "version": 3
}
DELETE /v1/storage/:collection/:key player token

Delete a collection entry

  • collection (path) — Collection key.
  • key (path) — Entry key.
response · 204 No Content.
null
POST /v1/storage/:collection/:key/mutate player token

Atomic server-side mutation of an entry

Apply structured ops (e.g. list append, counter add) server-side to avoid read-modify-write races.

  • collection (path) — Collection key.
  • key (path) — Entry key.
request
{
  "ops": [
    {
      "op": "append",
      "path": "deck",
      "value": "card_9"
    }
  ]
}
response
{
  "key": "deck",
  "version": 5
}
GET /v1/storage/shared/:collection/:key player token

Read a shared (game-wide) entry

  • collection (path) — Collection key.
  • key (path) — Entry key.
response
{
  "key": "motd",
  "data": {
    "text": "Welcome!"
  }
}
PUT /v1/storage/shared/:collection/:key player token

Write a shared entry (per the collection's write policy)

  • collection (path) — Collection key.
  • key (path) — Entry key.
request
{
  "data": {
    "text": "Welcome!"
  }
}
response
{
  "key": "motd",
  "version": 1
}
GET /v1/storage/team/:teamId/:collection player token

List a team collection's keys (members only)

  • teamId (path) — Team id.
  • collection (path) — Collection key.
response
{
  "keys": [
    "roster"
  ]
}
GET /v1/storage/team/:teamId/:collection/:key player token

Read a team entry

  • teamId (path) — Team id.
  • collection (path) — Collection key.
  • key (path) — Entry key.
response
{
  "key": "roster",
  "data": {}
}
PUT /v1/storage/team/:teamId/:collection/:key player token

Write a team entry (members only)

  • teamId (path) — Team id.
  • collection (path) — Collection key.
  • key (path) — Entry key.
request
{
  "data": {}
}
response
{
  "key": "roster",
  "version": 1
}
DELETE /v1/storage/team/:teamId/:collection/:key player token

Delete a team entry

  • teamId (path) — Team id.
  • collection (path) — Collection key.
  • key (path) — Entry key.
response · 204 No Content.
null

UGC

GET /v1/ugc player token

Browse published content

  • sort (query) — e.g. top, new.
response
{
  "items": [
    {
      "id": "ug_1",
      "title": "My Level",
      "plays": 42,
      "rating": 4.5
    }
  ]
}
GET /v1/ugc/mine player token

List the caller's content

response
{
  "items": [
    {
      "id": "ug_1",
      "title": "My Level",
      "status": "published"
    }
  ]
}
POST /v1/ugc player token

Create a draft

Title/content are moderated. Publish it with POST /v1/ugc/:id/submit.

request
{
  "title": "My Level",
  "data": {
    "grid": []
  }
}
response
{
  "id": "ug_1",
  "status": "draft"
}
GET /v1/ugc/:id player token

Get a content item

  • id (path) — Content id.
response
{
  "id": "ug_1",
  "title": "My Level",
  "data": {},
  "author": "p_1"
}
PATCH /v1/ugc/:id player token

Update a draft

  • id (path) — Content id.
request
{
  "title": "My Level v2"
}
response
{
  "id": "ug_1"
}
DELETE /v1/ugc/:id player token

Delete content

  • id (path) — Content id.
response · 204 No Content.
null
POST /v1/ugc/:id/submit player token

Publish a draft

  • id (path) — Content id.
response
{
  "id": "ug_1",
  "status": "published"
}
POST /v1/ugc/:id/play player token

Record a play

  • id (path) — Content id.
response
{
  "ok": true
}
GET /v1/ugc/:id/lineage player token

Get remix lineage (attribution)

  • id (path) — Content id.
response
{
  "ancestors": [
    {
      "id": "ug_0",
      "author": "p_0"
    }
  ]
}
POST /v1/ugc/:id/remix player token

Remix content (forks with attribution)

  • id (path) — Source content id.
request
{
  "title": "My Remix"
}
response
{
  "id": "ug_2",
  "remixed_from": "ug_1"
}
POST /v1/ugc/:id/rate player token

Rate content

  • id (path) — Content id.
request
{
  "rating": 5
}
response
{
  "ok": true,
  "rating": 4.6
}
POST /v1/ugc/:id/like player token

Like content

  • id (path) — Content id.
response
{
  "ok": true,
  "likes": 13
}
DELETE /v1/ugc/:id/like player token

Unlike content

  • id (path) — Content id.
response
{
  "ok": true,
  "likes": 12
}

Async

POST /v1/async player token

Create a turn-based match

No realtime/Durable Object needed — turns are submitted over HTTP with server-enforced turn order + OCC.

request
{
  "type": "chess",
  "opponents": [
    "p_9"
  ]
}
response
{
  "match": {
    "id": "am_1",
    "state": {},
    "turn": "p_1a2b3c"
  }
}
GET /v1/async/mine player token

List the player's active matches

response
{
  "matches": [
    {
      "id": "am_1",
      "type": "chess",
      "turn": "p_9"
    }
  ]
}
GET /v1/async/:id player token

Get a match (participants only)

  • id (path) — Match id.
response
{
  "match": {
    "id": "am_1",
    "state": {},
    "turn_number": 4
  }
}
POST /v1/async/:id/turn player token

Submit a turn

Rejected with not_your_turn out of order, or async_conflict on a stale version (send the version you read).

  • id (path) — Match id.
request
{
  "state": {
    "board": []
  },
  "version": 4,
  "end": false
}
response
{
  "match": {
    "id": "am_1",
    "turn_number": 5
  }
}
POST /v1/async/:id/forfeit player token

Forfeit a match

  • id (path) — Match id.
response
{
  "match": {
    "id": "am_1",
    "status": "complete",
    "winner": "p_9"
  }
}

LiveOps

GET /v1/config publishable key

Get the game's published config blob

The developer's key→value tuning blob (server-controlled, no client deploy to change).

response
{
  "config": {
    "spawn_rate": 1.5,
    "event_banner": "summer"
  },
  "version": 12
}
GET /v1/flags player token

Get feature flags resolved for the player

Each flag resolves per the player's segments, with a break-glass kill switch honoured.

response
{
  "flags": {
    "new_hud": true,
    "checkout_v2": "variant_b"
  }
}
GET /v1/flags/:key player token

Get one resolved flag value

  • key (path) — Flag key.
response
{
  "key": "new_hud",
  "value": true
}
GET /v1/liveops/events/live player token

Get currently-live events

response
{
  "events": [
    {
      "key": "summer_fest",
      "ends_at": "2026-08-01T00:00:00Z"
    }
  ]
}
GET /v1/players/me/segments player token

Get the player's segment memberships

response
{
  "segments": [
    "whales",
    "new_players"
  ]
}

Analytics

POST /v1/events player token

Ingest analytics events

Usually called via the SDK's durable outbox (tg.track), which coalesces + retries. Counts only — no per-player PII.

request
{
  "events": [
    {
      "name": "level_complete",
      "count": 1
    }
  ]
}
response
{
  "accepted": 1
}

Dev · Games

POST /v1/dev/games developer session

Create a game

request
{
  "name": "Neon Drift"
}
response
{
  "id": "g_1",
  "name": "Neon Drift",
  "tier": "shared",
  "env": "prod"
}
GET /v1/dev/games developer session

List your games

response
{
  "games": [
    {
      "id": "g_1",
      "name": "Neon Drift",
      "status": "active"
    }
  ]
}
GET /v1/dev/games/:id developer session

Get a game

  • id (path) — Game id.
response
{
  "id": "g_1",
  "name": "Neon Drift",
  "allowed_origins": [
    "https://mygame.com"
  ]
}
PATCH /v1/dev/games/:id developer session

Update a game (name, CORS allowlist, pause)

Set allowed_origins (CORS) — an empty list is open to any origin. status paused takes the game offline.

  • id (path) — Game id.
request
{
  "allowed_origins": [
    "https://mygame.com"
  ],
  "status": "active"
}
response
{
  "id": "g_1",
  "allowed_origins": [
    "https://mygame.com"
  ]
}
DELETE /v1/dev/games/:id developer session

Delete a game (and its keys/data)

  • id (path) — Game id.
response · 204 No Content.
null

Dev · Keys

POST /v1/dev/games/:id/keys developer session

Issue an API key

The full secret is returned ONCE. tg_pk_ (publishable) is safe in clients; tg_sk_ (secret) is server-only.

  • id (path) — Game id.
request
{
  "kind": "publishable"
}
response
{
  "id": "key_1",
  "prefix": "tg_pk_ab12",
  "key": "tg_pk_ab12…full-shown-once",
  "note": "Store this now — it won't be shown again."
}
GET /v1/dev/games/:id/keys developer session

List key metadata (never the secret)

  • id (path) — Game id.
response
{
  "keys": [
    {
      "id": "key_1",
      "kind": "publishable",
      "prefix": "tg_pk_ab12",
      "last_used_at": "2026-07-01T00:00:00Z",
      "revoked_at": null
    }
  ]
}
POST /v1/dev/games/:id/keys/:keyId/rotate developer session

Rotate a key (revoke + mint a replacement)

  • id (path) — Game id.
  • keyId (path) — Key id.
response
{
  "id": "key_2",
  "key": "tg_pk_cd34…full-shown-once"
}
DELETE /v1/dev/games/:id/keys/:keyId developer session

Revoke a key

  • id (path) — Game id.
  • keyId (path) — Key id.
response · 204 No Content.
null