Docs/Errors & Limits

Errors & Limits

Error shape

Every response uses the same envelope. code mirrors the HTTP status; error details live under flash.errors.

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

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

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

See the API Reference overview for the full envelope.

Status codes

CodeMeaningRetry?
200OK
201Created
204No Content
301 / 302Redirect
400Bad requestFix the request
401UnauthorizedRefresh the token
403ForbiddenNever
404Not foundNever
409ConflictNever
422Validation failedFix the input
429Rate limitedAfter Retry-After seconds
5xxServer errorExponential backoff

Rate limiting

Most write endpoints are throttled per IP. Limits are documented per-operation in the API Reference via the x-throttle extension. Common limits:

EndpointLimit
POST /v1/gateway/login10 / 60s
POST /v1/gateway/guest60 / 60s
POST /v1/users10 / 60s
POST /v1/gateway/reset-password/*5 / 60s
POST /v1/games/stumper/questions/generate30 / 60s

The 429 response includes a Retry-After header. The @katforge/api's RateLimitError exposes it as err.retryAfter.

@katforge/api error handling

TypeScript
import { ValidationError, RateLimitError, NetworkError } from '@katforge/api';

try {
   await katforge.users.create ({ email, username, password });
} catch (err) {
   if (err instanceof ValidationError) {
      err.violations.forEach (v => showFieldError (v.propertyPath, v.message));
   } else if (err instanceof RateLimitError) {
      showError (`Try again in ${err.retryAfter}s`);
   } else if (err instanceof NetworkError) {
      showError ('Check your connection');
   }
}