Docs/@katforge/api

@katforge/api

The official TypeScript @katforge/api. Wraps every API endpoint as a fluent namespace, handles JWT refresh, and types every error. Runs in browsers, Capacitor, Node, Bun, Deno.

shell
npm install @katforge/api

Instantiate

TypeScript
import { KATforge, browserStorage } from '@katforge/api';

const katforge = new KATforge ({
   baseUrl: 'https://api.katforge.com',
   storage: browserStorage,
   games:   [ 'lextris', 'goblins', 'stumper' ]
});

Namespace tree

NamespacePurposeKey methods
.authAuthentication, OAuth, guest sessions, refreshlogin, logout, guest, refresh, upgrade, passport, oauthUrl, oauth.{list,link,unlink}
.usersProfile, password, avatar, account lifecycleme, create, check, update, changePassword, delete, updateAvatar, avatarCatalog
.codexGame data manifests and entitiespreload, get, has, awards.{list,get}, game(name)
.walletBoth currencies + level info in one fetchme
.embersEarned currency, awards, ledgerbalance, awards.{list,grant,has}, ledger
.ingotsPaid currency and ledgerbalance, ledger
.games.lextrisLextris results and leaderboardsubmitResult, leaderboard
.games.goblinsGear Goblins characters and statecharacters.{list,create,get,update}, news, state
.games.stumperStumper questions, lobbies, leaderboardcategories, generateQuestions, createLobby, joinLobby, leaderboard, startEndless

.apiBase is a getter that returns the configured base URL (e.g. 'https://api.katforge.com') — useful when constructing asset URLs by hand.

Auth lifecycle in one example

A complete login → call → refresh → logout cycle. The SDK handles refresh transparently on 401; the explicit refresh() call is rarely needed.

TypeScript
import { KATforge, browserStorage, AuthenticationError } from '@katforge/api';

const katforge = new KATforge ({
   baseUrl: 'https://api.katforge.com',
   storage: browserStorage
});

// Hydrate from cached refresh cookie if present.
await katforge.init ();

if (! katforge.player) {
   await katforge.auth.login ('anders@example.com', 'hunter2');
}

try {
   const me = await katforge.users.me ();   // SDK auto-attaches the access token
   console.log ('hello', me.display_name);
} catch (err) {
   if (err instanceof AuthenticationError) {
      // refresh failed too — fall back to the login modal
   }
}

await katforge.auth.logout ();   // clears tokens, fires auth:change

Avatars

updateAvatar writes the caller's avatar selection; avatarCatalog fetches the full icon manifest minus globally-claimed icons. apiBase returns the configured base URL, useful for constructing image URLs by hand. See platform: avatars for the full story and SparkAvatar for the drop-in renderer.

TypeScript
// Claim an icon. Throws ConflictError (409) if already taken.
await katforge.users.updateAvatar ({
   source: 'custom',
   icon:   'lorc/dragon-head',
   color:  'cyan',
   bg:     'crimson'
});

// Switch to a linked provider's avatar
await katforge.users.updateAvatar ({ source: 'discord' });

// Reset to auto-generated
await katforge.users.updateAvatar ({ source: null });

// Picker catalog — 4k+ icons, minus any already claimed
const { icons, palettes, total, taken_count } = await katforge.users.avatarCatalog ();

Every Player payload includes a ready-to-use avatar_url (with ?v= cache-buster) and a structured avatar object for the dashboard. You almost never construct these URLs by hand.

Wallet — Embers & Ingots

katforge.embers, katforge.ingots, and katforge.wallet wrap the Embers reference endpoints. See the Currency platform doc for the conceptual model.

TypeScript
// Cheapest path for both currencies + level info in one fetch
const wallet = await katforge.wallet.me ();
wallet.embers.level.name;     // 'Tinder'
wallet.ingots.balance;        // 0

// Per-currency
const embers = await katforge.embers.balance ();
const ingots = await katforge.ingots.balance ();

// Awards
const earned = await katforge.embers.awards.list ();
const result = await katforge.embers.awards.grant ({
   game:     'lextris',
   award_id: 'id.award.first_word'
});
result.granted;          // true on first earn, false on idempotent repeat
result.wallet.balance;   // post-grant balance

// Ledger pagination
const page1 = await katforge.embers.ledger ({ limit: 25 });
const page2 = await katforge.embers.ledger ({ limit: 25, before: page1.entries.at (-1)?.id });

Mutations emit events so reactive UI (Spark composables) refetches without polling:

TypeScript
katforge.on ('embers:change', (balance) => { /* current EmberBalance */ });
katforge.on ('ingots:change', (balance) => { /* current IngotBalance */ });

Codex — game data

katforge.codex fetches manifests from /v1/codex/{game}/manifest.json and caches them in memory. See the codex docs for the architecture and the JS SDK reference for the full surface.

TypeScript
// Bootstrap the data layer for a game
await katforge.codex.preload ('lextris');

// Generic lookup
const def = katforge.codex.get ('lextris', 'id.award.first_word');

// Typed accessor
const award = katforge.codex.awards.get ('lextris', 'id.award.first_word');
award.data.ember_value;   // typed number

// Game-scoped surface (cleaner inside one game's UI code)
const lextris = katforge.codex.game ('lextris');
await lextris.preload ();
lextris.awards.list ();

Pagination

Every paginated endpoint returns a PaginatedResponse:

TypeScript
// Iterate all
for await (const entry of katforge.games.lextris.leaderboard ({ mode: 'daily' })) { ... }

// Single page
const page = await katforge.games.lextris.leaderboard ({ mode: 'daily' }).page ();

// Collect
const all = await katforge.games.lextris.leaderboard ({ mode: 'daily' }).all ();

Storage backends

BackendUse case
browserStoragelocalStorage (default for web)
createMemoryStorage ()In-memory (tests, SSR)
createCapacitorStorage (SecureStorage)iOS Keychain / Android EncryptedSharedPreferences

Errors

All extend KATforgeError. On a 401, the SDK refreshes the access token and replays the request once. If the retry also fails, it throws AuthenticationError.

TypeScript
AuthenticationError   // 401
AuthorizationError    // 403
NotFoundError         // 404
ConflictError         // 409
ValidationError       // 422 (has .violations)
RateLimitError        // 429 (has .retryAfter)
NetworkError          // transport failure