Auth - Login & Refresh
Base path: /api/v1/auth
POST /api/v1/auth/login — Login
Auth: Public
Used by: All apps (cms-booki-web-app, owner-booki-web-app, customer-booki-web-app, admin-booki-web-app)
organizationId: Resolved by tenant middleware (resolveOptionalTenant) — if x-tenant-slug header is present, login is scoped to that organization (customers, owners, branch managers); if absent, only owner, branch-manager, and admin accounts may log in globally.
Description: Login with email and password. Returns accessToken and refreshToken in the response body; the $api plugin stores them and injects accessToken as Authorization: Bearer on all subsequent requests.
Request
{
"email": "owner@example.com",
"password": "SecureP@ss123"
}
Fields:
email(string, required): User email (lowercase, valid email format)password(string, required): Min 8 chars, uppercase + lowercase + number + special char
Note:
organizationIdis NOT sent in the request body. It is resolved from thex-tenant-slugheader (auto-injected by the Nuxt$apiplugin from the subdomain). For global owner/admin login (no subdomain), omit the header entirely.
Login Behavior by App
| App | Subdomain | Behavior |
|---|---|---|
cms-booki-web-app | janes-salon.booki.app | Tenant-scoped — customers, owners, branch managers of that org |
customer-booki-web-app | janes-salon.booki.app | Customer login scoped to tenant |
owner-booki-web-app | owner.booki.app (no tenant subdomain) | Global owner/admin login — x-tenant-slug not sent |
admin-booki-web-app | admin.booki.app (no tenant subdomain) | Global admin login |
Response (200 OK)
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "abc123def456...",
"_id": "507f1f77bcf86cd799439011"
}
The frontend plugin stores the tokens and sends Authorization: Bearer <accessToken> on every subsequent request.
Error Responses
422 Unprocessable Entity — Missing or invalid fields:
{
"statusCode": 422,
"message": "\"email\" must be a valid email"
}
400 Bad Request — Too many failed attempts (5+):
{
"statusCode": 400,
"message": "Too many failed attempts. Please try again later."
}
400 Bad Request — Suspended owner account:
{
"statusCode": 400,
"message": "Your account has been suspended. Please contact support for assistance."
}
404 Not Found — Invalid credentials:
{
"statusCode": 404,
"message": "Invalid credentials."
}
cURL Examples
Tenant-scoped login (customer/owner on subdomain):
curl -i -X POST http://localhost:4001/api/v1/auth/login \
-H "Content-Type: application/json" \
-H "X-Tenant-Slug: janes-salon" \
-d '{"email": "owner@example.com", "password": "SecureP@ss123"}'
Global owner/admin login (no subdomain):
curl -i -X POST http://localhost:4001/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "owner@example.com", "password": "SecureP@ss123"}'
POST /api/v1/auth/refresh — Refresh Token
Auth: Public (uses refreshToken from previous login)
Used by: All authenticated apps (automatic — called by $api plugin on 401)
organizationId: Carried over from the existing refresh token payload (no header needed)
Description: Exchange refresh token for a new access token without re-login.
Request
{
"token": "abc123def456..."
}
Response (200 OK)
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "newRefreshToken123..."
}
Error Responses
400 Bad Request — Invalid or expired refresh token:
{
"statusCode": 400,
"message": "Invalid or expired refresh token."
}
cURL Example
curl -i -X POST http://localhost:4001/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{"token": "abc123def456..."}'
GET /api/v1/auth/user — Get Current User
Auth: Protected (authenticate)
Used by: All authenticated apps (called on app load to hydrate currentUser state)
organizationId: From JWT token (embedded at login)
Description: Get the authenticated user's profile.
Request
Authorization: Bearer <accessToken>
Response (200 OK)
{
"_id": "507f1f77bcf86cd799439011",
"firstName": "Jane",
"lastName": "Doe",
"phone": "09171234567",
"type": "owner",
"email": "owner@example.com",
"organizationId": "507f191e810c19729de860ea",
"status": "active",
"createdAt": "2026-03-15T10:30:00Z",
"emailVerifiedAt": "2026-04-13T10:01:15.656Z",
"updatedAt": "2026-04-13T10:01:19.694Z"
}
Note:
typeandstatusvalues are lowercase strings matching theUserTypeandUserStatusenums ("owner","active","customer", etc.).
Error Responses
401 Unauthorized — No valid token:
{
"statusCode": 401,
"message": "Access token is required to proceed."
}
cURL Example
curl -X GET http://localhost:4001/api/v1/auth/user \
-H "Authorization: Bearer eyJhbGc..."
GET /api/v1/auth/organization — Get Current User's Organization
Auth: Protected (requireBranchManagerOrAbove — owner, branch-manager, admin)
Used by: owner-booki-web-app (loaded in 01.org.ts middleware on app boot)
organizationId: From JWT token (no tenant header needed — owner app does not send x-tenant-slug)
Description: Get the organization owned or managed by the currently authenticated user.
Request
Authorization: Bearer <accessToken>
Response (200 OK)
{
"_id": "507f191e810c19729de860ea",
"name": "Jane's Beauty Salon",
"businessPermit": "BP-2024-001",
"businessRegistration": "BR-2024-001",
"branch": 1,
"slug": "janes-salon",
"businessAddress": {
"region": "NCR",
"province": "Metro Manila",
"municipalOrCity": "Quezon City",
"barangay": "Doña Julia",
"zip": "1100",
"street": "Main Avenue",
"address": "123 Business Complex"
},
"mayaConnected": false,
"cashConnected": false,
"businessHoursSettings": {
"businessHours": [
{
"day": "monday",
"isOpen": true,
"openTime": "08:00",
"closeTime": "17:00"
},
{
"day": "tuesday",
"isOpen": true,
"openTime": "08:00",
"closeTime": "17:00"
},
{
"day": "wednesday",
"isOpen": true,
"openTime": "08:00",
"closeTime": "17:00"
},
{
"day": "thursday",
"isOpen": true,
"openTime": "08:00",
"closeTime": "17:00"
},
{
"day": "friday",
"isOpen": true,
"openTime": "08:00",
"closeTime": "17:00"
},
{
"day": "saturday",
"isOpen": false,
"openTime": "08:00",
"closeTime": "17:00"
},
{
"day": "sunday",
"isOpen": false,
"openTime": "08:00",
"closeTime": "17:00"
}
],
"timezone": "Asia/Manila",
"intervalMinutes": 60
},
"createdAt": "2026-03-15T10:30:00Z",
"updatedAt": "2026-04-13T10:01:16.911Z",
"serviceName": "Personal Care & Beauty"
}
cURL Example
curl -X GET http://localhost:4001/api/v1/auth/organization \
-H "Authorization: Bearer eyJhbGc..."
DELETE /api/v1/auth/logout — Logout
Auth: Protected (authenticate)
Used by: All authenticated apps
organizationId: From JWT token
Description: Invalidate the current access token (blacklist via JTI) and clear session cache.
Request
Authorization: Bearer <accessToken>
No body required.
Response (200 OK)
{
"message": "Logged out successfully."
}
cURL Example
curl -X DELETE http://localhost:4001/api/v1/auth/logout \
-H "Authorization: Bearer eyJhbGc..."
