Skip to content

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

FieldTypeDescription
errorstringHuman-readable error message
codestringMachine-readable error code
detailsobjectAdditional error context (optional)

HTTP Status Codes

Client Errors (4xx)

StatusDescription
400Bad Request - Invalid parameters
401Unauthorized - Authentication required
403Forbidden - Insufficient permissions
404Not Found - Resource doesn’t exist
422Unprocessable Entity - Validation failed
429Too Many Requests - Rate limited

Server Errors (5xx)

StatusDescription
500Internal Server Error
502Bad Gateway - Upstream error
503Service Unavailable - Temporarily down
504Gateway Timeout - Request timeout

Error Codes

General Errors

CodeStatusDescription
NOT_FOUND404Resource not found
VALIDATION_ERROR400Invalid input parameters
INTERNAL_ERROR500Unexpected server error

Authentication Errors

CodeStatusDescription
UNAUTHORIZED401No authentication provided
INVALID_TOKEN401Token is invalid
TOKEN_EXPIRED401Token has expired
FORBIDDEN403Insufficient permissions

Resource Errors

CodeStatusDescription
SERVER_NOT_FOUND404Discord server not synced
CHANNEL_NOT_FOUND404Channel doesn’t exist or isn’t a forum
THREAD_NOT_FOUND404Thread doesn’t exist
USER_NOT_FOUND404User not found in database

Validation Errors

CodeStatusDescription
MISSING_PARAMETER400Required parameter not provided
INVALID_PARAMETER400Parameter value is invalid
INVALID_CURSOR400Pagination 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;
}
}
// Usage
try {
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 display
function 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()
# Usage
try:
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} />;
}
// ...
}