Errors
Every error response is JSON. The shape depends on the status code.
Standard error
For 400, 401, 403, 404, 409, 429, and 5xx:
{
"message": "Invalid credentials"
}
That's the whole body. The HTTP status code is the source of truth; the message is human-readable text intended for logs and debugging — never display it directly to end users without translation.
Validation errors
For 422 Unprocessable Entity, the body includes a violations array:
{
"message": "Validation failed",
"violations": [
{
"propertyPath": "email",
"message": "Invalid email format"
},
{
"propertyPath": "password",
"message": "Password must be at least 8 characters"
}
]
}
The propertyPath matches the request DTO field name and can be used to highlight the offending input.
Status codes
| Code | Meaning | When to retry |
|---|---|---|
200 | OK | — |
201 | Created | — |
204 | No Content (e.g. DELETE success) | — |
302 | Redirect (OAuth callbacks, customs) | — |
400 | Bad request — missing or malformed parameters | Never (fix the request) |
401 | Unauthorized — missing, invalid, or expired token | After refreshing the token |
403 | Forbidden — token valid but action not allowed | Never |
404 | Not found | Never |
409 | Conflict — resource exists or constraint violated | Never |
422 | Validation failed | Never (fix the input) |
429 | Rate limited | After Retry-After seconds |
5xx | Server error | With exponential backoff |
Rate limit headers
429 responses include Retry-After (in seconds). The SDK respects this header automatically.
Error handling with the SDK
The SDK throws typed exceptions for each category:
import {
AuthenticationError,
AuthorizationError,
ValidationError,
ConflictError,
NotFoundError,
RateLimitError,
NetworkError
} from '@katforge/api';
try {
await katforge.users.create ({ email, username, password });
} catch (err) {
if (err instanceof ValidationError) {
// err.violations is the parsed array
err.violations.forEach (v => showFieldError (v.propertyPath, v.message));
} else if (err instanceof ConflictError) {
showError ('Username or email already taken');
} else if (err instanceof RateLimitError) {
showError (`Too many attempts. Try again in ${err.retryAfter}s.`);
} else if (err instanceof NetworkError) {
showError ('Could not reach the server. Check your connection.');
} else {
throw err;
}
}
All error classes extend KATforgeError, which extends the built-in Error.
Token refresh
AuthenticationError (i.e. 401) is special. The SDK intercepts it, attempts to refresh the access token, and replays the original request once. You only see this exception if the refresh itself fails — at which point the user has to log in again.
You should treat any AuthenticationError that escapes the SDK as "session over" and trigger a re-login flow.