Guide - Common Error Responses
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 codemessage(string): Human-readable error descriptionerror(string): Error type/categorydetails(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_tokencookie - 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
requestIdto 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
retryAfterseconds - 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-slotsto 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/:slugto 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
statusCodebefore processing response - ✅ Display user-friendly messages from
messagefield - ✅ Retry on 5xx errors with exponential backoff
- ✅ Implement auto-retry for token refresh on 401
- ✅ Include
requestIdwhen 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
| Status | Error | Cause | Action |
|---|---|---|---|
| 400 | Validation failed | Bad data | Fix input, resend |
| 401 | Token expired | Auth invalid | Call refresh, re-login |
| 403 | Forbidden | No permission | Check role, contact admin |
| 404 | Not found | Resource missing | Verify ID/slug |
| 409 | Conflict | Already exists | Use different value |
| 422 | Invalid state | Business logic | Try different input |
| 500 | Server error | Backend issue | Retry later, report |
| 503 | Unavailable | Maintenance | Wait, 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
detailsarray with field info - Always implement token refresh on 401 (before re-login)
- Include
requestIdwhen reporting bugs
