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
| Group | Purpose |
|---|---|
| Gateway | Login, logout, refresh, guest sessions, password reset, SSO |
| OAuth | Discord / Google / Apple / Steam flows, plus the OAuth 2.0 server |
| Users | Registration, profile, password, phone, avatar |
| Avatars | Player avatar rendering and icon catalog |
| API Keys | Realm-scoped machine credentials |
| Realms | Realm metadata and branding overrides |
| Tokens | Token validation and cross-domain passports |
| System | Health check, mailing list, contact |
| Embers, Ingots & Wallet | Earned currency, paid currency, awards, ledgers |
| Codex | Public manifest endpoints for game data |
| Stumper | Trivia game: lobbies, questions, runs, leaderboards |
| Lextris | Word game: result submission, leaderboards |
| Gear Goblins | Idle RPG: characters, state, news |
| DarkerDB | Dark and Darker data: items, monsters, maps, loot, merchant, leaderboards, population |
Conventions
Base URL
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:
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.
{
"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.
{
"code": 401,
"status": "Unauthorized",
"flash": {
"errors": [
{ "code": "auth:invalid", "message": "Invalid credentials" }
]
}
}
Validation failures (422) surface per-field problems in flash.violations:
{
"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
| Code | Meaning |
|---|---|
200 | OK |
201 | Created |
204 | No Content |
301 / 302 | Redirect |
400 | Bad request |
401 | Authentication required or invalid |
403 | Forbidden |
404 | Not found |
409 | Conflict (e.g. duplicate username) |
422 | Validation failed (see flash.violations) |
423 | Account temporarily locked |
429 | Rate limited (see Retry-After) |
5xx | Server 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.