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
| Code | Meaning | Retry? |
|---|---|---|
200 | OK | — |
201 | Created | — |
204 | No Content | — |
301 / 302 | Redirect | — |
400 | Bad request | Fix the request |
401 | Unauthorized | Refresh the token |
403 | Forbidden | Never |
404 | Not found | Never |
409 | Conflict | Never |
422 | Validation failed | Fix the input |
429 | Rate limited | After Retry-After seconds |
5xx | Server error | Exponential 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:
| Endpoint | Limit |
|---|---|
POST /v1/gateway/login | 10 / 60s |
POST /v1/gateway/guest | 60 / 60s |
POST /v1/users | 10 / 60s |
POST /v1/gateway/reset-password/* | 5 / 60s |
POST /v1/games/stumper/questions/generate | 30 / 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');
}
}