API

Auth - Login & Refresh

Login, logout, and token refresh endpoints with real payloads and responses.

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: organizationId is NOT sent in the request body. It is resolved from the x-tenant-slug header (auto-injected by the Nuxt $api plugin from the subdomain). For global owner/admin login (no subdomain), omit the header entirely.

Login Behavior by App

AppSubdomainBehavior
cms-booki-web-appjanes-salon.booki.appTenant-scoped — customers, owners, branch managers of that org
customer-booki-web-appjanes-salon.booki.appCustomer login scoped to tenant
owner-booki-web-appowner.booki.app (no tenant subdomain)Global owner/admin login — x-tenant-slug not sent
admin-booki-web-appadmin.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: type and status values are lowercase strings matching the UserType and UserStatus enums ("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 (requireBranchManagerOrAboveowner, 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..."