Guides

Guide - Common Error Responses

Standard HTTP error codes, error response formats, and how to handle them.

The Booki API uses standard HTTP status codes and consistent error response formats. This guide helps you understand and handle errors.


Error Response Format

All errors follow this standard structure:

{
  "statusCode": 400,
  "message": "Validation failed",
  "error": "ValidationError",
  "details": [
    {
      "field": "email",
      "message": "Invalid email format"
    }
  ]
}

Fields:

  • statusCode (number): HTTP status code
  • message (string): Human-readable error description
  • error (string): Error type/category
  • details (array, optional): Field-level validation errors

HTTP Status Codes

200 OK

Request succeeded. Response contains data.

{ "user": {...} }

201 Created

Resource successfully created.

{
  "statusCode": 201,
  "message": "Booking created successfully",
  "booking": {...}
}

204 No Content

Request succeeded but no data to return (e.g., successful deletion).


4xx Client Errors

400 Bad Request

Client sent invalid data (malformed JSON, validation failure, etc.).

Causes:

  • Missing required field
  • Invalid data type
  • Field length exceeded
  • Invalid email format
  • Weak password

Example: Missing email

{
  "statusCode": 400,
  "message": "Validation failed",
  "details": [
    {
      "field": "email",
      "message": "Email is required"
    }
  ]
}

Example: Invalid date format

{
  "statusCode": 400,
  "message": "Validation failed",
  "details": [
    {
      "field": "bookingDate",
      "message": "Must be YYYY-MM-DD format"
    }
  ]
}

Example: Weak password

{
  "statusCode": 400,
  "message": "Password must contain uppercase, lowercase, number, and special character"
}

Example: Invalid JSON body

{
  "statusCode": 400,
  "message": "Invalid JSON format",
  "error": "ParseError"
}

401 Unauthorized

Request lacks valid authentication. Token expired, invalid, or missing.

Causes:

  • No access_token cookie
  • Token expired
  • Invalid JWT signature
  • Corrupted token

Example: Missing token

{
  "statusCode": 401,
  "message": "Unauthorized - no valid token"
}

Example: Expired token

{
  "statusCode": 401,
  "message": "Token expired",
  "error": "TokenExpiredError"
}

Recovery:

# Try refreshing token
POST /api/auth/refresh

# If refresh fails, user must re-login
POST /api/auth/login

403 Forbidden

User authenticated but lacks permission for this resource.

Causes:

  • User role doesn't match required role
  • Wrong tenant access
  • User is suspended
  • Insufficient permissions

Example: Wrong role

{
  "statusCode": 403,
  "message": "Forbidden - admin role required",
  "requiredRole": "ADMIN",
  "userRole": "CUSTOMER"
}

Example: Tenant mismatch

{
  "statusCode": 403,
  "message": "Access denied - tenant mismatch"
}

Example: User suspended

{
  "statusCode": 403,
  "message": "User account is suspended"
}

404 Not Found

Resource doesn't exist or user doesn't have access to it.

Causes:

  • Invalid ID format
  • Resource deleted
  • Wrong organization scope
  • Resource never existed

Example: Booking not found

{
  "statusCode": 404,
  "message": "Booking not found",
  "resourceId": "507f191e810c19729de860ea"
}

Example: Organization not found

{
  "statusCode": 404,
  "message": "Organization not found",
  "slug": "booki-salon"
}

409 Conflict

Request conflicts with current state (duplicate entry, already exists, etc.).

Causes:

  • Email already registered
  • Organization slug taken
  • Double-booking conflict
  • Invalid state transition

Example: Duplicate email

{
  "statusCode": 409,
  "message": "Email already registered",
  "field": "email"
}

Example: Double booking

{
  "statusCode": 409,
  "message": "This time slot is no longer available",
  "suggestedTimes": ["14:00", "14:30", "15:00"]
}

422 Unprocessable Entity

Request is well-formed but cannot be processed due to business logic.

Causes:

  • Invalid booking date (past date)
  • Insufficient funds
  • Resource limit exceeded
  • Invalid state transition (e.g., confirm already-declined booking)

Example: Booking in past

{
  "statusCode": 422,
  "message": "Cannot book in the past",
  "providedDate": "2026-02-01",
  "currentDate": "2026-04-01"
}

Example: Time slot fully booked

{
  "statusCode": 422,
  "message": "This time slot is fully booked",
  "availableSlots": [
    { "date": "2026-04-11", "time": "14:00" },
    { "date": "2026-04-11", "time": "14:30" }
  ]
}

5xx Server Errors

500 Internal Server Error

Server encountered unexpected error. Not client's fault.

Causes:

  • Database connection error
  • Unhandled exception
  • Deployment issue
  • Third-party service (payment gateway) failure

Example:

{
  "statusCode": 500,
  "message": "Internal server error",
  "error": "DatabaseError",
  "requestId": "req-abc123xyz"
}

What to do:

  • Check requestId to report to support
  • Retry after a few seconds
  • Avoid making repeated rapid requests

503 Service Unavailable

Server temporarily unavailable (maintenance, overload).

{
  "statusCode": 503,
  "message": "Service temporarily unavailable",
  "retryAfter": 300
}

What to do:

  • Wait retryAfter seconds
  • Retry request
  • Check status page

Common Error Scenarios & Solutions

Scenario 1: Login Fails

Error:

{
  "statusCode": 400,
  "message": "Invalid email or password"
}

Troubleshoot:

  • ✅ Verify email is correct
  • ✅ Check caps lock
  • ✅ Verify password (case-sensitive)
  • ✅ If forgot password, use password reset
  • ✅ Contact support if locked out

Scenario 2: Booking Creation Fails

Error:

{
  "statusCode": 422,
  "message": "This time slot is no longer available"
}

Troubleshoot:

  • ✅ Check another time
  • ✅ Use GET /api/bookings/fully-booked-slots to see availability
  • ✅ Try different date
  • ✅ Call organization directly for walk-in availability

Scenario 3: Permission Denied

Error:

{
  "statusCode": 403,
  "message": "Forbidden - admin role required"
}

Troubleshoot:

  • ✅ Verify you're logged in as correct user
  • ✅ Check your role matches endpoint requirements
  • ✅ For owner operations, ensure you're viewing your own organization
  • ✅ Contact organization owner if you need role upgrade

Scenario 4: Token Expired Mid-Request

Error:

{
  "statusCode": 401,
  "message": "Token expired"
}

Troubleshoot (Frontend):

// Detect the 401
const response = await fetch('/api/bookings');

if (response.status === 401) {
  // Try to refresh
  const refreshed = await fetch('/api/auth/refresh', {
    method: 'POST',
    credentials: 'include'  // Include cookies
  });
  
  if (refreshed.ok) {
    // Retry original request with new token
    const retry = await fetch('/api/bookings');
    // Continue...
  } else {
    // Redirect to login
    window.location.href = '/login';
  }
}

Scenario 5: Tenant Not Found

Error:

{
  "statusCode": 404,
  "message": "Organization not found",
  "slug": "typo-salon"
}

Troubleshoot:

  • ✅ Verify organization slug spelling
  • ✅ Ensure subdomain matches organization
  • ✅ Use GET /api/tenant/:slug to verify org exists
  • ✅ Check if organization was suspended or deleted

Scenario 6: Double Booking / Conflict

Error:

{
  "statusCode": 409,
  "message": "Email already registered",
  "field": "email"
}

Troubleshoot:

  • ✅ Use different email
  • ✅ If it's your email, try logging in instead
  • ✅ Contact support to recover account

Validation Error Examples

Email Validation

Invalid:

{
  "statusCode": 400,
  "details": [
    { "field": "email", "message": "Invalid email address" }
  ]
}

Valid formats:

  • user@example.com
  • user+tag@example.co.uk

Password Validation

Weak password:

{
  "statusCode": 400,
  "message": "Password must contain: uppercase (A-Z), lowercase (a-z), number (0-9), special char (!@#$%)"
}

Valid examples:

  • SecureP@ss123
  • MyP@ssw0rd
  • Strong!P@ss2026

Phone Number Validation

Invalid:

{
  "statusCode": 400,
  "details": [
    { "field": "phone", "message": "Invalid phone format" }
  ]
}

Valid formats (Philippines):

  • 09161234567
  • +639161234567
  • 02-1234567 (landline) ✅

Date Validation

Invalid:

{
  "statusCode": 400,
  "details": [
    { "field": "bookingDate", "message": "Must be YYYY-MM-DD format, not past" }
  ]
}

Valid formats:

  • 2026-04-10 (future) ✅
  • 2026-12-25

Error Handling Best Practices

✅ DO

  • ✅ Check statusCode before processing response
  • ✅ Display user-friendly messages from message field
  • ✅ Retry on 5xx errors with exponential backoff
  • ✅ Implement auto-retry for token refresh on 401
  • ✅ Include requestId when reporting bugs
  • ✅ Log errors for debugging (not in production)

❌ DON'T

  • ❌ Ignore error status codes
  • ❌ Assume 200 means success (check response)
  • ❌ Show raw error details to users (use message)
  • ❌ Retry infinitely (cap at 3-5 attempts)
  • ❌ Store sensitive error info
  • ❌ Expose stack traces to frontend

Quick Reference Table

StatusErrorCauseAction
400Validation failedBad dataFix input, resend
401Token expiredAuth invalidCall refresh, re-login
403ForbiddenNo permissionCheck role, contact admin
404Not foundResource missingVerify ID/slug
409ConflictAlready existsUse different value
422Invalid stateBusiness logicTry different input
500Server errorBackend issueRetry later, report
503UnavailableMaintenanceWait, retry

Summary

Always check response status first:

if (!response.ok) {
  // 4xx or 5xx error
  const error = await response.json();
  console.error(error.message);
  // Handle based on statusCode
} else {
  // 2xx success
  const data = await response.json();
  // Process data
}

Key points:

  • All errors have consistent format with statusCode + message
  • 4xx = Client problem (bad request, auth, permission)
  • 5xx = Server problem (retry later)
  • Validation errors include details array with field info
  • Always implement token refresh on 401 (before re-login)
  • Include requestId when reporting bugs