SureBooking REST API
Token-authenticated API for integrating SureBooking with mobile apps, third-party services, and custom frontends.
Jump to section
🔸 Overview
Base URL
https://yourdomain.com/api/v1
Response Format
JSON (Content-Type: application/json)
All API responses follow a consistent envelope structure:
{
"success": true,
"data": { ... }, // resource data or array
"message": "...", // optional human-readable message
"meta": { ... } // pagination info (list endpoints only)
}
Pagination meta (list endpoints)
{
"meta": {
"current_page": 1,
"last_page": 5,
"per_page": 15,
"total": 73
}
}
🔒 Authentication
401 Unauthorized.Sending the token
Include the token in the Authorization header of every request:
GET /api/v1/appointments HTTP/1.1
Host: yourdomain.com
Authorization: Bearer YOUR_API_TOKEN
Accept: application/json
Example with cURL
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json" \
https://yourdomain.com/api/v1/appointments
Rate Limiting
Each API token has a configurable rate limit (requests per minute). When exceeded, the API returns 429 Too Many Requests. The limit can be adjusted per client in Admin → API Clients.
🔑 Token Management
API tokens are managed from the admin panel. Each token belongs to an API Client record that controls its name, status, and rate limit.
Creating a token
- Login to the Admin Panel at
/admin - Navigate to Admin → API Clients
- Click Add New and enter a name for the client (e.g., "Mobile App")
- Save. Then click Generate Token on the client detail page
- Copy the token immediately — it will not be shown again in full
Revoking a token
Go to Admin → API Clients → [client name] and click Revoke next to the token. The token becomes invalid immediately.
Updating rate limits
On the client detail page click Update Config to change the requests-per-minute limit for that client.
📅 Appointments
/api/v1/appointments
List appointments
Returns a paginated list of appointments.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| status | integer | Filter by status value (see status table below) |
| date_from | string | Filter from date (YYYY-MM-DD) |
| date_to | string | Filter to date (YYYY-MM-DD) |
| branch_id | integer | Filter by branch ID |
| per_page | integer | Items per page (default: 15, max: 100) |
| page | integer | Page number (default: 1) |
Example response
{
"success": true,
"data": [
{
"id": 101,
"status": 1,
"note": null,
"createdAt": "2026-07-10T09:00:00.000000Z",
"branch": { "id": 2, "name": "Downtown Branch", "address": "123 Main St", "phone": "0901234567" },
"customer": { "id": 5, "name": "Nguyen Van A", "email": "customer@example.com", "phone": "0912345678", "isGuest": false },
"appointmentDetail": [
{
"id": 1,
"service": { "id": 3, "name": "Deep Tissue Massage" },
"employee": { "id": 7, "name": "Tran Thi B" },
"price": 350000,
"priceFrom": 320000,
"duration": 60,
"timeStart": "2026-07-12 10:00:00",
"timeEnd": "2026-07-12 11:00:00"
}
],
"payment": { "method": "bank_transfer", "amount": 350000, "status": "pending" }
}
],
"meta": {
"current_page": 1,
"last_page": 3,
"per_page": 15,
"total": 42
}
}
/api/v1/appointments/{id}
Get appointment detail
Returns full appointment data including branch, customer, all service line items with employee assignments, and payment info.
Error response (404)
{ "success": false, "message": "Appointment not found." }
/api/v1/appointments/{id}/status
Update appointment status
Transitions an appointment through its lifecycle. Only valid transitions are accepted — attempting an out-of-sequence change returns 422 with the list of allowed next statuses.
Status values
| Value | Constant | Meaning |
|---|---|---|
| -1 | STATUS_DRAF | Draft — saved but not yet submitted |
| 1 | STATUS_NEW | New — submitted, awaiting review |
| 2 | STATUS_CANCEL | Cancelled |
| 3 | STATUS_SUCCESS | Completed successfully |
| 4 | STATUS_ACCEPTED | Accepted by the salon |
Allowed transitions
| Current status | Can transition to | Side effects |
|---|---|---|
| -1 — DRAFT | 1 — NEW | History logged |
| 1 — NEW | 4 — ACCEPTED, 2 — CANCEL | History logged · Email sent to customer |
| 4 — ACCEPTED | 3 — SUCCESS, 2 — CANCEL | History logged · Email sent |
| 3 — SUCCESS | — | Terminal state |
| 2 — CANCEL | — | Terminal state |
Request body (JSON)
| Field | Type | Description |
|---|---|---|
| status | integer (required) | Target status value (see table above) |
| reason | string (optional) | Cancellation reason — used when transitioning to status 2 |
Example — accept appointment (NEW → ACCEPTED)
PATCH /api/v1/appointments/101/status
Authorization: Bearer {token}
Content-Type: application/json
{ "status": 4 }
Example — cancel appointment (NEW → CANCEL)
PATCH /api/v1/appointments/101/status
Authorization: Bearer {token}
Content-Type: application/json
{ "status": 2, "reason": "Customer requested cancellation" }
Success response 200
{
"success": true,
"message": "Appointment status updated.",
"data": { "id": 101, "status": 4, ... }
}
Invalid transition response 422
{
"success": false,
"message": "Status transition not allowed.",
"current": 1,
"allowed": [4, 2]
}
🏠 Branches
/api/v1/branches
List branches
Returns a paginated list of active branches.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| search | string | Filter by branch name (partial match) |
| per_page | integer | Items per page (default: 15, max: 100) |
| page | integer | Page number (default: 1) |
Response fields
id, name, slug, address, phone, image, status, createdAt
/api/v1/branches/{id}
Get branch detail
Returns a single active branch. Returns 404 if not found or inactive.
📁 Service Categories
/api/v1/service-categories
List service categories
Returns a list of active service categories. When branch_id is supplied, returns only categories that have at least one active service at that branch (flat list, no pagination).
Query parameters
| Parameter | Type | Description |
|---|---|---|
| branch_id | integer | Return categories that have active services at this branch (bypasses pagination) |
| per_page | integer | Items per page (default: 15, max: 100) — ignored when branch_id is set |
| page | integer | Page number (default: 1) — ignored when branch_id is set |
Example response
{
"success": true,
"data": [
{
"id": 3,
"name": "Hair Care",
"image": "uploads/categories/hair-care.jpg",
"description": "All hair styling and treatment services",
"branchId": 1,
"status": 1
}
],
"meta": { "current_page": 1, "last_page": 1, "per_page": 15, "total": 4 }
}
/api/v1/service-categories/{id}
Get category detail with services
Returns a single active category together with its active services (including variants for each service). Returns 404 if not found or inactive.
Example response
{
"success": true,
"data": {
"id": 3,
"name": "Hair Care",
"image": "uploads/categories/hair-care.jpg",
"description": "All hair styling and treatment services",
"branchId": 1,
"status": 1,
"services": [
{
"id": 12,
"name": "Hair Cut",
"image": "uploads/services/haircut.jpg",
"price": 150000,
"priceOld": null,
"isPriceFrom": true,
"duration": 30,
"status": 1,
"category": { "id": 3, "name": "Hair Care" },
"variants": [
{ "id": 5, "name": "Short Hair", "price": 150000, "isPriceFrom": false, "duration": 30 },
{ "id": 6, "name": "Long Hair", "price": 200000, "isPriceFrom": false, "duration": 45 }
]
}
]
}
}
✨ Services
/api/v1/services
List services
Returns a paginated list of active services. When branch_id is supplied, returns all services available at that branch (flat list, no pagination).
Query parameters
| Parameter | Type | Description |
|---|---|---|
| search | string | Filter by service name (partial match) |
| branch_id | integer | Return services available at this branch (bypasses pagination) |
| category_id | integer | Filter by service category ID (combinable with branch_id) |
| per_page | integer | Items per page (default: 15, max: 100) |
| page | integer | Page number (default: 1) |
Price display logic
price reflects the minimum price across active variants and isPriceFrom is true if any variant has that flag set. Use the variants array for per-option pricing.Example response
{
"success": true,
"data": [
{
"id": 12,
"name": "Hair Cut",
"image": "uploads/services/haircut.jpg",
"price": 150000,
"priceOld": null,
"isPriceFrom": true,
"duration": 30,
"status": 1,
"category": { "id": 3, "name": "Hair Care" },
"variants": [
{ "id": 5, "name": "Short Hair", "price": 150000, "isPriceFrom": false, "duration": 30 },
{ "id": 6, "name": "Long Hair", "price": 200000, "isPriceFrom": false, "duration": 45 }
]
},
{
"id": 13,
"name": "Head Massage",
"image": "uploads/services/massage.jpg",
"price": 120000,
"priceOld": 150000,
"isPriceFrom": false,
"duration": 45,
"status": 1,
"category": { "id": 3, "name": "Hair Care" },
"variants": []
}
],
"meta": { "current_page": 1, "last_page": 2, "per_page": 15, "total": 18 }
}
/api/v1/services/{id}
Get service detail
Returns a single active service with its category and full variants list. Returns 404 if not found or inactive.
Response fields
| Field | Type | Description |
|---|---|---|
| id | integer | Service ID |
| name | string | Service name |
| image | string|null | Icon/image path |
| price | integer | Display price — min variant price when variants exist, otherwise service price |
| priceOld | integer|null | Original price before discount (service level) |
| isPriceFrom | boolean | Whether to show as "from X" — true if any active variant has this flag |
| duration | integer | Service duration in minutes (service level) |
| status | integer | Status value |
| category | object | Service category — id, name |
| variants | array | Active variants — each has id, name, price, isPriceFrom, duration. Empty array when service has no variants. |
👤 Customers
/api/v1/customers
List customers
Returns a paginated list of customers. The search parameter matches against name, email, and phone simultaneously.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| search | string | Search by name, email, or phone (partial match) |
| per_page | integer | Items per page (default: 15, max: 100) |
| page | integer | Page number (default: 1) |
Response fields
id, name, email, phone, isGuest, createdAt
/api/v1/customers/{id}
Get customer detail with appointment history
Returns a single customer along with their paginated appointment history. Returns 404 if not found.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| appointments_per_page | integer | Appointments per page (default: 10, max: 50) |
| appointments_page | integer | Appointment page number (default: 1) |
Example response
{
"success": true,
"data": {
"id": 5,
"name": "Nguyen Van A",
"email": "customer@example.com",
"phone": "0912345678",
"isGuest": false,
"createdAt": "2026-06-01T08:00:00.000000Z",
"appointments": [
{
"id": 101,
"code": "APT-20260712-001",
"status": 3,
"statusText": "Completed",
"dateAppointment": "2026-07-12",
"timeStart": "10:00:00",
"timeEnd": "11:00:00",
"totalPrice": 350000,
"branch": { "id": 2, "name": "Downtown Branch" },
"createdAt": "2026-07-10T09:00:00.000000Z"
}
]
},
"meta": {
"appointments": {
"current_page": 1,
"last_page": 3,
"per_page": 10,
"total": 25
}
}
}
⚠️ Error Responses
| HTTP Code | Meaning | Common cause |
|---|---|---|
| 400 | Bad Request | Malformed JSON body |
| 401 | Unauthorized | Missing or invalid token |
| 404 | Not Found | Resource does not exist or is inactive |
| 422 | Unprocessable Entity | Validation failed or status transition not allowed |
| 429 | Too Many Requests | Rate limit exceeded for this token |
| 500 | Server Error | Unexpected server error (check Laravel logs) |
Validation error envelope (422)
{
"success": false,
"message": "The status field is required.",
"errors": {
"status": ["The status field is required."]
}
}
SureBooking — REST API Reference
© 2026 DreamTeam. All rights reserved. | Install Guide | User Guide | FAQ