This document is the detailed API contract for this branch.
Use this file as the single source of truth for frontend/backend integration details. The READMEs intentionally stay shorter and should defer to this document for endpoint-level behavior.
Base URL in local development:
http://localhost:3001
Global API prefix:
/v1
- The backend uses HttpOnly cookies for
access_tokenandrefresh_token. - A
csrf_tokencookie is also used for mutation requests when CSRF protection is enabled.
Most successful endpoints use the backend ApiResponseDto envelope:
{
"code": 200,
"message": "Operation succeeded",
"data": {},
"requestId": "req_xxx",
"timestamp": "2026-03-30T00:00:00.000Z"
}Some booking list flows return the list payload directly from the controller/service path rather than being wrapped in data. That is part of the current branch contract and is documented below endpoint-by-endpoint.
List endpoints that paginate use this shape:
{
"items": [],
"total": 0,
"page": 1,
"limit": 10,
"totalPages": 0
}- method:
POST - path:
/v1/auth/login - auth required?:
No - request shape:
{
"phoneNumber": "13800138000",
"verificationCode": "123456"
}- response shape:
{
"code": 200,
"message": "Login succeeded",
"data": {
"accessToken": "jwt",
"refreshToken": "jwt",
"tokenType": "Bearer",
"expiresIn": 3600,
"user": {
"id": "uuid",
"name": "Alice",
"phoneNumber": "13800138000",
"role": "ADMIN",
"status": "ACTIVE"
}
},
"requestId": "req_xxx",
"timestamp": "..."
}- notes:
Uses phone number plus verification code.
Also sets
access_token,refresh_token, andcsrf_tokencookies.
- method:
POST - path:
/v1/auth/register - auth required?:
No - request shape:
{
"name": "Alice",
"phoneNumber": "13800138000",
"email": "alice@example.com",
"verificationCode": "123456"
}- response shape:
{
"code": 200,
"message": "Register succeeded",
"data": {
"accessToken": "jwt",
"refreshToken": "jwt",
"tokenType": "Bearer",
"expiresIn": 3600,
"user": {
"id": "uuid",
"name": "Alice",
"phoneNumber": "13800138000",
"role": "CUSTOMER",
"status": "ACTIVE"
}
},
"requestId": "req_xxx",
"timestamp": "..."
}- notes: Registration also sets auth cookies on success.
- method:
POST - path:
/v1/auth/send-verification-code - auth required?:
No - request shape:
{
"phoneNumber": "13800138000",
"type": "login"
}- response shape:
{
"code": 200,
"message": "Verification code sent",
"data": null,
"requestId": "req_xxx",
"timestamp": "..."
}- notes:
typemust beloginorregister.
- method:
POST - path:
/v1/auth/refresh - auth required?:
No - request shape:
{}- response shape:
{
"code": 200,
"message": "Token refreshed",
"data": {
"accessToken": "jwt",
"refreshToken": "jwt",
"tokenType": "Bearer",
"expiresIn": 3600,
"user": {
"id": "uuid",
"name": "Alice",
"phoneNumber": "13800138000",
"role": "ADMIN",
"status": "ACTIVE"
}
},
"requestId": "req_xxx",
"timestamp": "..."
}- notes:
The backend uses the
refresh_tokencookie for authentication. The request body is typically empty as the token is transported via HttpOnly cookie.
- method:
POST - path:
/v1/auth/logout - auth required?:
Yes - request shape:
{}- response shape:
{
"code": 200,
"message": "Logout succeeded",
"data": null,
"requestId": "req_xxx",
"timestamp": "..."
}- notes:
Clears
access_token,refresh_token, andcsrf_tokencookies.
-
method:
GET -
path:
/v1/auth/profile -
auth required?:
Yes -
request shape: No request body.
-
response shape:
{
"code": 200,
"message": "Profile loaded",
"data": {
"id": "uuid",
"name": "Alice",
"phoneNumber": "13800138000",
"email": "alice@example.com",
"role": "ADMIN",
"status": "ACTIVE",
"remarks": "",
"createdAt": "2026-03-30T00:00:00.000Z",
"updatedAt": "2026-03-30T00:00:00.000Z"
},
"requestId": "req_xxx",
"timestamp": "..."
}- notes: Treat this as the canonical "current user" endpoint for this branch.
- method:
POST - path:
/v1/bookings - auth required?:
Yes - request shape:
{
"timeSlotId": "uuid",
"userId": "uuid",
"serviceId": "uuid",
"appointmentDate": "2026-03-30",
"customerName": "Alice",
"customerPhone": "13800138000",
"customerEmail": "alice@example.com",
"customerWechat": "alice_wechat",
"notes": "Window seat if possible",
"serviceName": "Consultation"
}- response shape:
{
"code": 200,
"message": "Booking created",
"data": {
"id": "uuid",
"appointmentNumber": "AP-20260330-0001",
"timeSlotId": "uuid",
"userId": "uuid",
"appointmentDate": "2026-03-30T00:00:00.000Z",
"status": "PENDING",
"customerName": "Alice",
"customerPhone": "13800138000",
"customerEmail": "alice@example.com",
"customerWechat": "alice_wechat",
"notes": "Window seat if possible",
"timeSlot": {
"slotTime": "09:00:00",
"durationMinutes": 30
},
"user": {
"name": "Alice",
"phoneNumber": "13800138000"
},
"service": {
"id": "uuid",
"name": "Consultation",
"durationMinutes": 30
},
"confirmationSent": false,
"reminderSent": false,
"createdAt": "2026-03-30T00:00:00.000Z",
"updatedAt": "2026-03-30T00:00:00.000Z"
},
"requestId": "req_xxx",
"timestamp": "..."
}- notes:
If
userIdis omitted, the backend fills it fromcurrentUser.id.
-
method:
GET -
path:
/v1/bookings/all -
auth required?:
Yes -
request shape: Query params may include
userId,timeSlotId,status,customerName,customerPhone,startDate,endDate,page,limit, andkeyword. -
response shape:
{
"items": [
{
"id": "uuid",
"appointmentNumber": "AP-20260330-0001",
"timeSlotId": "uuid",
"userId": "uuid",
"appointmentDate": "2026-03-30T00:00:00.000Z",
"status": "PENDING",
"customerName": "Alice",
"customerPhone": "13800138000"
}
],
"total": 1,
"page": 1,
"limit": 10,
"totalPages": 1
}- notes:
/bookings/allis the shared list endpoint for both user and admin clients. Non-admin users are narrowed to the current authenticated user by backend logic even if the incoming query is broader./bookings/meis not introduced in this branch and must not be treated as part of the contract.
- method:
GET - path:
/v1/bookings/by-date - auth required?:
Yes - request shape:
?date=YYYY-MM-DD
- response shape:
{
"items": [
{
"id": "uuid",
"appointmentNumber": "AP-20260330-0001",
"status": "PENDING",
"customerName": "Alice"
}
],
"total": 1,
"page": 1,
"limit": 10,
"totalPages": 1
}- notes: Used for date-specific booking views and date-based availability support. Non-admin users are also filtered to their own records here.
-
method:
GET -
path:
/v1/bookings/:id -
auth required?:
Yes -
request shape: No request body.
-
response shape:
{
"id": "uuid",
"appointmentNumber": "AP-20260330-0001",
"timeSlotId": "uuid",
"userId": "uuid",
"appointmentDate": "2026-03-30T00:00:00.000Z",
"status": "PENDING",
"customerName": "Alice",
"customerPhone": "13800138000"
}- notes: Returns a single booking object rather than a wrapped paginated payload. Non-admin users can only access their own booking.
- method:
PATCH - path:
/v1/bookings/:id - auth required?:
Yes - request shape:
{
"status": "CONFIRMED",
"appointmentDate": "2026-03-31",
"timeSlotId": "uuid",
"serviceId": "uuid",
"customerName": "Alice",
"customerPhone": "13800138000",
"customerEmail": "alice@example.com",
"customerWechat": "alice_wechat",
"notes": "Updated note"
}- response shape:
{
"code": 200,
"message": "Booking updated",
"data": {
"id": "uuid",
"status": "CONFIRMED"
},
"requestId": "req_xxx",
"timestamp": "..."
}- notes: Non-admin users can only update their own booking.
- method:
PATCH - path:
/v1/bookings/:id/cancel - auth required?:
Yes - request shape:
{}- response shape:
{
"code": 200,
"message": "Booking cancelled",
"data": null,
"requestId": "req_xxx",
"timestamp": "..."
}- notes: This is the frontend-compatible cancel endpoint for this branch.
-
method:
GET -
path:
/v1/services -
auth required?:
Yes -
request shape: No request body.
-
response shape:
{
"code": 200,
"message": "Services loaded",
"data": [
{
"id": "uuid",
"name": "Consultation",
"description": "30 minute consultation",
"durationMinutes": 30,
"price": 199,
"imageUrl": "https://example.com/service.png",
"categoryId": "uuid",
"isActive": true,
"displayOrder": 1,
"category": {
"id": "uuid",
"name": "General"
}
}
],
"requestId": "req_xxx",
"timestamp": "..."
}- notes: Shared service list endpoint used by booking flows.
-
method:
GET -
path:
/v1/services/all -
auth required?:
Yes -
request shape: Query params may include
name,description,durationMinutes,price,imageUrl,categoryId,isActive,displayOrder,page, andlimit. -
response shape:
{
"code": 200,
"message": "Services loaded",
"data": {
"items": [
{
"id": "uuid",
"name": "Consultation",
"durationMinutes": 30,
"isActive": true
}
],
"total": 1,
"page": 1,
"limit": 10,
"totalPages": 1
},
"requestId": "req_xxx",
"timestamp": "..."
}- notes: Admin-oriented service list endpoint with pagination and filtering support.
- method:
POST - path:
/v1/services/admin - auth required?:
Yes - request shape:
{
"name": "Consultation",
"description": "30 minute consultation",
"durationMinutes": 30,
"price": 199,
"imageUrl": "https://example.com/service.png",
"isActive": true
}- response shape:
{
"code": 200,
"message": "Service created",
"data": {
"id": "uuid",
"name": "Consultation",
"description": "30 minute consultation",
"durationMinutes": 30,
"price": 199,
"imageUrl": "https://example.com/service.png",
"isActive": true,
"createdAt": "2026-03-30T00:00:00.000Z",
"updatedAt": "2026-03-30T00:00:00.000Z"
},
"requestId": "req_xxx",
"timestamp": "..."
}- notes: Admin-only endpoint for creating new services.
- method:
PATCH - path:
/v1/services/admin/:id - auth required?:
Yes - request shape:
{
"name": "Updated Consultation",
"description": "45 minute consultation",
"durationMinutes": 45,
"price": 299,
"imageUrl": "https://example.com/service-updated.png",
"isActive": false
}- response shape:
{
"code": 200,
"message": "Service updated",
"data": {
"id": "uuid",
"name": "Updated Consultation",
"description": "45 minute consultation",
"durationMinutes": 45,
"price": 299,
"imageUrl": "https://example.com/service-updated.png",
"isActive": false,
"createdAt": "2026-03-30T00:00:00.000Z",
"updatedAt": "2026-03-31T00:00:00.000Z"
},
"requestId": "req_xxx",
"timestamp": "..."
}- notes: Admin-only endpoint for updating existing services.
- method:
PATCH - path:
/v1/services/admin/:id/status - auth required?:
Yes - request shape:
{
"isActive": false
}- response shape:
{
"code": 200,
"message": "Service status updated",
"data": {
"id": "uuid",
"isActive": false,
"updatedAt": "2026-03-31T00:00:00.000Z"
},
"requestId": "req_xxx",
"timestamp": "..."
}- notes: Admin-only endpoint for toggling service active status.
-
method:
GET -
path:
/v1/time-slots -
auth required?:
No -
request shape: Query params may include
slotTime,isActive,minDuration,maxDuration,page, andlimit. -
response shape:
{
"items": [
{
"id": "uuid",
"slotTime": "09:00:00",
"durationMinutes": 30,
"isActive": true
}
],
"total": 1,
"page": 1,
"limit": 10,
"totalPages": 1
}- notes: General time-slot listing endpoint.
- method:
GET - path:
/v1/time-slots/available-slots - auth required?:
No - request shape:
?date=YYYY-MM-DD
- response shape:
{
"code": 200,
"message": "Operation succeeded",
"data": [
{
"id": "uuid",
"slotTime": "09:00:00",
"durationMinutes": 30,
"bookedCount": 1,
"isAvailable": true,
"availabilityStatus": "AVAILABLE",
"appointments": [
{
"id": "uuid",
"customerName": "Alice",
"status": "PENDING"
}
]
}
],
"requestId": "req_xxx",
"timestamp": "..."
}- notes:
Primary time-slot availability endpoint for this branch.
datemust be passed inYYYY-MM-DDformat.
-
method:
GET -
path:
/v1/notifications -
auth required?:
Yes -
request shape: Query params may include
userId,type,isRead,priority,page,limit. -
response shape:
{
"code": 200,
"message": "Notifications loaded",
"data": {
"items": [
{
"id": "uuid",
"userId": "uuid",
"type": "BOOKING_CONFIRMED",
"title": "Booking Confirmed",
"message": "Your booking AP-20260330-0001 has been confirmed",
"isRead": false,
"priority": "MEDIUM",
"metadata": {},
"createdAt": "2026-03-30T00:00:00.000Z",
"updatedAt": "2026-03-30T00:00:00.000Z"
}
],
"total": 1,
"page": 1,
"limit": 10,
"totalPages": 1
},
"requestId": "req_xxx",
"timestamp": "..."
}- notes: Retrieve notifications for the current user.
-
method:
GET -
path:
/v1/notifications/unread-count -
auth required?:
Yes -
request shape: No request body.
-
response shape:
{
"code": 200,
"message": "Unread count loaded",
"data": {
"count": 5
},
"requestId": "req_xxx",
"timestamp": "..."
}- notes: Get unread notification count for the current user.
- method:
PUT - path:
/v1/notifications/:id/read - auth required?:
Yes - request shape:
{}- response shape:
{
"code": 200,
"message": "Notification marked as read",
"data": {
"id": "uuid",
"isRead": true,
"updatedAt": "2026-03-30T00:00:00.000Z"
},
"requestId": "req_xxx",
"timestamp": "..."
}- notes: Mark a specific notification as read.
- method:
PUT - path:
/v1/notifications/read-all - auth required?:
Yes - request shape:
{}- response shape:
{
"code": 200,
"message": "All notifications marked as read",
"data": null,
"requestId": "req_xxx",
"timestamp": "..."
}- notes: Mark all notifications for the current user as read.