Skip to main content

Error Handling

The BookWish API uses conventional HTTP status codes and returns detailed error information in JSON format to help you understand and resolve issues.

Error Response Format

All error responses follow a consistent structure:

{
"error": "ErrorCode",
"message": "Human-readable error description",
"details": []
}
FieldTypeDescription
errorstringMachine-readable error code
messagestringHuman-readable error description
detailsarray/objectOptional additional error information

HTTP Status Codes

The API uses standard HTTP status codes to indicate the success or failure of requests:

Success Codes (2xx)

CodeStatusDescription
200OKRequest succeeded
201CreatedResource successfully created
204No ContentRequest succeeded with no response body

Client Error Codes (4xx)

CodeStatusDescription
400Bad RequestInvalid request format or missing required fields
401UnauthorizedMissing or invalid authentication
403ForbiddenAuthenticated but not authorized for this action
404Not FoundResource does not exist
409ConflictRequest conflicts with existing data
422Unprocessable EntityRequest is well-formed but semantically invalid
429Too Many RequestsRate limit exceeded

Server Error Codes (5xx)

CodeStatusDescription
500Internal Server ErrorUnexpected server error
502Bad GatewayUpstream service error
503Service UnavailableService temporarily unavailable

Error Categories

Authentication Errors (401)

Authentication failures when credentials or tokens are invalid:

Missing Authorization Header

{
"error": "Unauthorized",
"message": "Missing or invalid authorization header"
}

Cause: Request missing Authorization header or header not in Bearer {token} format.

Solution: Include valid Bearer token in Authorization header.

Invalid or Expired Token

{
"error": "Unauthorized",
"message": "Invalid or expired token"
}

Cause: JWT token is malformed, expired, or signed with wrong secret.

Solution: Refresh access token using refresh token endpoint.

User Not Found

{
"error": "Unauthorized",
"message": "User not found"
}

Cause: Token is valid but user account no longer exists.

Solution: User must re-authenticate or create new account.

Invalid Credentials

{
"error": "InvalidCredentials",
"message": "Invalid credentials"
}

Cause: Email or password incorrect during login.

Solution: Verify credentials and retry.

Token Revoked

{
"error": "InvalidToken",
"message": "Token is invalid or expired"
}

Cause: Refresh token has been revoked (user logged out).

Solution: User must log in again.

Validation Errors (400)

Input validation failures with detailed error information:

{
"error": "ValidationError",
"message": "Request validation failed",
"details": [
{
"path": "email",
"message": "Invalid email format"
},
{
"path": "password",
"message": "String must contain at least 8 character(s)"
}
]
}

Common Validation Rules:

FieldValidationError Message
emailValid email format"Invalid email format"
passwordMinimum 8 characters"String must contain at least 8 character(s)"
display_name1-100 characters"String must contain at least 1 character(s)"
username3-50 chars, alphanumeric + underscore"Username must contain only letters, numbers, and underscores"
bookIdRequired"bookId is required"
contentMax 1000 characters"Content must be 1000 characters or less"

Resource Not Found (404)

Requested resource does not exist:

{
"error": "NotFound",
"message": "User not found"
}

Common Not Found Errors:

ResourceError Message
User"User not found"
Book"Book not found"
Store"Store not found"
Line"Line not found"
Inventory Item"Inventory item not found"
Order"Order not found"
Club"Club not found"
Challenge"Challenge not found"

Conflict Errors (409)

Request conflicts with existing data:

Email Already Exists

{
"error": "EmailConflict",
"message": "Email already in use"
}

Cause: Attempting to register with email that's already registered.

Solution: Use different email or log in with existing account.

Username Already Taken

{
"error": "UsernameConflict",
"message": "Username already taken"
}

Cause: Attempting to use username that's already taken.

Solution: Choose a different username.

Business Logic Errors (400, 403)

Errors related to business rules and permissions:

Not a Guest Account

{
"error": "BadRequest",
"message": "User is not a guest"
}

Cause: Attempting to migrate a full account as if it were a guest.

Solution: This action only applies to guest accounts.

Not Store Owner

{
"error": "Forbidden",
"message": "Only store owners can access this resource"
}

Cause: Attempting to access store management features without ownership.

Solution: User must own the store to perform this action.

Insufficient Permissions

{
"error": "Forbidden",
"message": "Insufficient permissions"
}

Cause: User tier does not allow this action.

Solution: Upgrade user tier or request appropriate permissions.

Rate Limiting Errors (429)

Rate limit exceeded errors come in two forms:

Temporary Rate Limit

{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please try again in 45 seconds.",
"retryAfter": 45000,
"limit": 60,
"remaining": 0
}

Cause: Exceeded per-minute or per-hour rate limit.

Solution: Wait for retryAfter milliseconds before retrying.

Monthly Limit (Free Tier)

{
"error": "monthly_limit_reached",
"message": "Free accounts are limited to 30 posts per month. Upgrade to Premium for unlimited posts.",
"limit": 30,
"current": 30,
"resetsAt": "2025-01-01T00:00:00.000Z",
"upgradeUrl": "/upgrade"
}

Cause: Free tier user exceeded monthly post limit.

Solution: Upgrade to Premium or wait until monthly reset.

See Rate Limiting for detailed information.

Server Errors (500)

Internal server errors indicate unexpected issues:

{
"error": "InternalServerError",
"message": "An unexpected error occurred"
}

In Development Mode:

Server errors include stack traces in the details field for debugging:

{
"error": "InternalServerError",
"message": "Database query failed",
"details": "Error: connect ECONNREFUSED 127.0.0.1:5432\n at TCPConnectWrap.afterConnect..."
}

Note: Stack traces are never exposed in production.

Error Handling Best Practices

1. Check Status Codes

Always check the HTTP status code first:

const response = await fetch('https://api.bookwish.app/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});

if (!response.ok) {
const error = await response.json();
console.error(`Error ${response.status}:`, error);
// Handle error
}

2. Handle Specific Error Codes

Handle specific error codes for better UX:

async function login(email, password) {
try {
const response = await fetch('https://api.bookwish.app/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});

if (!response.ok) {
const error = await response.json();

switch (error.error) {
case 'InvalidCredentials':
throw new Error('Email or password is incorrect');

case 'ValidationError':
throw new Error('Please check your email and password format');

case 'rate_limit_exceeded':
throw new Error(`Too many attempts. Try again in ${error.retryAfter / 1000}s`);

default:
throw new Error('Login failed. Please try again.');
}
}

return response.json();
} catch (error) {
console.error('Login error:', error);
throw error;
}
}

3. Display Validation Errors

Show detailed validation errors to users:

if (error.error === 'ValidationError' && error.details) {
error.details.forEach(detail => {
// Show error next to the relevant form field
showFieldError(detail.path, detail.message);
});
}

4. Retry Transient Errors

Implement retry logic for transient errors (5xx, network failures):

async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);

// Success or client error (don't retry)
if (response.ok || response.status < 500) {
return response;
}

// Server error - retry with backoff
if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
} catch (error) {
// Network error - retry
if (attempt === maxRetries - 1) throw error;
}
}
}

5. Log Errors for Debugging

Always log errors with context for debugging:

async function createPost(content) {
try {
const response = await fetch('https://api.bookwish.app/lines', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(content)
});

if (!response.ok) {
const error = await response.json();

// Log with context
console.error('Failed to create post', {
status: response.status,
error: error.error,
message: error.message,
userId: getCurrentUserId(),
timestamp: new Date().toISOString()
});

throw error;
}

return response.json();
} catch (error) {
console.error('Network error creating post:', error);
throw error;
}
}

Common Error Scenarios

Scenario 1: User Not Authenticated

Request:

GET /users/me

Response: 401 Unauthorized

{
"error": "Unauthorized",
"message": "Missing or invalid authorization header"
}

Solution: Include valid access token in Authorization header.


Scenario 2: Token Expired During Request

Request:

GET /wishlists
Authorization: Bearer expired_token

Response: 401 Unauthorized

{
"error": "Unauthorized",
"message": "Invalid or expired token"
}

Solution: Refresh access token using refresh token, then retry request.


Scenario 3: Invalid Input Format

Request:

POST /auth/signup
{
"email": "not-an-email",
"password": "short",
"display_name": ""
}

Response: 400 Bad Request

{
"error": "ValidationError",
"message": "Request validation failed",
"details": [
{
"path": "email",
"message": "Invalid email"
},
{
"path": "password",
"message": "String must contain at least 8 character(s)"
},
{
"path": "display_name",
"message": "String must contain at least 1 character(s)"
}
]
}

Solution: Fix validation errors and resubmit.


Scenario 4: Resource Not Found

Request:

GET /users/nonexistent-username

Response: 404 Not Found

{
"error": "NotFound",
"message": "User not found"
}

Solution: Verify resource ID/identifier is correct.


Scenario 5: Rate Limit Exceeded

Request:

POST /lines (61st request in one minute)

Response: 429 Too Many Requests

{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please try again in 30 seconds.",
"retryAfter": 30000,
"limit": 60,
"remaining": 0
}

Solution: Wait for retryAfter milliseconds before retrying.

Error Code Reference

Error CodeHTTP StatusMeaning
Unauthorized401Authentication required or failed
InvalidCredentials401Login credentials incorrect
InvalidToken401Token invalid, expired, or revoked
ValidationError400Request validation failed
BadRequest400Invalid request format or business rule violation
Forbidden403User lacks permission for this action
NotFound404Resource does not exist
EmailConflict409Email already registered
UsernameConflict409Username already taken
rate_limit_exceeded429Temporary rate limit exceeded
monthly_limit_reached429Monthly post limit exceeded (free tier)
InternalServerError500Unexpected server error

Getting Help

If you encounter persistent errors or need clarification: