@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.
npm install @katforge/api
Instantiate
import { KATforge, browserStorage } from '@katforge/api';
const katforge = new KATforge ({
baseUrl: 'https://api.katforge.com',
storage: browserStorage,
games: [ 'lextris', 'goblins', 'stumper' ]
});
Namespace tree
| Namespace | Purpose | Key methods |
|---|---|---|
.auth | Authentication, OAuth, guest sessions, refresh | login, logout, guest, refresh, upgrade, passport, oauthUrl, oauth.{list,link,unlink} |
.users | Profile, password, avatar, account lifecycle | me, create, check, update, changePassword, delete, updateAvatar, avatarCatalog |
.codex | Game data manifests and entities | preload, get, has, awards.{list,get}, game(name) |
.wallet | Both currencies + level info in one fetch | me |
.embers | Earned currency, awards, ledger | balance, awards.{list,grant,has}, ledger |
.ingots | Paid currency and ledger | balance, ledger |
.games.lextris | Lextris results and leaderboard | submitResult, leaderboard |
.games.goblins | Gear Goblins characters and state | characters.{list,create,get,update}, news, state |
.games.stumper | Stumper questions, lobbies, leaderboard | categories, 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.
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.
// 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.
// 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:
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.
// 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:
// 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
| Backend | Use case |
|---|---|
browserStorage | localStorage (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.
AuthenticationError // 401
AuthorizationError // 403
NotFoundError // 404
ConflictError // 409
ValidationError // 422 (has .violations)
RateLimitError // 429 (has .retryAfter)
NetworkError // transport failure