API Reference — Booking System
Complete documentation for all REST API endpoints (V1.1).
Base URL: http://localhost:3000/api
Authentication: Admin endpoints require a JWT token in the Authorization: Bearer <token> header.
Public Endpoints (No Authentication Required)
GET /api/health
Server health check.
Response: 200 OK
{ "status": "ok" }
GET /api/resources
List of active resources.
Response: 200 OK
[
{
"id": 1,
"name": "Meeting Room A",
"description": "10-seat room with projector",
"capacity": 10,
"color": "#3B82F6",
"active": true
}
]
GET /api/resources/:id/slots
Available time slots for a resource on a given date.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
date |
string | Yes | Date in YYYY-MM-DD format |
Example: GET /api/resources/1/slots?date=2026-04-15
Response: 200 OK
[
{ "start": "09:00", "end": "10:00" },
{ "start": "10:00", "end": "11:00" },
{ "start": "14:00", "end": "15:00" }
]
GET /api/resources/:id/fields
Custom fields for a resource (for the booking form).
Example: GET /api/resources/1/fields
Response: 200 OK
[
{
"id": 1,
"resource_id": 1,
"label": "Number of adults",
"type": "number",
"required": true,
"options": null,
"placeholder": "E.g.: 2",
"sort_order": 1
},
{
"id": 2,
"resource_id": 1,
"label": "Breakfast",
"type": "checkbox",
"required": false,
"options": null,
"placeholder": "",
"sort_order": 2
}
]
GET /api/resources/bookings/public
Public bookings (without client personal data).
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
date_from |
string | Yes | Start date (YYYY-MM-DD) |
date_to |
string | Yes | End date (YYYY-MM-DD) |
Example: GET /api/resources/bookings/public?date_from=2026-04-01&date_to=2026-04-30
Response: 200 OK
[
{
"id": 1,
"resource_id": 1,
"start_at": "2026-04-15T09:00:00.000Z",
"end_at": "2026-04-15T10:00:00.000Z",
"status": "confirmed",
"color": "#3B82F6"
}
]
GET /api/resources/blocks/public
Public blocks/unavailabilities.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
date_from |
string | Yes | Start date (YYYY-MM-DD) |
date_to |
string | Yes | End date (YYYY-MM-DD) |
Response: 200 OK
[
{
"id": 1,
"resource_id": 1,
"start_at": "2026-04-20T00:00:00.000Z",
"end_at": "2026-04-22T23:59:00.000Z",
"reason": "Holiday"
}
]
POST /api/bookings
Create a booking (client form).
Request Body:
{
"resource_id": 1,
"client_name": "John Doe",
"email": "john@example.com",
"phone": "0612345678",
"start_at": "2026-04-15T09:00:00",
"end_at": "2026-04-15T10:00:00",
"notes": "First visit",
"color": "",
"custom_values": [
{ "field_id": 1, "value": "2" },
{ "field_id": 2, "value": "true" }
]
}
Success Response: 201 Created
{
"id": 1,
"resource_id": 1,
"client_name": "John Doe",
"email": "john@example.com",
"phone": "0612345678",
"start_at": "2026-04-15T09:00:00.000Z",
"end_at": "2026-04-15T10:00:00.000Z",
"token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "confirmed",
"notes": "First visit"
}
Conflict Response: 409 Conflict
{ "error": "This time slot is already booked for this resource" }
DELETE /api/bookings/:token
Cancel a booking via the unique token (email link).
Example: DELETE /api/bookings/a1b2c3d4-e5f6-7890-abcd-ef1234567890
Response: 200 OK
{ "message": "Booking successfully cancelled" }
POST /api/auth/login
Administrator login.
Request Body:
{
"email": "admin@booking.com",
"password": "admin123"
}
Response: 200 OK
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": 1,
"email": "admin@booking.com",
"role": "admin"
}
}
GET /api/settings/public
Public settings (name, logo, colors).
Response: 200 OK
{
"app_name": "My Booking",
"logo_url": "/uploads/logo.png",
"primary_color": "#3B82F6",
"secondary_color": "#10B981"
}
GET /api/geocode
Geocode an address (for address type fields).
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
address |
string | Yes | Address to geocode |
Example: GET /api/geocode?address=10+rue+de+Rivoli+Paris
Response: 200 OK
{
"lat": 48.8566,
"lng": 2.3522,
"formatted_address": "10 Rue de Rivoli, 75004 Paris, France"
}
POST /api/upload
Upload a file to MinIO (S3-compatible).
Content-Type: multipart/form-data
Body: file field containing the file to upload.
Limits:
- Maximum size: 10 MB
- Allowed types: images (jpg, png, gif, webp, svg), documents (pdf, doc, docx)
Success Response: 200 OK
{
"url": "http://localhost:9000/booking/uploads/abc123-photo.jpg"
}
Error Response (file too large): 413 Payload Too Large
{ "error": "The file exceeds the maximum allowed size (10 MB)" }
Error Response (type not allowed): 400 Bad Request
{ "error": "This file type is not allowed" }
Admin Endpoints (JWT Required)
All endpoints below require the header:
Authorization: Bearer <jwt_token>
Bookings
GET /api/admin/bookings
List bookings with filters and pagination.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
status |
string | No | Filter by status (confirmed, cancelled) |
resource_id |
integer | No | Filter by resource |
date_from |
string | No | Start date (YYYY-MM-DD) |
date_to |
string | No | End date (YYYY-MM-DD) |
page |
integer | No | Page number (default: 1) |
limit |
integer | No | Items per page (default: 20) |
Response: 200 OK
{
"bookings": [
{
"id": 1,
"resource_id": 1,
"client_name": "John Doe",
"email": "john@example.com",
"phone": "0612345678",
"start_at": "2026-04-15T09:00:00.000Z",
"end_at": "2026-04-15T10:00:00.000Z",
"status": "confirmed",
"notes": "First visit",
"Resource": { "id": 1, "name": "Room A", "color": "#3B82F6" }
}
],
"total": 45,
"page": 1,
"totalPages": 3
}
POST /api/admin/bookings
Create a booking manually (admin).
Body: same format as POST /api/bookings.
Response: 201 Created
PUT /api/admin/bookings/:id
Update a booking.
Request Body:
{
"resource_id": 1,
"client_name": "John Doe",
"email": "john@example.com",
"phone": "0612345678",
"start_at": "2026-04-15T10:00:00",
"end_at": "2026-04-15T11:00:00",
"status": "confirmed",
"notes": "Rescheduled by 1 hour",
"color": "#EF4444"
}
Response: 200 OK
DELETE /api/admin/bookings/:id
Cancel a booking (sets status to cancelled, does not delete).
Response: 200 OK
{ "message": "Booking cancelled" }
DELETE /api/admin/bookings/:id/permanent
Permanently delete a booking, its custom field values, and associated MinIO files.
Response: 200 OK
{ "message": "Booking permanently deleted" }
Custom Field Values
GET /api/admin/booking-values/:bookingId
Retrieve custom field values for a booking.
Example: GET /api/admin/booking-values/1
Response: 200 OK
[
{
"id": 1,
"booking_id": 1,
"field_id": 1,
"value": "2",
"CustomField": {
"id": 1,
"label": "Number of adults",
"type": "number"
}
},
{
"id": 2,
"booking_id": 1,
"field_id": 2,
"value": "true",
"CustomField": {
"id": 2,
"label": "Breakfast",
"type": "checkbox"
}
}
]
Resources
GET /api/admin/resources
List resources (active and inactive). Supports optional pagination and search.
| Param | Type | Description |
|---|---|---|
page |
number | Page number (enables pagination) |
limit |
number | Results per page (default: 20) |
search |
string | Filter by name (case-insensitive) |
Without page, returns all resources (backward-compatible for the public calendar).
Response: 200 OK
[
{
"id": 1,
"name": "Room A",
"description": "10-seat meeting room",
"capacity": 10,
"color": "#3B82F6",
"active": true
}
]
POST /api/admin/resources
Create a resource.
Request Body:
{
"name": "Room B",
"description": "20-seat conference room",
"capacity": 20,
"color": "#10B981",
"active": true
}
Response: 201 Created
PUT /api/admin/resources/:id
Update a resource.
Response: 200 OK
DELETE /api/admin/resources/:id
Delete a resource.
Response: 200 OK
PUT /api/admin/resources-reorder
Reorder resources.
Request Body:
{
"order": [3, 1, 2]
}
Response: 200 OK
Time Slots
POST /api/admin/slots
Define time slots for a resource.
Request Body:
{
"resource_id": 1,
"slots": [
{ "day_of_week": 1, "start_time": "09:00", "end_time": "18:00", "duration_min": 60 },
{ "day_of_week": 2, "start_time": "09:00", "end_time": "18:00", "duration_min": 60 },
{ "day_of_week": 3, "start_time": "09:00", "end_time": "18:00", "duration_min": 60 },
{ "day_of_week": 4, "start_time": "09:00", "end_time": "18:00", "duration_min": 60 },
{ "day_of_week": 5, "start_time": "09:00", "end_time": "17:00", "duration_min": 60 }
]
}
Response: 200 OK
Note: this endpoint replaces all existing slots for the given resource.
Blocks / Unavailabilities
GET /api/admin/blocks
List blocks/unavailabilities with pagination.
| Param | Type | Description |
|---|---|---|
resource_id |
number | Filter by resource |
page |
number | Page number (default: 1) |
limit |
number | Results per page (default: 20) |
Response: 200 OK
[
{
"id": 1,
"resource_id": 1,
"start_at": "2026-04-20T00:00:00.000Z",
"end_at": "2026-04-22T23:59:00.000Z",
"reason": "Annual leave",
"Resource": { "id": 1, "name": "Room A" }
}
]
POST /api/admin/blocks
Create an unavailability block.
Request Body:
{
"resource_id": 1,
"start_at": "2026-04-20T00:00:00",
"end_at": "2026-04-22T23:59:00",
"reason": "Annual leave"
}
Response: 201 Created
PUT /api/admin/blocks/:id
Update an existing block. (New in V1.1)
Request Body:
{
"resource_id": 1,
"start_at": "2026-04-20T00:00:00",
"end_at": "2026-04-25T23:59:00",
"reason": "Extended leave"
}
Response: 200 OK
Note: dates are converted to UTC using the
toLocalISOfunction to correctly handle timezones.
DELETE /api/admin/blocks/:id
Delete a block.
Response: 200 OK
{ "message": "Block deleted" }
Custom Fields
GET /api/admin/custom-fields
List all custom fields.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
resource_id |
integer | No | Filter by resource |
Response: 200 OK
[
{
"id": 1,
"resource_id": 1,
"label": "Number of adults",
"type": "number",
"required": true,
"options": null,
"placeholder": "E.g.: 2",
"sort_order": 1
},
{
"id": 2,
"resource_id": 1,
"label": "Service",
"type": "select",
"required": true,
"options": ["Haircut 30min $25", "Color 1h30 $60", "Highlights 2h $80"],
"placeholder": "",
"sort_order": 2
}
]
POST /api/admin/custom-fields
Create a custom field.
Request Body:
{
"resource_id": 1,
"label": "Number of adults",
"type": "number",
"required": true,
"options": null,
"placeholder": "E.g.: 2",
"sort_order": 1
}
Response: 201 Created
PUT /api/admin/custom-fields/:id
Update a custom field.
Body: same format as creation (fields to update).
Response: 200 OK
DELETE /api/admin/custom-fields/:id
Delete a custom field.
Response: 200 OK
{ "message": "Custom field deleted" }
PUT /api/admin/custom-fields-reorder
Reorder custom fields.
Request Body:
{
"order": [3, 1, 2]
}
Response: 200 OK
Settings
GET /api/admin/settings
Read all settings.
Response: 200 OK
{
"app_name": "My Booking",
"logo_url": "/uploads/logo.png",
"primary_color": "#3B82F6",
"secondary_color": "#10B981",
"smtp_host": "smtp.example.com",
"smtp_port": "587",
"smtp_user": "noreply@example.com",
"smtp_pass": "***",
"smtp_from": "noreply@example.com"
}
PUT /api/admin/settings
Update settings.
Request Body:
{
"app_name": "New Name",
"primary_color": "#EF4444"
}
Response: 200 OK
POST /api/admin/settings/test-smtp
Test the SMTP configuration (sends a test email).
Success Response: 200 OK
{ "message": "Test email sent successfully" }
Error Response: 500 Internal Server Error
{ "error": "Send failed: SMTP connection refused" }
POST /api/admin/settings/upload-logo
Upload a logo.
Content-Type: multipart/form-data
Body: logo field containing the image.
Response: 200 OK
{ "url": "/uploads/logo.png" }
GET /api/admin/settings/public
Public settings (also accessible without authentication via GET /api/settings/public).
Endpoint Summary
Public Endpoints (10)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/health |
Health check |
| GET | /api/resources |
List active resources |
| GET | /api/resources/:id/slots |
Available time slots |
| GET | /api/resources/:id/fields |
Custom fields for a resource |
| GET | /api/resources/bookings/public |
Public bookings |
| GET | /api/resources/blocks/public |
Public blocks |
| POST | /api/bookings |
Create a booking |
| DELETE | /api/bookings/:token |
Cancel via token |
| POST | /api/auth/login |
Admin login |
| GET | /api/settings/public |
Public settings |
| POST | /api/upload |
File upload |
| GET | /api/geocode |
Address geocoding |
Admin Endpoints (22)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/bookings |
List bookings (filters + pagination) |
| POST | /api/admin/bookings |
Create booking (admin) |
| PUT | /api/admin/bookings/:id |
Update booking |
| DELETE | /api/admin/bookings/:id |
Cancel booking (soft delete) |
| DELETE | /api/admin/bookings/:id/permanent |
Permanently delete booking |
| GET | /api/admin/booking-values/:bookingId |
Custom field values |
| GET | /api/admin/resources |
List resources |
| POST | /api/admin/resources |
Create resource |
| PUT | /api/admin/resources/:id |
Update resource |
| DELETE | /api/admin/resources/:id |
Delete resource |
| PUT | /api/admin/resources-reorder |
Reorder resources |
| POST | /api/admin/slots |
Define time slots |
| GET | /api/admin/blocks |
List blocks |
| POST | /api/admin/blocks |
Create block |
| PUT | /api/admin/blocks/:id |
Update block (V1.1) |
| DELETE | /api/admin/blocks/:id |
Delete block |
| GET | /api/admin/settings |
Read settings |
| PUT | /api/admin/settings |
Update settings |
| POST | /api/admin/settings/test-smtp |
Test SMTP |
| POST | /api/admin/settings/upload-logo |
Upload logo |
| GET | /api/admin/custom-fields |
List custom fields |
| POST | /api/admin/custom-fields |
Create field |
| PUT | /api/admin/custom-fields/:id |
Update field |
| DELETE | /api/admin/custom-fields/:id |
Delete field |
| PUT | /api/admin/custom-fields-reorder |
Reorder fields |
HTTP Response Codes
| Code | Meaning |
|---|---|
200 |
Success |
201 |
Successfully created |
400 |
Invalid request (validation) |
401 |
Not authenticated (JWT missing or expired) |
404 |
Resource not found |
409 |
Conflict (booking overlap) |
413 |
File too large |
500 |
Server error |