Guide - Branch Management
Each organization can have multiple branches. Branches are created through an invite-based flow — the owner invites a branch manager by email, the manager accepts, and the branch is created during that process.
Data Model
Each branch (IBranch) stores:
| Field | Type | Description |
|---|---|---|
_id | ObjectId | Unique branch ID |
organizationId | ObjectId | Parent organization |
managerId | ObjectId | Linked BRANCH_MANAGER user |
name | string | Human-readable name |
slug | string | URL-safe identifier (auto-generated from name) |
address | object | Region, province, city, barangay, zip |
status | string | active, suspended, or closed |
deletedAt | Date | null | Soft delete timestamp |
Note: The branch → manager relationship lives in
Branch.managerId, not in the user document.IUserhas nobranchIdfield.
Invite Flow (Creating a Branch)
Branches are not created directly. Instead, owners invite a branch manager by email and the branch is created when the manager accepts.
1. Owner POSTs invite (email + branch name)
↓
2. Manager receives email with invite link (contains token)
↓
3. Manager verifies token via GET endpoint
↓
4. Manager POSTs branch details (address, personal info)
↓
5. Branch + BRANCH_MANAGER user created together
Auto-generated on creation:
slug— derived from branch name (e.g."Manila Branch"→"manila-branch")status— defaults toactivecreatedAttimestamp
Uniqueness constraints:
- Branch name must be unique within the organization
- Branch slug must be unique within the organization
- Manager email must be unique across the entire system
API Endpoints
Send Branch Invite
POST /api/organizations/branches/invite
Authorization: Bearer <owner_token>
Content-Type: application/json
{
"email": "manager@example.com"
}
Response (200):
{ "message": "Branch manager invite sent successfully." }
List Branch Invites
GET /api/organizations/branches/invite
Authorization: Bearer <owner_token>
Response (200):
{
"invites": [
{
"_id": "507f1f77bcf86cd799439014",
"email": "manager@example.com",
"branchName": "Manila Branch",
"status": "pending",
"createdAt": "2026-02-21T00:00:00.000Z"
}
]
}
Resend Branch Invite
PUT /api/organizations/branches/invite/:id/resend
Authorization: Bearer <owner_token>
Response (200):
{ "message": "Invite resent successfully." }
Cancel Branch Invite
PUT /api/organizations/branches/invite/:id/cancel
Authorization: Bearer <owner_token>
Response (200):
{ "message": "Invite cancelled successfully." }
Verify Invite Token (Public)
Called by the manager's invite link before submitting branch creation form.
GET /api/organizations/branches/invite/token/:token/verify
Returns invite details (email, branch name) if valid. Returns 400 if expired or already used.
Create Branch (from Token)
POST /api/organizations/branches/token/:token
Content-Type: application/json
{
"address": {
"region": "NCR",
"province": "Metro Manila",
"municipalOrCity": "Manila",
"barangay": "Ermita",
"zip": "1000"
},
"firstName": "Juan",
"lastName": "Dela Cruz",
"phone": "09161234567",
"password": "SecurePassword123"
}
Response (201):
{ "message": "Branch successfully created." }
Get Branches (Paginated)
GET /api/organizations/branches
Authorization: Bearer <owner_token>
Query: page, limit, search
Response (200):
{
"data": [
{
"_id": "507f1f77bcf86cd799439011",
"name": "Manila Branch",
"slug": "manila-branch",
"organizationId": "507f1f77bcf86cd799439012",
"managerId": "507f1f77bcf86cd799439013",
"managerName": "Juan Dela Cruz",
"status": "active",
"createdAt": "2026-02-21T00:00:00.000Z"
}
],
"page": 1,
"limit": 10,
"total": 3,
"totalPages": 1
}
Update Branch Status
Status is supplied as a route parameter, not a request body field.
Allowed values: activate, suspend, close
PUT /api/organizations/branches/:id/status/:status
Authorization: Bearer <owner_token>
# Example: suspend a branch
PUT /api/organizations/branches/507f1f77bcf86cd799439011/status/suspend
Response (200):
{ "message": "Branch has been suspended successfully." }
Error responses:
| Status | Message |
|---|---|
| 404 | Branch not found |
| 400 | Branch does not belong to your organization |
| 400 | Branch is already suspended (or active / closed) |
Branch Status Lifecycle
ACTIVE ──suspend──→ SUSPENDED
SUSPENDED ──activate──→ ACTIVE
ACTIVE / SUSPENDED ──close──→ CLOSED
| Status | Accepts new bookings | Manager access | Reversible |
|---|---|---|---|
active | Yes | Full | — |
suspended | No | Read-only | Yes (activate) |
closed | No | Revoked | No |
Branch Manager Permissions
Branch managers are scoped to their own branch only:
| Can access | Cannot access |
|---|---|
| Own branch bookings | Other branches' bookings or data |
| Own branch customers | Organization-level settings |
| Own branch reports | Subscription / billing info |
Booking Rules
- A booking must be assigned to a branch at creation (
branchIdrequired). - The branch must be
activeto accept new bookings. - The branch must belong to the booking's
organizationId. - Branch managers cannot see bookings from other branches.
Related
Guide - Authentication Flow & Sessions
Understanding JWT tokens, refresh cycles, cookie management, and secure authentication patterns.
Guide - Customer Payment Methods
How customers save and manage Maya payment methods in booki-api, including payment preferences and organization-level payment method validation.
