Copy-paste integration recipes
Each recipe is a self-contained snippet you (or your agent) can lift verbatim. They are the same building blocks that assemble llms-full.txt.
Install & initialize
npm i @triggair/sdk
import { createClient } from '@triggair/sdk';
const tg = createClient({ key: 'tg_pk_your_key' });
Only the publishable key (tg_pk_) is needed. NEVER put a secret key (tg_sk_) in client code — it belongs on a server. Player identity (anonymous-first)
const { playerId } = await tg.login();
An anonymous player token is minted from a device id on first use and silently refreshed. Every player-scoped call attaches it automatically — no accounts, no login screen required. Cloud saves
await tg.saves.put('slot1', { level: 4 }); // last-write-wins
await tg.saves.put('slot1', data, { ifMatch: v }); // conflict-safe (throws save_conflict)
const { data } = await tg.saves.get('slot1');
tg.saves.queue('slot1', data); // durable/offline (replays on reconnect) Leaderboards
Configure a board once (dashboard → Leaderboards, or triggair_configure_leaderboard), then:
await tg.leaderboards.submit('high_scores', 9000);
const { entries } = await tg.leaderboards.top('high_scores', { limit: 10 });
Keyed boards (per-team / per-UGC-item) live on tg.keyedBoards. Player stats & profile
await tg.stats.update([{ key: 'kills', op: 'increment', value: 1 }]);
const stats = await tg.stats.get(); // the player's own stats
Stats back leaderboards, achievements, and segment targeting. Achievements & daily rewards
await tg.achievements.report('first_win', 1); // reward lands in the inbox on unlock
const status = await tg.daily.status();
if (status.claimable) await tg.daily.claim(); // server-day gated; streaks tracked
All rewards are collected from tg.inbox — the one hardened grant path. Inbox & reward claims
const items = await tg.inbox.list();
for (const it of items) if (it.claimable) await tg.inbox.claim(it.id);
Claiming is exactly-once (idempotent per item) — the single audited path that grants currency, items, or stat rewards. Economy: currency, store, inventory
const wallet = await tg.economy.wallet();
await tg.economy.buy('main_store', listingId, { idem: uuid }); // server-authoritative price + balance
const items = await tg.economy.inventory();
await tg.economy.equip(itemId);
Grants are server-side and idempotent (pass idem); insufficient_funds when the player can't afford it. Loot boxes & energy
const odds = await tg.economy.loot.odds('bronze_box'); // disclosed drop rates
const result = await tg.economy.loot.open('bronze_box', { idem: uuid });
await tg.economy.energy.spend('stamina', 1, { idem: uuid }); // regenerating meter
Loot/IAP are age-gated where required — see the age-gate recipe. Remote config, flags & live events
const cfg = await tg.config.get(); // your published key→value blob
const live = await tg.config.liveEvents();
Feature flags (tg.flags) resolve per-segment with a break-glass kill switch; edit config/flags in the dashboard (Setup → Config, Operations → LiveOps) or via MCP — no client deploy. Moderation (names, chat, UGC)
const v = await tg.moderation.check('username', name); // allow | mask | block | review
if (v.verdict === 'block') { /* reject */ }
await tg.moderation.report('player', targetId, 'harassment');
const me = await tg.moderation.myStatus(); // the caller's bans/mutes (shadow hidden)
Run moderation BEFORE anything is visible — it defeats leet/homoglyph evasion. Age gate & parental consent (compliance)
await tg.compliance.setAge({ birthYear: 2013 }); // mapped to a bracket + discarded (no DOB stored)
const view = await tg.compliance.status(); // { bracket, consent_state, gated: {feature:bool} }
if (view.gated.open_chat) { /* disable in UI; the server also enforces it */ }
await tg.compliance.requestConsent('parent@email'); // emails the parent a signed decision link
Gate regulated features (chat, loot/IAP) with view.gated; the server is the enforcement. Analytics (durable & offline)
tg.track('level_complete');
Events are coalesced and queued in a durable outbox; they survive a dropped connection and flush on reconnect. Counts + funnels + retention + sessions roll up nightly — view them in the dashboard Analytics tab. No per-player PII. Realtime rooms (presence & chat)
const room = await tg.realtime.join('lobby'); // authed WebSocket; resolves on connect
room.on('presence', ({ members }) => renderRoster(members));
room.on('message', ({ from, data }) => renderChat(from, data));
room.send({ text: 'gg' }); // fan-out to everyone else in the room
room.close();
Presence + broadcast over a Durable Object; `room.members` is the live roster; `room.history` replays recent chat on join. Chat text is moderated in transit (mask/block) by the game's policy. Private rooms: join('team:<teamId>') requires team membership, join('match:<matchId>') requires being a participant (non-members get 403); any other name is public. Node/tests pass a WebSocket via createClient({ webSocket }). Crash reporting
try { /* game loop */ } catch (e) {
tg.crashes.report(e.message, { stack: e.stack, appVersion: '1.4.0' });
}
Crashes are grouped server-side by a normalized-stack fingerprint into a handful of issues with a crash-free-users %. Turn-based matches
const { match } = await tg.asyncMatch.create({ type: 'chess', opponents: [otherId] });
await tg.asyncMatch.turn(match.id, { state: next, version: match.version });
Server-enforced turn order (not_your_turn) + OCC (async_conflict). No realtime connection needed — players move whenever. Storage collections
await tg.storage.put('decks', 'main', { cards: [] });
const { data } = await tg.storage.get('decks', 'main');
await tg.storage.incr('stats', 'wins', 1); // atomic, avoids read-modify-write races
Scopes: player (private), shared (game-wide), team (members only). Configure collections in the dashboard. Promo codes & gifts
await tg.codes.redeem('LAUNCH2026'); // grants the campaign reward into the inbox
await tg.economy.gifts.send(friendId, { item: 'sword', qty: 1 }, { idem: uuid });
code_invalid / code_expired / code_already_redeemed guard redemption. Tournaments (scheduled, prize tables)
const opens = await tg.tournaments.list(); // status: scheduled | running | closed
await tg.tournaments.join(t.id); // may charge entry_fee → { joined, reason?, fee_txn }
await tg.leaderboards.submit(t.board, score); // you score via the tournament's own board
const top = await tg.tournaments.standings(t.id, { limit: 20 });
const me = await tg.tournaments.me(t.id); // { entered, rank, prize }
Prizes land in the inbox when the operator closes it. Configure schedule + reward_table in the dashboard/MCP. Leagues (tiered divisions, promotion/relegation)
await tg.leagues.join('ranked'); // placed in the lowest division for the season (idempotent)
const me = await tg.leagues.me('ranked'); // { division, division_name, rank, zone: promoting|safe|relegating }
const standings = await tg.leagues.divisionTop('ranked', me.division);
Players score through tg.leaderboards.submit (the league ranks that board); promotion/relegation runs on the operator-driven season advance. Teams & clans
const { team } = await tg.teams.create('Night Owls', 'OWL', { privacy: 'invite_only' });
await tg.teams.join(teamId); // open teams; invite_only → tg.teams.requestJoin(teamId)
await tg.teams.invite(teamId, playerId); // admin/owner; recipient calls tg.teams.acceptInvite(inviteId)
const t = await tg.teams.get(teamId); // team + roster
const board = await tg.teams.leaderboard('weekly', { agg: 'sum' }); // members' scores aggregated
Admin actions are role-gated server-side: setRole · kick · transfer · ban · disband. Errors: team_forbidden (role), team_full (cap), conflict (tag taken). Keyed leaderboards (per-entity boards)
await tg.keyedBoards.submit('level_times', levelId, 42.5); // score keyed to any entity (a UGC level, a team, a map)
const top = await tg.keyedBoards.top('level_times', { limit: 10 });
const mine = await tg.keyedBoards.entry('level_times', levelId);
Use these when the ranked subject isn't the player — one board namespace, many entities. Configure the board in the dashboard. Battle pass & quests
const bp = await tg.battlePass.get('s3'); // { bp, tier, claimed lanes }
await tg.battlePass.claim('s3', tier, 'premium'); // reward → inbox; tier_not_earned / premium_required guard it
const quests = await tg.quests.list(); // progress per objective
await tg.quests.claim(questKey); // when complete → reward lands in the inbox
Progress is driven by tg.stats.update / achievements; all rewards are collected from tg.inbox. Player segments
const segments = await tg.segments.mine(); // [{ key, segment_id, source }]
Segments materialize from stats/behaviour and are DEFINED in the dashboard (authoring, not client). When a player token is present they automatically personalize tg.config.get() and tg.flags — read mine() only if you want to branch UI on membership. User-generated content (create, publish, remix)
const { item } = await tg.ugc.create('level', { title: 'Sky Temple', payload, allow_remix: true });
await tg.ugc.submit(item.id); // runs moderation → published or held for review
const feed = await tg.ugc.browse({ sort: 'popular' }); // new | top | popular
await tg.ugc.play(id); // debounced play count; unlocks rating
await tg.ugc.rate(id, 5); await tg.ugc.like(id);
const { item: fork } = await tg.ugc.remix(id); // fork a remix-allowed item → new draft with lineage/attribution
const tree = await tg.ugc.lineage(id); // parent (remix_of) + root_id + remixes More domains, same shape
Every domain is configured in the dashboard/MCP, then called on the typed SDK client with the same error contract: tg.progression (level curve/XP) · tg.storage (collections) · tg.codes (promo codes) · tg.asyncMatch (turn-based) · tg.crashes · tg.rng. See /docs/guides for the narrative walkthrough of each. CORS: works locally, 403s when deployed
Browser calls are origin-checked. If a call fails only once deployed, add your deployed origin to the game's allowed_origins (dashboard → Setup → Keys/CORS, or triggair_set_allowed_origins). An empty allowlist = open to any origin — set it before you ship. Recovery (cross-device)
const { code } = await tg.mintRecoveryCode(); // show/share once
await tg.recover(code); // same anonymous player on a new device Errors are actionable
Every failure throws a TriggairError with { code, message, agentHint, requestId }. Read agentHint — it tells you how to fix the call. rate_limited/5xx/network retry automatically. See /docs/errors/<code>. Verify your integration
After wiring the SDK, run the MCP tool triggair_verify_integration (or the dashboard's verify runner). It live-probes player + saves + leaderboards and advises on CORS, with pass/fail per service and fix hints — closing the loop.