Error Handling
All API errors follow a consistent format. This guide covers error responses and how to handle them.
Error Response Format
All errors return this JSON structure:
{ "error": "Human-readable error message", "code": "ERROR_CODE", "details": {}}Fields
| Field | Type | Description |
|---|---|---|
error | string | Human-readable error message |
code | string | Machine-readable error code |
details | object | Additional error context (optional) |
HTTP Status Codes
Client Errors (4xx)
| Status | Description |
|---|---|
| 400 | Bad Request - Invalid parameters |
| 401 | Unauthorized - Authentication required |
| 403 | Forbidden - Insufficient permissions |
| 404 | Not Found - Resource doesn’t exist |
| 422 | Unprocessable Entity - Validation failed |
| 429 | Too Many Requests - Rate limited |
Server Errors (5xx)
| Status | Description |
|---|---|
| 500 | Internal Server Error |
| 502 | Bad Gateway - Upstream error |
| 503 | Service Unavailable - Temporarily down |
| 504 | Gateway Timeout - Request timeout |
Error Codes
General Errors
| Code | Status | Description |
|---|---|---|
NOT_FOUND | 404 | Resource not found |
VALIDATION_ERROR | 400 | Invalid input parameters |
INTERNAL_ERROR | 500 | Unexpected server error |
Authentication Errors
| Code | Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | No authentication provided |
INVALID_TOKEN | 401 | Token is invalid |
TOKEN_EXPIRED | 401 | Token has expired |
FORBIDDEN | 403 | Insufficient permissions |
Resource Errors
| Code | Status | Description |
|---|---|---|
SERVER_NOT_FOUND | 404 | Discord server not synced |
CHANNEL_NOT_FOUND | 404 | Channel doesn’t exist or isn’t a forum |
THREAD_NOT_FOUND | 404 | Thread doesn’t exist |
USER_NOT_FOUND | 404 | User not found in database |
Validation Errors
| Code | Status | Description |
|---|---|---|
MISSING_PARAMETER | 400 | Required parameter not provided |
INVALID_PARAMETER | 400 | Parameter value is invalid |
INVALID_CURSOR | 400 | Pagination cursor is invalid |
Error Examples
Not Found
{ "error": "Thread not found", "code": "THREAD_NOT_FOUND"}Validation Error
{ "error": "Invalid limit parameter: must be between 1 and 100", "code": "INVALID_PARAMETER", "details": { "parameter": "limit", "value": 500, "min": 1, "max": 100 }}Authentication Error
{ "error": "Authentication required", "code": "UNAUTHORIZED"}Rate Limit
{ "error": "Too many requests, please slow down", "code": "RATE_LIMITED", "details": { "retryAfter": 60 }}Handling Errors
JavaScript/TypeScript
interface APIError { error: string; code: string; details?: Record<string, unknown>;}
async function fetchAPI<T>(url: string): Promise<T> { const response = await fetch(url);
if (!response.ok) { const error: APIError = await response.json(); throw new APIRequestError(error, response.status); }
return response.json();}
class APIRequestError extends Error { constructor( public readonly apiError: APIError, public readonly status: number ) { super(apiError.error); this.name = 'APIRequestError'; }
get code() { return this.apiError.code; }}
// Usagetry { const thread = await fetchAPI('/api/threads/123456');} catch (error) { if (error instanceof APIRequestError) { switch (error.code) { case 'NOT_FOUND': console.log('Thread not found'); break; case 'UNAUTHORIZED': console.log('Please log in'); break; default: console.error('API error:', error.message); } }}React Error Handling
function useAPI(url) { const [data, setData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true);
useEffect(() => { async function fetchData() { try { setLoading(true); setError(null); const response = await fetch(url);
if (!response.ok) { const apiError = await response.json(); setError(apiError); return; }
const result = await response.json(); setData(result); } catch (err) { setError({ error: 'Network error', code: 'NETWORK_ERROR' }); } finally { setLoading(false); } }
fetchData(); }, [url]);
return { data, error, loading };}
// Usage with error displayfunction ThreadPage({ threadId }) { const { data, error, loading } = useAPI(`/api/threads/${threadId}`);
if (loading) return <Spinner />;
if (error) { return <ErrorDisplay error={error} />; }
return <Thread data={data} />;}
function ErrorDisplay({ error }) { const messages = { NOT_FOUND: 'This content could not be found.', UNAUTHORIZED: 'Please log in to view this content.', FORBIDDEN: "You don't have permission to view this.", INTERNAL_ERROR: 'Something went wrong. Please try again later.', };
return ( <div className="error-container"> <h2>Error</h2> <p>{messages[error.code] || error.error}</p> {error.code === 'UNAUTHORIZED' && ( <button onClick={() => redirectToLogin()}>Log In</button> )} </div> );}Python
import requests
class APIError(Exception): def __init__(self, message, code, status, details=None): super().__init__(message) self.code = code self.status = status self.details = details or {}
def fetch_api(url): response = requests.get(url)
if not response.ok: error = response.json() raise APIError( error['error'], error['code'], response.status_code, error.get('details') )
return response.json()
# Usagetry: thread = fetch_api('http://localhost:3000/api/threads/123')except APIError as e: if e.code == 'NOT_FOUND': print('Thread not found') elif e.code == 'RATE_LIMITED': print(f'Rate limited, retry after {e.details.get("retryAfter")} seconds') else: print(f'Error: {e}')Retry Logic
For transient errors, implement retry logic:
async function fetchWithRetry(url, maxRetries = 3) { let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) { try { const response = await fetch(url);
if (response.ok) { return response.json(); }
const error = await response.json();
// Don't retry client errors (except rate limits) if (response.status < 500 && response.status !== 429) { throw new APIRequestError(error, response.status); }
// Handle rate limiting if (response.status === 429) { const retryAfter = error.details?.retryAfter || 60; await delay(retryAfter * 1000); continue; }
lastError = new APIRequestError(error, response.status); } catch (err) { if (err instanceof APIRequestError) throw err; lastError = err; }
// Exponential backoff await delay(Math.pow(2, attempt) * 1000); }
throw lastError;}
function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms));}Best Practices
1. Always Check Response Status
const response = await fetch(url);if (!response.ok) { // Handle error}2. Display User-Friendly Messages
Map error codes to user-friendly messages rather than showing raw API errors.
3. Log Errors for Debugging
if (error.code === 'INTERNAL_ERROR') { console.error('API Error:', error); // Send to error tracking service Sentry.captureException(error);}4. Handle Network Errors
try { const response = await fetch(url);} catch (err) { // Network error (offline, DNS failure, etc.) showNotification('Network error. Check your connection.');}5. Graceful Degradation
function ThreadList({ serverId }) { const { data, error } = useAPI(`/api/threads?serverId=${serverId}`);
if (error && error.code === 'INTERNAL_ERROR') { return <CachedThreadList serverId={serverId} />; }
// ...}