The Bookwell API uses standard HTTP status codes and provides detailed error messages to help you handle issues gracefully.
Error Response Format
All errors follow this structure:
{
"error": {
"code": "invalid_request",
"message": "The request was invalid or cannot be served.",
"details": {
"field": "start_time",
"reason": "Must be a future date"
}
}
}Fields
| Field | Description |
|---|---|
code | Machine-readable error code |
message | Human-readable description |
details | Additional context (optional) |
HTTP Status Codes
Success Codes
| Code | Description |
|---|---|
200 | Request succeeded |
201 | Resource created |
204 | Request succeeded, no content |
Client Error Codes
| Code | Description |
|---|---|
400 | Bad request (invalid parameters) |
401 | Authentication required |
403 | Insufficient permissions |
404 | Resource not found |
409 | Conflict (e.g., slot already booked) |
422 | Validation error |
429 | Rate limit exceeded |
Server Error Codes
| Code | Description |
|---|---|
500 | Internal server error |
502 | Bad gateway |
503 | Service unavailable |
Error Codes Reference
Authentication Errors
| Code | HTTP | Description |
|---|---|---|
authentication_required | 401 | No API key provided |
invalid_api_key | 401 | API key is invalid or revoked |
expired_api_key | 401 | API key has expired |
insufficient_permissions | 403 | Key lacks required scope |
Example:
{
"error": {
"code": "invalid_api_key",
"message": "The provided API key is invalid or has been revoked."
}
}Validation Errors
| Code | HTTP | Description |
|---|---|---|
invalid_request | 400 | Request format is invalid |
missing_parameter | 400 | Required parameter missing |
invalid_parameter | 400 | Parameter value is invalid |
validation_error | 422 | Multiple validation failures |
Example:
{
"error": {
"code": "validation_error",
"message": "The request contains invalid data.",
"details": {
"errors": [
{
"field": "customer.email",
"message": "Must be a valid email address"
},
{
"field": "start_time",
"message": "Must be in the future"
}
]
}
}
}Resource Errors
| Code | HTTP | Description |
|---|---|---|
not_found | 404 | Resource doesn't exist |
service_not_found | 404 | Service ID invalid |
therapist_not_found | 404 | Therapist ID invalid |
appointment_not_found | 404 | Appointment ID invalid |
customer_not_found | 404 | Customer ID invalid |
Example:
{
"error": {
"code": "appointment_not_found",
"message": "No appointment found with ID apt_abc123."
}
}Booking Errors
| Code | HTTP | Description |
|---|---|---|
slot_unavailable | 409 | Time slot is not available |
therapist_unavailable | 409 | Therapist not available |
service_unavailable | 409 | Service not bookable |
past_booking | 400 | Cannot book past dates |
booking_limit_reached | 400 | Too far in advance |
customer_blocked | 403 | Customer is blocked |
duplicate_booking | 409 | Booking already exists |
Example:
{
"error": {
"code": "slot_unavailable",
"message": "The requested time slot is no longer available.",
"details": {
"requested_time": "2025-01-15T10:00:00Z",
"next_available": "2025-01-15T11:00:00Z"
}
}
}Rate Limiting Errors
| Code | HTTP | Description |
|---|---|---|
rate_limit_exceeded | 429 | Too many requests |
Example:
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Please try again later.",
"details": {
"limit": 60,
"period": "minute",
"retry_after": 45
}
}
}Server Errors
| Code | HTTP | Description |
|---|---|---|
internal_error | 500 | Unexpected server error |
service_unavailable | 503 | Temporary outage |
Example:
{
"error": {
"code": "internal_error",
"message": "An unexpected error occurred. Please try again.",
"details": {
"request_id": "req_xyz789"
}
}
}Handling Errors
General Pattern
try {
const response = await fetch('https://api.bookwell.app/v1/appointments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(appointmentData)
});
if (!response.ok) {
const error = await response.json();
handleError(response.status, error);
return;
}
const data = await response.json();
// Process successful response
} catch (networkError) {
// Handle network errors
}Error Handler Example
function handleError(status, error) {
switch (error.error.code) {
case 'slot_unavailable':
// Show alternative times
showAlternativeSlots(error.error.details.next_available);
break;
case 'validation_error':
// Show field errors
showFieldErrors(error.error.details.errors);
break;
case 'rate_limit_exceeded':
// Wait and retry
const retryAfter = error.error.details.retry_after;
setTimeout(() => retryRequest(), retryAfter * 1000);
break;
case 'authentication_required':
case 'invalid_api_key':
// Handle auth issues
refreshAuthentication();
break;
default:
// Generic error handling
showGenericError(error.error.message);
}
}Retry Strategy
Retryable Errors
These errors may succeed on retry:
| Code | Retry Strategy |
|---|---|
429 | Wait for retry_after |
500 | Exponential backoff |
502 | Wait and retry |
503 | Wait and retry |
Non-Retryable Errors
Don't retry these:
400- Fix the request first401- Fix authentication403- Get proper permissions404- Resource doesn't exist409- Resolve conflict first422- Fix validation errors
Exponential Backoff
async function retryWithBackoff(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
if (!isRetryable(error)) throw error;
const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
await sleep(delay);
}
}
}Request IDs
Every response includes a request ID:
X-Request-Id: req_abc123xyz
Include this when contacting support about errors.
Best Practices
User-Friendly Messages
Map error codes to user-friendly messages:
const userMessages = {
'slot_unavailable': 'This time is no longer available. Please choose another.',
'customer_blocked': 'Unable to complete booking. Please contact us.',
'rate_limit_exceeded': 'Too many requests. Please wait a moment.',
// ...
};
function getUserMessage(code) {
return userMessages[code] || 'Something went wrong. Please try again.';
}Logging
Log errors for debugging:
function logError(error, context) {
console.error('API Error:', {
code: error.error.code,
message: error.error.message,
details: error.error.details,
context: context,
timestamp: new Date().toISOString()
});
}Validation Before Requests
Validate locally before API calls:
function validateAppointmentData(data) {
const errors = [];
if (!data.service_id) {
errors.push({ field: 'service_id', message: 'Service is required' });
}
if (!data.start_time || new Date(data.start_time) <= new Date()) {
errors.push({ field: 'start_time', message: 'Must be a future time' });
}
if (!data.customer?.email?.includes('@')) {
errors.push({ field: 'customer.email', message: 'Valid email required' });
}
return errors;
}Getting Help
For persistent errors:
- Check the API Status
- Review error details and request ID
- Check documentation for the endpoint
- Contact support with request ID