Docs/API Reference

API Reference

Complete endpoint reference for https://api.katforge.com. Every path, method, request payload, response shape, and error code is documented.

Prefer the @katforge/api SDK over raw fetch — it handles token refresh, typed errors, and platform-specific cookie handling. The raw API is for languages the SDK doesn't target.

Groups

GroupPurpose
GatewayLogin, logout, refresh, guest sessions, password reset, SSO
OAuthDiscord / Google / Apple / Steam flows, plus the OAuth 2.0 server
UsersRegistration, profile, password, phone, avatar
AvatarsPlayer avatar rendering and icon catalog
API KeysRealm-scoped machine credentials
RealmsRealm metadata and branding overrides
TokensToken validation and cross-domain passports
SystemHealth check, mailing list, contact
Embers, Ingots & WalletEarned currency, paid currency, awards, ledgers
CodexPublic manifest endpoints for game data
StumperTrivia game: lobbies, questions, runs, leaderboards
LextrisWord game: result submission, leaderboards
Gear GoblinsIdle RPG: characters, state, news
DarkerDBDark and Darker data: items, monsters, maps, loot, merchant, leaderboards, population

Conventions

Base URL

text
https://api.katforge.com

All endpoints are under /v1. The API is versioned; /v1 is the only supported version today.

Authentication

Most endpoints accept (or require) a bearer token:

http
Authorization: Bearer <access_token>

Access tokens come from POST /v1/gateway/login, POST /v1/gateway/guest, POST /v1/users, or any OAuth flow. See Authentication for the token lifecycle.

Each endpoint in the reference declares its auth requirement:

  • Public — no token needed.
  • Authenticated — any valid access token (guest or registered).
  • ROLE_REGISTERED — requires a full (non-guest) account.

Response envelope

Every JSON response is wrapped in an envelope. The controller's return value is placed under body; transient messages from the request land under flash.

JSON
{
   "version": "0.0.14",
   "code": 200,
   "status": "OK",
   "env": "prod",
   "debug": false,
   "elapsed": 0.0142,
   "timestamp": "2026-04-18T12:00:00Z",
   "meta": {
      "method": "GET",
      "path": "/v1/users/@me"
   },
   "body": {
      "user": { "id": 42, "username": "anders" }
   }
}

Response examples in this reference show the body payload only. The envelope is always present.

Responses that don't carry a body (like POST /v1/gateway/logout) omit the body field entirely.

Errors

Errors use the same envelope. The status code is in code, and a human-readable message plus machine-readable error code lands in flash.errors.

JSON
{
   "code": 401,
   "status": "Unauthorized",
   "flash": {
      "errors": [
         { "code": "auth:invalid", "message": "Invalid credentials" }
      ]
   }
}

Validation failures (422) surface per-field problems in flash.violations:

JSON
{
   "code": 422,
   "flash": {
      "violations": [
         { "propertyPath": "email", "title": "This value is not a valid email address." }
      ]
   }
}

Rate-limit responses (429) attach a Retry-After header and a flash.errors[].params.retry_after value.

See Errors & Limits for recovery patterns.

Status codes

CodeMeaning
200OK
201Created
204No Content
301 / 302Redirect
400Bad request
401Authentication required or invalid
403Forbidden
404Not found
409Conflict (e.g. duplicate username)
422Validation failed (see flash.violations)
423Account temporarily locked
429Rate limited (see Retry-After)
5xxServer error

Rate limiting

Per-IP throttles are applied to write endpoints. Each endpoint in the reference lists its limit as limit / window (e.g. 30 / 60s = 30 requests per 60 seconds). Exceeding the limit returns 429 with a Retry-After header in seconds.

Field conventions

  • created_at, updated_at, *_at → ISO 8601 UTC (2026-04-18T12:00:00+00:00).
  • is_* → boolean.
  • num_* → integer count.
  • *_id → foreign key (integer).
  • All JSON field names are snake_case on the wire, even when the DTO uses camelCase internally.