Admin Portal API Flow
This document is for the Next.js admin portal developer integrating the Laravel admin portal APIs.
Base URL
{API_BASE_URL}/api/admin
Example local URL:
http://127.0.0.1:8000/api/admin
For protected APIs, send:
Authorization: Bearer {access_token}
Accept: application/json
Access Rules
Admin portal access is based on users.type.
| User type | Access |
|---|---|
SUPER_ADMIN |
Global access. Can log in to admin portal. |
ADMIN |
Admin portal only. |
MEMBER |
App only. Must not log in to admin portal. |
Do not mix this with app-side admin logic. App admin behavior belongs to member/chapter logic. Portal login gate remains users.type in (ADMIN, SUPER_ADMIN).
Spatie roles/permissions are used for portal authorization after login. They are separate from the initial users.type login gate.
Login Flow
- Admin enters username and password.
usernamecurrently supports email and also maps toemail_or_phone.- Backend checks password and confirms the user is
ADMINorSUPER_ADMIN. - Backend creates a
LOGINOTP. - If username is an email and the app is running in production, backend sends the OTP by Laravel mail.
- Admin submits OTP.
- Backend verifies OTP and returns a Sanctum token with
admin-portalability.
Outside production, the OTP is also returned in the login response for development/testing. In production, otp is returned as null.
Seeders
Run the default permission and role/user seeders:
php artisan db:seed --class=Database\\Seeders\\Configurations\\DefaultPermissionSeeder
php artisan db:seed --class=Database\\Seeders\\Configurations\\DefaultRoleAndUserSeeder
If DatabaseSeeder is configured to call them, this is enough:
php artisan db:seed
Default portal users:
| Type | Username | Password |
|---|---|---|
SUPER_ADMIN |
superadmin@bci.test |
password |
ADMIN |
kailashd.phppoets@gmail.com |
password |
SUPER_ADMIN |
test@example.com |
password |
Default roles:
| Role stored in DB | Frontend display |
|---|---|
SUPER_ADMIN |
Super Admin |
ADMIN |
Admin |
Role display names are not stored separately. The frontend can convert underscores to spaces and title-case the value when needed.
Mail Setup
OTP email uses Laravel mail in production. Configure .env with a real SMTP provider when testing real inbox delivery on a production/staging environment.
For Laravel Symfony Mailer, use MAIL_ENCRYPTION=tls, not MAIL_SCHEME=tls.
Example:
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=your_username
MAIL_PASSWORD=your_password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=no-reply@example.com
MAIL_FROM_NAME="BCI API"
For local logging, note that current admin login code only sends mail when app()->isProduction() is true:
MAIL_MAILER=log
The current admin login mail call is synchronous Mail::send, not queued.
Health API
Admin Health
GET /health
Success response:
{
"status": "ok",
"service": "BCI API",
"portal": "admin"
}
This endpoint does not require authentication.
Dashboard API
Dashboard
GET /dashboard
Authorization: Bearer {admin_access_token}
Use this API for the first admin dashboard load. It returns the summary cards and limited dashboard widgets in one response.
Success response:
{
"message": "Dashboard data found.",
"data": {
"counters": {
"total_members": 1248,
"active_businesses": 842,
"chapters": 24,
"upcoming_events": 36
},
"this_month": {
"new_members": 128,
"new_businesses": 96,
"new_referrals": 320,
"business_given": "1275000.00",
"points_earned": "4850.00"
},
"upcoming_events": [
{
"id": "event_uuid",
"title": "Monthly Networking Meet",
"event_type": {
"id": "event_type_uuid",
"code": "EVENT",
"name": "Event"
},
"event_date": "2026-05-25",
"start_time": "09:00",
"end_time": "11:00",
"location_name": "BCI Conference Hall",
"visible_to": "All Members",
"banner_image": "event-banners/networking.jpg",
"banner_image_url": "https://cdn.example.com/event-banners/networking.jpg",
"status": "ACTIVE"
}
],
"recent_activities": [
{
"id": "activity_uuid",
"activity_type": "REFERRAL",
"title": "Amit Shah sent referral to Neha Patel",
"description": "Amit Shah referred Rahul Mehta to Neha Patel",
"time_ago": "10 minutes ago",
"activity_at": "2026-05-28T10:00:00.000000Z",
"amount": null
}
],
"grievances": {
"open": 12,
"in_progress": 8,
"resolved": 45,
"closed": 30
}
}
}
Notes:
countersis for the top summary cards.this_monthis for current month growth summary.- Member and business totals include approved members only.
upcoming_eventsis limited to 5 records.recent_activitiesis limited to 5 records.grievancesis a status summary, not a full grievance list.
Recent Activities
GET /activities
Authorization: Bearer {admin_access_token}
Use this for the admin dashboard "View All" activity page.
Examples:
GET /activities
GET /activities?limit=10&page=1
GET /activities?activity_type=REFERRAL
GET /activities?chapter_id={chapter_id}
GET /activities?member_id={member_id}
GET /activities?search=Amit&from_date=2026-05-01&to_date=2026-05-31
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches title, description, source type, chapter name, member name/email/phone, and related member name/email/phone. |
q |
No | Alias for search. |
limit |
No | Pagination size, 1 to 100. Defaults to 10. |
per_page |
No | Backward-compatible alias for limit. |
page |
No | Page number. |
activity_type |
No | One of ONE_TO_ONE_SESSION, REFERRAL, or BUSINESS_EXCHANGE. |
chapter_id |
No | Filter by chapter. |
member_id |
No | Filter activities where this member is either the main member or related member. |
from_date |
No | Activity date lower bound. Alias: date_from. |
to_date |
No | Activity date upper bound. Alias: date_to. |
Success response:
{
"message": "Activities found.",
"meta": {
"total_records": 1,
"page": 1,
"limit": 10,
"total_pages": 1,
"record_start": 1,
"record_end": 1
},
"data": [
{
"id": "activity_uuid",
"chapter_id": "chapter_uuid",
"chapter": {
"id": "chapter_uuid",
"name": "Ahmedabad Chapter"
},
"member_id": "member_uuid",
"member": {
"id": "member_uuid",
"name": "Amit Shah",
"email": "amit@example.com",
"phone_number": "9876543210"
},
"related_member_id": "member_uuid",
"related_member": {
"id": "member_uuid",
"name": "Neha Patel",
"email": "neha@example.com",
"phone_number": "9876543211"
},
"activity_type": "REFERRAL",
"title": "Amit Shah sent referral to Neha Patel",
"description": "Amit Shah referred Rahul Mehta to Neha Patel",
"source_type": "Referral",
"source_id": "referral_uuid",
"amount": null,
"metadata": {
"referring_person": "Rahul Mehta"
},
"time_ago": "10 minutes ago",
"activity_at": "2026-05-28T10:00:00.000000Z",
"created_at": "2026-05-28T10:00:00.000000Z",
"updated_at": "2026-05-28T10:00:00.000000Z"
}
]
}
Notification APIs
Notification APIs are module-level admin routes for:
- sending manual general notifications
- viewing notification logs
Important implementation note:
- the current backend creates notification rows and recipient rows
- if Firebase push is enabled and configured, backend sends mobile push through Firebase FCM
- parent notification status is aggregated from recipient push results
- Firebase delivery is package-backed through
kreait/laravel-firebase - full Firebase setup is documented in firebase-push-notification-setup.md
List Notifications
GET /notifications
Authorization: Bearer {admin_access_token}
Examples:
GET /notifications
GET /notifications?limit=10&page=1
GET /notifications?type=GENERAL
GET /notifications?delivery_status=SENT
GET /notifications?member_id={member_id}
GET /notifications?chapter_id={chapter_id}
GET /notifications?from_date=2026-06-01&to_date=2026-06-30
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches title, message, and notification type code. |
q |
No | Alias for search. |
type |
No | Notification type code such as GENERAL, EVENT, EVENT_ATTENDANCE, REFERRAL, SESSION, BUSINESS_EXCHANGE, NEW_MEMBER_JOINED, CHAPTER_ASSIGNED. |
delivery_status |
No | PENDING, SENT, FAILED, or READ. This filters the parent notification status. |
member_id |
No | Filters notifications where the member exists in notification recipients. |
chapter_id |
No | Filters notifications by recipient chapter context. |
from_date |
No | Created date lower bound. Alias: date_from. |
to_date |
No | Created date upper bound. Alias: date_to. |
limit |
No | Pagination size, 1 to 100. Defaults to 10. |
per_page |
No | Backward-compatible alias for limit. |
page |
No | Page number. |
Success response:
{
"message": "Notifications found.",
"meta": {
"total_records": 1,
"page": 1,
"limit": 10,
"total_pages": 1,
"record_start": 1,
"record_end": 1
},
"data": [
{
"id": "notification_uuid",
"type": "GENERAL",
"title": "Monthly Meeting Reminder",
"message": "Meeting starts at 6 PM.",
"image_path": "notifications/general/image.png",
"image_url": "https://cdn.example.com/notifications/general/image.png",
"audience": "CURRENT_CHAPTER_MEMBERS",
"status": "SENT",
"recipients_count": 125,
"created_by": {
"id": "admin_user_uuid",
"name": "Portal Admin",
"email": "portal-admin@example.com"
},
"sent_at": "2026-06-02T10:30:00.000000Z",
"created_at": "2026-06-02T10:30:00.000000Z"
}
]
}
Send General Notification
POST /notifications/store
Authorization: Bearer {admin_access_token}
Content-Type: multipart/form-data
Request fields:
| Field | Required | Notes |
|---|---|---|
title |
Yes | String, max 255. |
message |
Yes | Notification body. |
audience |
Yes | ALL_MEMBERS or CURRENT_CHAPTER_MEMBERS. |
chapter_id |
Yes when audience = CURRENT_CHAPTER_MEMBERS |
Active chapter UUID. |
image |
No | JPG/JPEG/PNG/WEBP image, max 10 MB. |
notification_image |
No | Alias for image. |
Request example:
{
"title": "Monthly Meeting Reminder",
"message": "Meeting starts at 6 PM.",
"audience": "CURRENT_CHAPTER_MEMBERS",
"chapter_id": "chapter_uuid"
}
Success response:
{
"message": "General notification created successfully.",
"data": {
"id": "notification_uuid",
"type": "GENERAL",
"title": "Monthly Meeting Reminder",
"message": "Meeting starts at 6 PM.",
"image_path": null,
"image_url": null,
"audience": "CURRENT_CHAPTER_MEMBERS",
"status": "PENDING",
"recipients_count": 0,
"created_by": {
"id": "admin_user_uuid",
"name": "Portal Admin",
"email": "portal-admin@example.com"
},
"sent_at": null,
"created_at": "2026-06-02T10:30:00.000000Z"
}
}
Notes:
- recipient generation runs through a queued job for scale
- broad audience notifications are not processed inline in the request path
- real mobile push requires Firebase config and a running queue worker
Report APIs
Membership Report
GET /reports/membership
Authorization: Bearer {admin_access_token}
Use this report for membership expiry tracking. Each row is based on one member_chapter entry.
Logic:
joining_datecomes frommember_chapters.date_of_joiningdurationcomes from the selected chapter's current active feeexpiry_dateis calculated fromjoining_date + chapter fee durationexpired_inis calculated against the current server timemembership_statusis returned asACTIVE,EXPIRING_SOON, orEXPIRED
Important note:
- This report uses the chapter's current active fee, not a historical fee snapshot.
- Subscription notifications use the same expiry calculation through
php artisan notifications:send-subscription-status. - The subscription notification command sends
SUBSCRIPTIONnotifications to members whose chapter subscription is expiring within 7 days or already expired. RENEWEDnotification support exists in the common service, but automatic renewed trigger logic is pending until renewal storage/logic is added.
Member Points Foundation
The points module uses ledger-based storage so earned points remain auditable even if point rules change later.
Tables:
| Table | Purpose |
|---|---|
financial_years |
Stores April-March financial year windows. Created automatically when points are awarded or seeders run. |
point_types |
Master point categories such as MEMBER_REFERRAL and BUSINESS_EXCHANGE. |
point_rules |
Versioned calculation and expiry rules for each point type. |
member_point_ledgers |
Immutable earned/reversal ledger rows per member, chapter, source, financial year, point type, and point rule. |
Current point rules:
| Code | Trigger | Points |
|---|---|---|
EVENT_ATTENDED |
Event attendance marked present | 20 |
TIME_PUNCTUALITY |
Attendance marked within 15 minutes of event start | 10 |
BUSINESS_EXCHANGE |
Business exchange created | 1% of business amount |
MEMBER_REFERRAL |
Referral created | 50 |
ONE_TO_ONE_SESSION |
1:1 session created | 20 per participant |
Financial year logic:
- Backend resolves
financial_year_id; frontend does not pass it. - Financial year runs from April 1 to March 31.
- Example:
2026-06-12resolves toFY 2026-27.
Expiry logic:
- Point rule expiry supports
NONE,DAYS,MONTHS,QUARTERLY,HALF_YEARLY,YEARLY, andFINANCIAL_YEAR. - Current seeded point rules use
FINANCIAL_YEAR, soexpired_atis the linked financial year end date. - Dashboard point totals include only rows where
earned_at <= nowandexpired_atis null or in the future.
Duplicate rule:
- The ledger has a unique constraint on
point_type_id,source_type,source_id, andmember_id. - Re-submitting the same source action does not create duplicate points.
- The ledger also stores
point_rule_id, so historical rows keep the exact rule used even if the current rule changes later.
Examples:
GET /reports/membership
GET /reports/membership?tab=ALL
GET /reports/membership?tab=ACTIVE
GET /reports/membership?tab=EXPIRED
GET /reports/membership?limit=10&page=1
GET /reports/membership?search=Amit
GET /reports/membership?chapter_id={chapter_id}
GET /reports/membership?member_id={member_id}
GET /reports/membership?from_date=2026-01-01&to_date=2026-12-31
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches member name, email, phone number, member number, and chapter name. |
q |
No | Alias for search. |
chapter_id |
No | Filter by chapter. |
member_id |
No | Filter by member. |
tab |
No | Use ALL, ACTIVE, or EXPIRED. Do not use status for this report tab filter. |
from_date |
No | Joining date lower bound. Alias: date_from. |
to_date |
No | Joining date upper bound. Alias: date_to. |
limit |
No | Pagination size, 1 to 100. Defaults to 10. |
per_page |
No | Backward-compatible alias for limit. |
page |
No | Page number. |
Ordering:
ALL: active rows expiring soonest first, then expired rows.ACTIVE: only active rows, expiring soonest first.EXPIRED: only expired rows, most recently expired first.
Success response:
{
"message": "Membership reports found.",
"meta": {
"total_records": 1,
"page": 1,
"limit": 10,
"total_pages": 1,
"record_start": 1,
"record_end": 1
},
"data": [
{
"member_id": "member_uuid",
"member_chapter_id": "member_chapter_uuid",
"chapter_id": "chapter_uuid",
"member_name": "Amit Shah",
"chapter_name": "Ahmedabad Chapter",
"joining_date": "2026-05-01",
"duration": "12 MONTHS",
"expiry_date": "2027-05-01",
"membership_status": "ACTIVE",
"expired_in": {
"is_expired": false,
"years": 0,
"months": 11,
"days": 3,
"hours": 0,
"minutes": 0,
"time": "Expires in 11 months 3 days"
}
}
]
}
Member Points Report
GET /reports/member-points
Authorization: Bearer {admin_access_token}
Use this report for member point ledger tracking. Each row is based on one member_point_ledgers entry.
Examples:
GET /reports/member-points
GET /reports/member-points?tab=ALL
GET /reports/member-points?tab=ACTIVE
GET /reports/member-points?tab=EXPIRED
GET /reports/member-points?limit=10&page=1
GET /reports/member-points?search=Amit
GET /reports/member-points?chapter_id={chapter_id}
GET /reports/member-points?member_id={member_id}
GET /reports/member-points?point_type_id={point_type_id}
GET /reports/member-points?financial_year_id={financial_year_id}
GET /reports/member-points?from_date=2026-04-01&to_date=2027-03-31
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches member number, member name, email, phone number, chapter name, point type name/code, and remarks. |
q |
No | Alias for search. |
chapter_id |
No | Filter by chapter. |
member_id |
No | Filter by member. |
point_type_id |
No | Filter by point type. |
financial_year_id |
No | Filter by financial year. |
tab |
No | Use ALL, ACTIVE, or EXPIRED. |
from_date |
No | Earned date lower bound. Alias: date_from. |
to_date |
No | Earned date upper bound. Alias: date_to. |
limit |
No | Pagination size, 1 to 100. Defaults to 10. |
per_page |
No | Backward-compatible alias for limit. |
page |
No | Page number. |
Ordering:
ALL: active rows first, then expired rows.ACTIVE: active rows ordered by nearest expiry, then latest earned rows.EXPIRED: expired rows ordered by latest expiry first.
Success response:
{
"message": "Member points reports found.",
"meta": {
"total_records": 1,
"page": 1,
"limit": 10,
"total_pages": 1,
"record_start": 1,
"record_end": 1
},
"data": [
{
"id": "ledger_uuid",
"member_id": "member_uuid",
"member_chapter_id": "member_chapter_uuid",
"chapter_id": "chapter_uuid",
"financial_year_id": "financial_year_uuid",
"point_type_id": "point_type_uuid",
"point_rule_id": "point_rule_uuid",
"member_number": "BCI001",
"member_name": "Amit Shah",
"chapter_name": "Ahmedabad Chapter",
"financial_year_name": "FY 2026-27",
"financial_year_code": "FY2026-27",
"point_type": {
"id": "point_type_uuid",
"code": "MEMBER_REFERRAL",
"name": "Member Referral"
},
"point_rule": {
"id": "point_rule_uuid",
"calculation_type": "FIXED",
"percentage": null,
"expiry_type": "FINANCIAL_YEAR",
"expiry_value": null
},
"points": "50.00",
"base_amount": null,
"source_type": "Referral",
"source_id": "source_uuid",
"remarks": "Member referral points.",
"earned_at": "2026-06-12T10:00:00.000000Z",
"expired_at": "2027-03-31T23:59:59.000000Z",
"point_status": "ACTIVE"
}
]
}
Auth APIs
Login: Send OTP
POST /auth/login
Request:
{
"username": "kailashd.phppoets@gmail.com",
"password": "password"
}
email_or_phone is also accepted:
{
"email_or_phone": "kailashd.phppoets@gmail.com",
"password": "password"
}
Validation:
| Field | Required | Notes |
|---|---|---|
username |
Yes if email_or_phone is not sent |
Alias for email_or_phone. |
email_or_phone |
Yes if username is not sent |
Email now; mobile number supported by API path for future use. |
password |
Yes | User password. |
Success response:
{
"message": "OTP sent successfully.",
"expires_at": "2026-05-20T10:00:00.000000Z",
"otp": "123456"
}
Behavior:
- This endpoint does not return a login token.
- It creates an OTP with type
LOGIN. - OTP is valid for 5 minutes.
ADMINandSUPER_ADMINcan receive OTP.MEMBERis rejected and no OTP is created.- Wrong password is rejected and no OTP is created.
Common validation error:
{
"message": "The provided credentials are incorrect.",
"errors": {
"email_or_phone": [
"The provided credentials are incorrect."
]
}
}
Login: Verify OTP
POST /auth/login/verify
Request:
{
"email_or_phone": "kailashd.phppoets@gmail.com",
"otp": "123456",
"device_name": "admin-web"
}
username is also accepted:
{
"username": "kailashd.phppoets@gmail.com",
"otp": "123456"
}
Validation:
| Field | Required | Notes |
|---|---|---|
username |
Yes if email_or_phone is not sent |
Alias for email_or_phone. |
email_or_phone |
Yes if username is not sent |
Same email or phone used for login OTP. |
otp |
Yes | 6 digits, must exist, not expired, and not used. |
device_name |
No | Defaults to admin-portal-login. |
Success response:
{
"message": "Login successful.",
"token_type": "Bearer",
"access_token": "ADMIN_PORTAL_ACCESS_TOKEN",
"data": {
"id": "user_uuid",
"name": "BCI Admin",
"email": "kailashd.phppoets@gmail.com",
"phone_number": "0000000001",
"role": "ADMIN",
"role_id": "role_uuid"
}
}
Behavior:
- OTP is marked as used after successful verification.
- Returned Sanctum token has
admin-portalability. roleis the assigned Spatie role code fromroles.name.role_idis the assigned Spatie role UUID.- Use this token for protected admin portal APIs.
Common OTP errors:
{
"message": "The OTP is invalid.",
"errors": {
"otp": [
"The OTP is invalid."
]
}
}
{
"message": "The OTP has expired.",
"errors": {
"otp": [
"The OTP has expired."
]
}
}
{
"message": "The OTP has already been used.",
"errors": {
"otp": [
"The OTP has already been used."
]
}
}
Route Summary
GET /api/admin/health
GET /api/admin/dashboard
GET /api/admin/activities
GET /api/admin/notifications
POST /api/admin/notifications/store
GET /api/admin/reports/membership
POST /api/admin/auth/login
POST /api/admin/auth/login/verify
GET /api/admin/masters/permissions
GET /api/admin/masters/categories
POST /api/admin/masters/categories
GET /api/admin/masters/categories/{category}
PUT /api/admin/masters/categories/{category}
PATCH /api/admin/masters/categories/{category}
DELETE /api/admin/masters/categories/{category}
GET /api/admin/masters/chapters
POST /api/admin/masters/chapters
GET /api/admin/masters/chapters/{chapter}
PUT /api/admin/masters/chapters/{chapter}
PATCH /api/admin/masters/chapters/{chapter}
DELETE /api/admin/masters/chapters/{chapter}
GET /api/admin/masters/countries
POST /api/admin/masters/countries
GET /api/admin/masters/countries/{country}
PUT /api/admin/masters/countries/{country}
PATCH /api/admin/masters/countries/{country}
DELETE /api/admin/masters/countries/{country}
GET /api/admin/masters/states
POST /api/admin/masters/states
GET /api/admin/masters/states/{state}
PUT /api/admin/masters/states/{state}
PATCH /api/admin/masters/states/{state}
DELETE /api/admin/masters/states/{state}
GET /api/admin/masters/cities
POST /api/admin/masters/cities
GET /api/admin/masters/cities/{city}
PUT /api/admin/masters/cities/{city}
PATCH /api/admin/masters/cities/{city}
DELETE /api/admin/masters/cities/{city}
GET /api/admin/masters/code-of-conduct
POST /api/admin/masters/code-of-conduct/upload
GET /api/admin/masters/event-types
GET /api/admin/masters/member-types
GET /api/admin/masters/users
POST /api/admin/masters/users
GET /api/admin/masters/users/{user}
PUT /api/admin/masters/users/{user}
PATCH /api/admin/masters/users/{user}
DELETE /api/admin/masters/users/{user}
GET /api/admin/masters/roles
POST /api/admin/masters/roles
GET /api/admin/masters/roles/{role}
PUT /api/admin/masters/roles/{role}
PATCH /api/admin/masters/roles/{role}
DELETE /api/admin/masters/roles/{role}
GET /api/admin/masters/roles/permissions/assigned/{role?}
GET /api/admin/masters/roles/permissions/sidebar/{role?}
GET /api/admin/masters/roles/permissions/{role}
POST /api/admin/masters/roles/permissions/{role}/sync
GET /api/admin/members
POST /api/admin/members
POST /api/admin/members/{member}/approve
POST /api/admin/members/bulk-approve
GET /api/admin/members/{member}/chapters
POST /api/admin/members/{member}/chapters
GET /api/admin/members/{member}
PUT /api/admin/members/{member}
PATCH /api/admin/members/{member}
DELETE /api/admin/members/{member}
GET /api/admin/one-to-one-sessions
GET /api/admin/one-to-one-sessions/member-options
POST /api/admin/one-to-one-sessions
GET /api/admin/one-to-one-sessions/{one_to_one_session}
PUT /api/admin/one-to-one-sessions/{one_to_one_session}
PATCH /api/admin/one-to-one-sessions/{one_to_one_session}
DELETE /api/admin/one-to-one-sessions/{one_to_one_session}
GET /api/admin/referrals
GET /api/admin/referrals/member-options
POST /api/admin/referrals
GET /api/admin/referrals/{referral}
PUT /api/admin/referrals/{referral}
PATCH /api/admin/referrals/{referral}
DELETE /api/admin/referrals/{referral}
GET /api/admin/business-exchanges
GET /api/admin/business-exchanges/member-options
POST /api/admin/business-exchanges
GET /api/admin/business-exchanges/{business_exchange}
PUT /api/admin/business-exchanges/{business_exchange}
PATCH /api/admin/business-exchanges/{business_exchange}
DELETE /api/admin/business-exchanges/{business_exchange}
POST /api/admin/events
GET /api/admin/events
GET /api/admin/events/{event}
PUT /api/admin/events/{event}
PATCH /api/admin/events/{event}
DELETE /api/admin/events/{event}
Admin routes are grouped in code as:
require __DIR__ . '/Auth/auth.route.php';
require __DIR__ . '/Masters/masters.route.php';
require __DIR__ . '/Modules/modules.route.php';
require __DIR__ . '/Reports/reports.route.php';
Auth routes live under /api/admin/auth. Master resources live under /api/admin/masters. Module routes live under /api/admin/activities, /api/admin/notifications, /api/admin/members, /api/admin/one-to-one-sessions, /api/admin/referrals, /api/admin/business-exchanges, and /api/admin/events. Report routes live under /api/admin/reports.
Route-level permissions:
| Route | Required permission |
|---|---|
GET /api/admin/dashboard |
DASHBOARD |
GET /api/admin/activities |
ACTIVITY |
GET /api/admin/notifications |
NOTIFICATION_LIST |
POST /api/admin/notifications/store |
NOTIFICATION_ADD |
GET /api/admin/reports/membership |
MEMBERSHIP_REPORT |
GET /api/admin/masters/permissions |
PERMISSION_LIST |
GET /api/admin/masters/categories |
CATEGORY_LIST |
POST /api/admin/masters/categories |
CATEGORY_ADD |
GET /api/admin/masters/categories/{category} |
CATEGORY_EDIT |
PUT/PATCH /api/admin/masters/categories/{category} |
CATEGORY_EDIT |
DELETE /api/admin/masters/categories/{category} |
CATEGORY_DELETE |
GET /api/admin/masters/chapters |
CHAPTER_LIST |
POST /api/admin/masters/chapters |
CHAPTER_ADD |
GET /api/admin/masters/chapters/{chapter} |
CHAPTER_EDIT |
PUT/PATCH /api/admin/masters/chapters/{chapter} |
CHAPTER_EDIT |
DELETE /api/admin/masters/chapters/{chapter} |
CHAPTER_DELETE |
GET /api/admin/masters/countries |
COUNTRY_LIST |
POST /api/admin/masters/countries |
COUNTRY_ADD |
GET /api/admin/masters/countries/{country} |
COUNTRY_EDIT |
PUT/PATCH /api/admin/masters/countries/{country} |
COUNTRY_EDIT |
DELETE /api/admin/masters/countries/{country} |
COUNTRY_DELETE |
GET /api/admin/masters/states |
STATE_LIST |
POST /api/admin/masters/states |
STATE_ADD |
GET /api/admin/masters/states/{state} |
STATE_EDIT |
PUT/PATCH /api/admin/masters/states/{state} |
STATE_EDIT |
DELETE /api/admin/masters/states/{state} |
STATE_DELETE |
GET /api/admin/masters/cities |
CITY_LIST |
POST /api/admin/masters/cities |
CITY_ADD |
GET /api/admin/masters/cities/{city} |
CITY_EDIT |
PUT/PATCH /api/admin/masters/cities/{city} |
CITY_EDIT |
DELETE /api/admin/masters/cities/{city} |
CITY_DELETE |
GET /api/admin/masters/code-of-conduct |
CODE_OF_CONDUCT_LIST |
POST /api/admin/masters/code-of-conduct/upload |
CODE_OF_CONDUCT_EDIT |
GET /api/admin/masters/event-types |
EVENT_LIST or EVENT_ADD or EVENT_EDIT or EVENT_VIEW |
GET /api/admin/masters/member-types |
MEMBER_TYPE |
GET /api/admin/masters/users |
ADMIN_USER_LIST |
POST /api/admin/masters/users |
ADMIN_USER_ADD |
GET /api/admin/masters/users/{user} |
ADMIN_USER_VIEW |
PUT/PATCH /api/admin/masters/users/{user} |
ADMIN_USER_EDIT |
DELETE /api/admin/masters/users/{user} |
ADMIN_USER_DELETE |
GET /api/admin/masters/roles |
ROLE_LIST |
POST /api/admin/masters/roles |
ROLE_ADD |
GET /api/admin/masters/roles/{role} |
ROLE_EDIT |
PUT/PATCH /api/admin/masters/roles/{role} |
ROLE_EDIT |
DELETE /api/admin/masters/roles/{role} |
ROLE_DELETE |
GET /api/admin/masters/roles/permissions/assigned/{role?} |
auth:sanctum + admin.portal |
GET /api/admin/masters/roles/permissions/sidebar/{role?} |
auth:sanctum + admin.portal |
GET /api/admin/masters/roles/permissions/{role} |
ASSIGN_PERMISSION |
POST /api/admin/masters/roles/permissions/{role}/sync |
ASSIGN_PERMISSION |
GET /api/admin/members |
MEMBER_LIST |
POST /api/admin/members |
MEMBER_ADD |
POST /api/admin/members/{member}/approve |
MEMBER_APPROVE |
POST /api/admin/members/bulk-approve |
MEMBER_APPROVE |
GET /api/admin/members/{member}/chapters |
MEMBER_ASSIGN_CHAPTER |
POST /api/admin/members/{member}/chapters |
MEMBER_ASSIGN_CHAPTER |
GET /api/admin/members/{member} |
MEMBER_VIEW |
PUT/PATCH /api/admin/members/{member} |
MEMBER_EDIT |
DELETE /api/admin/members/{member} |
MEMBER_DELETE |
GET /api/admin/one-to-one-sessions |
ONE_TO_ONE_SESSION_LIST |
GET /api/admin/one-to-one-sessions/member-options |
ONE_TO_ONE_SESSION_ADD or ONE_TO_ONE_SESSION_EDIT |
POST /api/admin/one-to-one-sessions |
ONE_TO_ONE_SESSION_ADD |
GET /api/admin/one-to-one-sessions/{one_to_one_session} |
ONE_TO_ONE_SESSION_VIEW |
PUT/PATCH /api/admin/one-to-one-sessions/{one_to_one_session} |
ONE_TO_ONE_SESSION_EDIT |
DELETE /api/admin/one-to-one-sessions/{one_to_one_session} |
ONE_TO_ONE_SESSION_DELETE |
GET /api/admin/referrals |
REFERRAL_LIST |
GET /api/admin/referrals/member-options |
REFERRAL_ADD or REFERRAL_EDIT |
POST /api/admin/referrals |
REFERRAL_ADD |
GET /api/admin/referrals/{referral} |
REFERRAL_VIEW |
PUT/PATCH /api/admin/referrals/{referral} |
REFERRAL_EDIT |
DELETE /api/admin/referrals/{referral} |
REFERRAL_DELETE |
GET /api/admin/business-exchanges |
BUSINESS_EXCHANGE_LIST |
GET /api/admin/business-exchanges/member-options |
BUSINESS_EXCHANGE_ADD or BUSINESS_EXCHANGE_EDIT |
POST /api/admin/business-exchanges |
BUSINESS_EXCHANGE_ADD |
GET /api/admin/business-exchanges/{business_exchange} |
BUSINESS_EXCHANGE_VIEW |
PUT/PATCH /api/admin/business-exchanges/{business_exchange} |
BUSINESS_EXCHANGE_EDIT |
DELETE /api/admin/business-exchanges/{business_exchange} |
BUSINESS_EXCHANGE_DELETE |
POST /api/admin/events |
EVENT_ADD |
GET /api/admin/events |
EVENT_LIST |
GET /api/admin/events/{event} |
EVENT_VIEW |
PUT/PATCH /api/admin/events/{event} |
EVENT_EDIT |
DELETE /api/admin/events/{event} |
EVENT_DELETE |
SUPER_ADMIN bypasses these permission checks through the global gate. ADMIN must have the required Spatie permission.
Category, country, state, city, chapter, and role masters do not generate separate VIEW permissions. Their detail GET endpoints load edit-form data and therefore use the corresponding *_EDIT permission.
Referenced master records cannot be deleted directly. This protection applies to categories, countries, states, cities, chapters, event types, member types, roles, and users. If a record is used by another table, delete returns 422 Unprocessable Entity with a validation-style message. Reassign or remove dependent records first.
Role route parameters use UUID Spatie role IDs:
->whereUuid('role')
So {role} must be a UUID. It will not accidentally catch random slugs like /xyz-role.
Admin user route parameters use UUID user IDs:
->whereUuid('user')
So {user} must be a UUID.
Category route parameters use UUID business category IDs:
->whereUuid('category')
So {category} must be a UUID.
Chapter route parameters use UUID chapter IDs:
->whereUuid('chapter')
So {chapter} must be a UUID.
Country route parameters use UUID country IDs:
->whereUuid('country')
So {country} must be a UUID.
State route parameters use UUID state IDs:
->whereUuid('state')
So {state} must be a UUID.
City route parameters use UUID city IDs:
->whereUuid('city')
So {city} must be a UUID.
Permission APIs
Permission APIs require:
Authorization: Bearer {admin_portal_access_token}
List Permissions
GET /masters/permissions
Examples:
GET /masters/permissions
GET /masters/permissions?search=role
GET /masters/permissions?q=member
GET /masters/permissions?guard_name=web
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches permission name. |
q |
No | Alias for search. |
guard_name |
No | Filters by guard. |
Success response:
{
"message": "Permissions found.",
"total_permissions": 1,
"data": [
{
"id": "permission_uuid",
"name": "Roles",
"module": "ROLE",
"parent_id": "parent_permission_uuid",
"route_name": "roles.index",
"icon": "fas fa-shield-alt",
"status": "ACTIVE",
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
]
}
When no permissions are found:
{
"message": "Permissions not found.",
"total_permissions": 0,
"data": []
}
Category APIs
Category APIs manage records in business_categories.
Use parent_id = null for a top-level category. Use parent_id = category_uuid to create a sub-category under a parent category.
List Categories
GET /masters/categories
Examples:
GET /masters/categories
GET /masters/categories?search=technology
GET /masters/categories?q=legal
GET /masters/categories?status=ACTIVE
GET /masters/categories?parent_id=null
GET /masters/categories?parent_id=category_uuid
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches name and description. |
q |
No | Alias for search. |
status |
No | ACTIVE or INACTIVE. |
parent_id |
No | Send null or empty string for top-level categories. Send parent category UUID for sub-categories. |
Success response:
{
"message": "Categories found.",
"total_categories": 1,
"data": [
{
"id": "category_uuid",
"parent_id": null,
"name": "Professional Services",
"description": "Business service category.",
"status": "ACTIVE",
"parent": null,
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
]
}
When no categories are found:
{
"message": "Categories not found.",
"total_categories": 0,
"data": []
}
Create Category
POST /masters/categories
Request for top-level category:
{
"name": "Professional Services",
"description": "Business service category.",
"status": "ACTIVE"
}
Request for sub-category:
{
"parent_id": "category_uuid",
"name": "Legal Consulting",
"description": null,
"status": "ACTIVE"
}
Validation:
| Field | Required | Notes |
|---|---|---|
parent_id |
No | Existing business_categories.id. Omit or send null for top-level category. |
name |
Yes | Category display name. |
description |
No | Category description. |
status |
No | ACTIVE or INACTIVE. Defaults to ACTIVE. |
Success response:
{
"message": "Category created successfully.",
"data": {
"id": "category_uuid",
"parent_id": null,
"name": "Professional Services",
"description": "Business service category.",
"status": "ACTIVE",
"parent": null,
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
}
Category Details
GET /masters/categories/{category}
Success response:
{
"message": "Category found.",
"data": {
"id": "category_uuid",
"parent_id": "parent_category_uuid",
"name": "Legal Consulting",
"description": null,
"status": "ACTIVE",
"parent": {
"id": "parent_category_uuid",
"name": "Professional Services"
},
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
}
Update Category
PATCH /masters/categories/{category}
PUT /masters/categories/{category} is also accepted.
Request:
{
"parent_id": "parent_category_uuid",
"name": "Legal Advisory",
"description": null,
"status": "INACTIVE"
}
Validation:
| Field | Required | Notes |
|---|---|---|
parent_id |
No | Existing category UUID. Cannot be the same as {category}. |
name |
Yes | Category display name. |
description |
No | Send null if empty. |
status |
Yes | ACTIVE or INACTIVE. |
Success response:
{
"message": "Category updated successfully.",
"data": {
"id": "category_uuid",
"parent_id": "parent_category_uuid",
"name": "Legal Advisory",
"description": null,
"status": "INACTIVE",
"parent": {
"id": "parent_category_uuid",
"name": "Professional Services"
},
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:30:00.000000Z"
}
}
Delete Category
DELETE /masters/categories/{category}
Success response:
{
"message": "Category deleted successfully."
}
Delete soft-deletes the category. A soft-deleted category name can be created again.
If the category is referenced by child categories or other module records, delete is blocked with 422 Unprocessable Entity and a validation-style message. Remove or reassign dependent records first.
Chapter APIs
Chapter APIs manage records in chapters and their location/business mappings in chapter_rows.
The admin portal creates and updates a chapter and its chapter rows in one request. Do not call a separate chapter row endpoint. On update, the backend replaces the chapter's existing rows with the submitted rows array.
Each row maps one chapter to:
country_idstate_idcity_idbusiness_category_idbusiness_sub_category_id
List Chapters
GET /masters/chapters
Examples:
GET /masters/chapters
GET /masters/chapters?search=mumbai
GET /masters/chapters?q=business
GET /masters/chapters?status=ACTIVE
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches chapter name and description. |
q |
No | Alias for search. |
status |
No | ACTIVE or INACTIVE. |
Success response:
{
"message": "Chapters found.",
"total_chapters": 1,
"data": [
{
"id": "chapter_uuid",
"name": "Mumbai Business Chapter",
"description": "Mumbai chapter.",
"status": "ACTIVE",
"chapter_rows": [
{
"id": "chapter_row_uuid",
"country_id": "country_uuid",
"state_id": "state_uuid",
"city_id": "city_uuid",
"business_category_id": "category_uuid",
"business_sub_category_id": "sub_category_uuid",
"status": "ACTIVE",
"country": {
"id": "country_uuid",
"name": "India",
"iso2": "IN"
},
"state": {
"id": "state_uuid",
"country_id": "country_uuid",
"name": "Maharashtra",
"code": "MH"
},
"city": {
"id": "city_uuid",
"state_id": "state_uuid",
"name": "Mumbai"
},
"business_category": {
"id": "category_uuid",
"name": "Professional Services"
},
"business_sub_category": {
"id": "sub_category_uuid",
"parent_id": "category_uuid",
"name": "Legal Consulting"
}
}
],
"rows": [
{
"id": "chapter_row_uuid",
"country_id": "country_uuid",
"state_id": "state_uuid",
"city_id": "city_uuid",
"business_category_id": "category_uuid",
"business_sub_category_id": "sub_category_uuid",
"status": "ACTIVE"
}
],
"created_at": "2026-05-21T10:00:00.000000Z",
"updated_at": "2026-05-21T10:00:00.000000Z"
}
]
}
rows is currently returned as a compatibility alias for chapter_rows. Frontend should prefer chapter_rows for display.
When no chapters are found:
{
"message": "Chapters not found.",
"total_chapters": 0,
"data": []
}
Create Chapter
POST /masters/chapters
Request:
{
"name": "Mumbai Business Chapter",
"description": "Mumbai chapter.",
"status": "ACTIVE",
"rows": [
{
"country_id": "country_uuid",
"state_id": "state_uuid",
"city_id": "city_uuid",
"business_category_id": "category_uuid",
"business_sub_category_id": "sub_category_uuid",
"status": "ACTIVE"
}
]
}
Validation:
| Field | Required | Notes |
|---|---|---|
name |
Yes | Chapter display name. Must be unique among non-deleted chapters. |
description |
No | Chapter description. |
status |
No | ACTIVE or INACTIVE. Defaults to ACTIVE. |
rows |
Yes | Array with at least one row. |
rows.*.country_id |
Yes | Existing countries.id. |
rows.*.state_id |
Yes | Existing states.id. Must belong to country_id. |
rows.*.city_id |
Yes | Existing cities.id. Must belong to state_id. |
rows.*.business_category_id |
Yes | Existing top-level business_categories.id. |
rows.*.business_sub_category_id |
Yes | Existing business_categories.id. Must belong to business_category_id. |
rows.*.status |
No | ACTIVE or INACTIVE. Defaults to ACTIVE. |
Duplicate rows in the same request are rejected. A duplicate row means the same country, state, city, business category, and business sub-category combination appears more than once for the chapter.
Success response:
{
"message": "Chapter created successfully.",
"data": {
"id": "chapter_uuid",
"name": "Mumbai Business Chapter",
"description": "Mumbai chapter.",
"status": "ACTIVE",
"chapter_rows": [
{
"id": "chapter_row_uuid",
"country_id": "country_uuid",
"state_id": "state_uuid",
"city_id": "city_uuid",
"business_category_id": "category_uuid",
"business_sub_category_id": "sub_category_uuid",
"status": "ACTIVE"
}
],
"rows": [
{
"id": "chapter_row_uuid",
"country_id": "country_uuid",
"state_id": "state_uuid",
"city_id": "city_uuid",
"business_category_id": "category_uuid",
"business_sub_category_id": "sub_category_uuid",
"status": "ACTIVE"
}
],
"created_at": "2026-05-21T10:00:00.000000Z",
"updated_at": "2026-05-21T10:00:00.000000Z"
}
}
Chapter Details
GET /masters/chapters/{chapter}
Success response:
{
"message": "Chapter found.",
"data": {
"id": "chapter_uuid",
"name": "Mumbai Business Chapter",
"description": "Mumbai chapter.",
"status": "ACTIVE",
"chapter_rows": [
{
"id": "chapter_row_uuid",
"country_id": "country_uuid",
"state_id": "state_uuid",
"city_id": "city_uuid",
"business_category_id": "category_uuid",
"business_sub_category_id": "sub_category_uuid",
"status": "ACTIVE",
"country": {
"id": "country_uuid",
"name": "India",
"iso2": "IN"
},
"state": {
"id": "state_uuid",
"country_id": "country_uuid",
"name": "Maharashtra",
"code": "MH"
},
"city": {
"id": "city_uuid",
"state_id": "state_uuid",
"name": "Mumbai"
},
"business_category": {
"id": "category_uuid",
"name": "Professional Services"
},
"business_sub_category": {
"id": "sub_category_uuid",
"parent_id": "category_uuid",
"name": "Legal Consulting"
}
}
],
"rows": [
{
"id": "chapter_row_uuid",
"country_id": "country_uuid",
"state_id": "state_uuid",
"city_id": "city_uuid",
"business_category_id": "category_uuid",
"business_sub_category_id": "sub_category_uuid",
"status": "ACTIVE"
}
],
"created_at": "2026-05-21T10:00:00.000000Z",
"updated_at": "2026-05-21T10:00:00.000000Z"
}
}
Update Chapter
PATCH /masters/chapters/{chapter}
PUT /masters/chapters/{chapter} is also accepted.
Request:
{
"name": "Mumbai Prime Chapter",
"description": null,
"status": "INACTIVE",
"rows": [
{
"country_id": "country_uuid",
"state_id": "state_uuid",
"city_id": "city_uuid",
"business_category_id": "category_uuid",
"business_sub_category_id": "sub_category_uuid",
"status": "ACTIVE"
}
]
}
Validation is the same as create, except status is required and name uniqueness ignores the current chapter.
Update is not partial. Send the full editable chapter payload and the full intended rows array. Existing chapter_rows for the chapter are soft-deleted and replaced with the submitted rows.
Success response:
{
"message": "Chapter updated successfully.",
"data": {
"id": "chapter_uuid",
"name": "Mumbai Prime Chapter",
"description": null,
"status": "INACTIVE",
"chapter_rows": [
{
"id": "new_chapter_row_uuid",
"country_id": "country_uuid",
"state_id": "state_uuid",
"city_id": "city_uuid",
"business_category_id": "category_uuid",
"business_sub_category_id": "sub_category_uuid",
"status": "ACTIVE"
}
],
"rows": [
{
"id": "new_chapter_row_uuid",
"country_id": "country_uuid",
"state_id": "state_uuid",
"city_id": "city_uuid",
"business_category_id": "category_uuid",
"business_sub_category_id": "sub_category_uuid",
"status": "ACTIVE"
}
],
"created_at": "2026-05-21T10:00:00.000000Z",
"updated_at": "2026-05-21T10:30:00.000000Z"
}
}
Delete Chapter
DELETE /masters/chapters/{chapter}
Success response:
{
"message": "Chapter deleted successfully."
}
Delete soft-deletes the chapter.
If the chapter is referenced by members, chapter rows, sessions, referrals, business exchanges, events, or other module records, delete is blocked with 422 Unprocessable Entity and a validation-style message. Remove or reassign dependent records first.
Country APIs
Country APIs manage records in countries.
List Countries
GET /masters/countries
Examples:
GET /masters/countries
GET /masters/countries?search=india
GET /masters/countries?q=inr
GET /masters/countries?status=ACTIVE
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches name, ISO codes, phone code, currency code, and currency name. |
q |
No | Alias for search. |
status |
No | ACTIVE or INACTIVE. |
Success response:
{
"message": "Countries found.",
"total_countries": 1,
"data": [
{
"id": "country_uuid",
"name": "India",
"iso2": "IN",
"iso3": "IND",
"phone_code": "+91",
"currency_code": "INR",
"currency_name": "Indian Rupee",
"status": "ACTIVE",
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
]
}
When no countries are found:
{
"message": "Countries not found.",
"total_countries": 0,
"data": []
}
Create Country
POST /masters/countries
Request:
{
"name": "India",
"iso2": "in",
"iso3": "ind",
"phone_code": "+91",
"currency_code": "inr",
"currency_name": "Indian Rupee",
"status": "ACTIVE"
}
Validation:
| Field | Required | Notes |
|---|---|---|
name |
Yes | Must be unique among non-deleted countries. A soft-deleted country name can be reused. |
iso2 |
Yes | 2 letters. Must be unique among non-deleted countries. Backend stores uppercase. |
iso3 |
No | 3 letters if sent. Must be unique among non-deleted countries. Backend stores uppercase. |
phone_code |
No | Country calling code, for example +91. |
currency_code |
No | 3 letters if sent. Backend stores uppercase. |
currency_name |
No | Currency display name. |
status |
No | ACTIVE or INACTIVE. Defaults to ACTIVE. |
Success response:
{
"message": "Country created successfully.",
"data": {
"id": "country_uuid",
"name": "India",
"iso2": "IN",
"iso3": "IND",
"phone_code": "+91",
"currency_code": "INR",
"currency_name": "Indian Rupee",
"status": "ACTIVE",
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
}
Country Details
GET /masters/countries/{country}
Success response:
{
"message": "Country found.",
"data": {
"id": "country_uuid",
"name": "India",
"iso2": "IN",
"iso3": "IND",
"phone_code": "+91",
"currency_code": "INR",
"currency_name": "Indian Rupee",
"status": "ACTIVE",
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
}
Update Country
PATCH /masters/countries/{country}
PUT /masters/countries/{country} is also accepted.
Request:
{
"name": "Bharat",
"iso2": "in",
"iso3": "ind",
"phone_code": "+91",
"currency_code": "inr",
"currency_name": "Indian Rupee",
"status": "INACTIVE"
}
Validation:
| Field | Required | Notes |
|---|---|---|
name |
Yes | Must be unique among non-deleted countries except current country. |
iso2 |
Yes | 2 letters. Must be unique among non-deleted countries except current country. Backend stores uppercase. |
iso3 |
No | 3 letters if sent. Must be unique among non-deleted countries except current country. Backend stores uppercase. |
phone_code |
No | Country calling code. |
currency_code |
No | 3 letters if sent. Backend stores uppercase. |
currency_name |
No | Currency display name. |
status |
Yes | ACTIVE or INACTIVE. |
Update is not partial. Send the full editable payload.
Success response:
{
"message": "Country updated successfully.",
"data": {
"id": "country_uuid",
"name": "Bharat",
"iso2": "IN",
"iso3": "IND",
"phone_code": "+91",
"currency_code": "INR",
"currency_name": "Indian Rupee",
"status": "INACTIVE",
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:30:00.000000Z"
}
}
Delete Country
DELETE /masters/countries/{country}
Success response:
{
"message": "Country deleted successfully."
}
Delete soft-deletes the country. A soft-deleted country can be created again with the same name/ISO values.
If the country is referenced by states, chapter rows, businesses, or other module records, delete is blocked with 422 Unprocessable Entity and a validation-style message. Remove or reassign dependent records first.
State APIs
State APIs manage records in states. Each state belongs to a country.
List States
GET /masters/states
Examples:
GET /masters/states
GET /masters/states?search=gujarat
GET /masters/states?q=gj
GET /masters/states?country_id=country_uuid
GET /masters/states?status=ACTIVE
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches state name, state code, and country name. |
q |
No | Alias for search. |
country_id |
No | Filters states by country UUID. |
status |
No | ACTIVE or INACTIVE. |
Success response:
{
"message": "States found.",
"total_states": 1,
"data": [
{
"id": "state_uuid",
"country_id": "country_uuid",
"name": "Gujarat",
"code": "GJ",
"status": "ACTIVE",
"country": {
"id": "country_uuid",
"name": "India",
"iso2": "IN"
},
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
]
}
When no states are found:
{
"message": "States not found.",
"total_states": 0,
"data": []
}
Create State
POST /masters/states
Request:
{
"country_id": "country_uuid",
"name": "Gujarat",
"code": "gj",
"status": "ACTIVE"
}
Validation:
| Field | Required | Notes |
|---|---|---|
country_id |
Yes | Existing countries.id. |
name |
Yes | Must be unique for the selected country among non-deleted states. A soft-deleted state name can be reused. |
code |
No | State code. Backend stores uppercase. |
status |
No | ACTIVE or INACTIVE. Defaults to ACTIVE. |
Success response:
{
"message": "State created successfully.",
"data": {
"id": "state_uuid",
"country_id": "country_uuid",
"name": "Gujarat",
"code": "GJ",
"status": "ACTIVE",
"country": {
"id": "country_uuid",
"name": "India",
"iso2": "IN"
},
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
}
State Details
GET /masters/states/{state}
Success response:
{
"message": "State found.",
"data": {
"id": "state_uuid",
"country_id": "country_uuid",
"name": "Gujarat",
"code": "GJ",
"status": "ACTIVE",
"country": {
"id": "country_uuid",
"name": "India",
"iso2": "IN"
},
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
}
Update State
PATCH /masters/states/{state}
PUT /masters/states/{state} is also accepted.
Request:
{
"country_id": "country_uuid",
"name": "Gujarat State",
"code": "gs",
"status": "INACTIVE"
}
Validation:
| Field | Required | Notes |
|---|---|---|
country_id |
Yes | Existing countries.id. |
name |
Yes | Must be unique for the selected country among non-deleted states except current state. |
code |
No | State code. Backend stores uppercase. |
status |
Yes | ACTIVE or INACTIVE. |
Update is not partial. Send the full editable payload.
Success response:
{
"message": "State updated successfully.",
"data": {
"id": "state_uuid",
"country_id": "country_uuid",
"name": "Gujarat State",
"code": "GS",
"status": "INACTIVE",
"country": {
"id": "country_uuid",
"name": "India",
"iso2": "IN"
},
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:30:00.000000Z"
}
}
Delete State
DELETE /masters/states/{state}
Success response:
{
"message": "State deleted successfully."
}
Delete soft-deletes the state. A soft-deleted state can be created again with the same country/name.
If the state is referenced by cities, chapter rows, businesses, or other module records, delete is blocked with 422 Unprocessable Entity and a validation-style message. Remove or reassign dependent records first.
City APIs
City APIs manage records in cities. Each city belongs to a state.
List Cities
GET /masters/cities
Examples:
GET /masters/cities
GET /masters/cities?search=ahmedabad
GET /masters/cities?q=surat
GET /masters/cities?state_id=state_uuid
GET /masters/cities?status=ACTIVE
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches city name and state name. |
q |
No | Alias for search. |
state_id |
No | Filters cities by state UUID. |
status |
No | ACTIVE or INACTIVE. |
Success response:
{
"message": "Cities found.",
"total_cities": 1,
"data": [
{
"id": "city_uuid",
"state_id": "state_uuid",
"name": "Ahmedabad",
"latitude": "23.0225000",
"longitude": "72.5714000",
"status": "ACTIVE",
"state": {
"id": "state_uuid",
"country_id": "country_uuid",
"name": "Gujarat",
"code": "GJ",
"country": {
"id": "country_uuid",
"name": "India",
"iso2": "IN"
}
},
"created_at": "2026-05-21T10:00:00.000000Z",
"updated_at": "2026-05-21T10:00:00.000000Z"
}
]
}
When no cities are found:
{
"message": "Cities not found.",
"total_cities": 0,
"data": []
}
Create City
POST /masters/cities
Request:
{
"state_id": "state_uuid",
"name": "Ahmedabad",
"latitude": 23.0225,
"longitude": 72.5714,
"status": "ACTIVE"
}
Validation:
| Field | Required | Notes |
|---|---|---|
state_id |
Yes | Existing states.id. |
name |
Yes | Must be unique for the selected state among non-deleted cities. A soft-deleted city name can be reused. |
latitude |
No | Numeric value between -90 and 90. |
longitude |
No | Numeric value between -180 and 180. |
status |
No | ACTIVE or INACTIVE. Defaults to ACTIVE. |
Success response:
{
"message": "City created successfully.",
"data": {
"id": "city_uuid",
"state_id": "state_uuid",
"name": "Ahmedabad",
"latitude": "23.0225000",
"longitude": "72.5714000",
"status": "ACTIVE",
"state": {
"id": "state_uuid",
"country_id": "country_uuid",
"name": "Gujarat",
"code": "GJ",
"country": {
"id": "country_uuid",
"name": "India",
"iso2": "IN"
}
},
"created_at": "2026-05-21T10:00:00.000000Z",
"updated_at": "2026-05-21T10:00:00.000000Z"
}
}
City Details
GET /masters/cities/{city}
Success response:
{
"message": "City found.",
"data": {
"id": "city_uuid",
"state_id": "state_uuid",
"name": "Ahmedabad",
"latitude": "23.0225000",
"longitude": "72.5714000",
"status": "ACTIVE",
"state": {
"id": "state_uuid",
"country_id": "country_uuid",
"name": "Gujarat",
"code": "GJ",
"country": {
"id": "country_uuid",
"name": "India",
"iso2": "IN"
}
},
"created_at": "2026-05-21T10:00:00.000000Z",
"updated_at": "2026-05-21T10:00:00.000000Z"
}
}
Update City
PATCH /masters/cities/{city}
PUT /masters/cities/{city} is also accepted.
Request:
{
"state_id": "state_uuid",
"name": "Amdavad",
"latitude": 23.03,
"longitude": 72.58,
"status": "INACTIVE"
}
Validation:
| Field | Required | Notes |
|---|---|---|
state_id |
Yes | Existing states.id. |
name |
Yes | Must be unique for the selected state among non-deleted cities except current city. |
latitude |
No | Numeric value between -90 and 90. |
longitude |
No | Numeric value between -180 and 180. |
status |
Yes | ACTIVE or INACTIVE. |
Update is not partial. Send the full editable payload.
Success response:
{
"message": "City updated successfully.",
"data": {
"id": "city_uuid",
"state_id": "state_uuid",
"name": "Amdavad",
"latitude": "23.0300000",
"longitude": "72.5800000",
"status": "INACTIVE",
"state": {
"id": "state_uuid",
"country_id": "country_uuid",
"name": "Gujarat",
"code": "GJ",
"country": {
"id": "country_uuid",
"name": "India",
"iso2": "IN"
}
},
"created_at": "2026-05-21T10:00:00.000000Z",
"updated_at": "2026-05-21T10:30:00.000000Z"
}
}
Delete City
DELETE /masters/cities/{city}
Success response:
{
"message": "City deleted successfully."
}
Delete soft-deletes the city. A soft-deleted city can be created again with the same state/name.
If the city is referenced by chapter rows, businesses, or other module records, delete is blocked with 422 Unprocessable Entity and a validation-style message. Remove or reassign dependent records first.
Code Of Conduct PDF APIs
These APIs manage the Code of Conduct PDF shown in the app through /api/app/masters/code-of-conduct-pdf.
Only one Code of Conduct PDF is active at a time. When a new PDF is uploaded, the previous code-of-conduct record is deleted and a new active record is created. The PDF is stored on the application server public disk, not S3.
Get Current Code Of Conduct PDF
GET /masters/code-of-conduct
Authorization: Bearer {admin_access_token}
Use this when opening the upload page so the portal can preview the previously uploaded PDF.
Success response:
{
"message": "Code of conduct PDF found.",
"data": {
"id": "uuid",
"title": "Code of Conduct",
"pdf_path": "code-of-conduct/file.pdf",
"url": null,
"pdf_url": "https://cdn.example.com/code-of-conduct/file.pdf",
"description": "Updated PDF.",
"status": "ACTIVE",
"created_at": "2026-05-22T10:00:00.000000Z",
"updated_at": "2026-05-22T10:30:00.000000Z"
}
}
When no record exists, message is Code of conduct PDF not found. and data is null.
Upload Code Of Conduct PDF
POST /masters/code-of-conduct/upload
Authorization: Bearer {admin_access_token}
Content-Type: multipart/form-data
Request fields:
| Field | Required | Notes |
|---|---|---|
pdf_file |
Yes | PDF file, max 10 MB. Alias: pdf. |
title |
No | Defaults to Code of Conduct. |
description |
No | Optional description. |
Success response:
{
"message": "Code of conduct PDF uploaded successfully.",
"data": {
"id": "uuid",
"title": "Code of Conduct",
"pdf_path": "code-of-conduct/file.pdf",
"url": null,
"pdf_url": "https://cdn.example.com/code-of-conduct/file.pdf",
"description": "Updated PDF.",
"status": "ACTIVE",
"created_at": "2026-05-22T10:00:00.000000Z",
"updated_at": "2026-05-22T10:30:00.000000Z"
}
}
Upload deletes the previous active Code of Conduct record and creates a new active record. The previous local PDF file is removed from the public disk when it exists.
Member APIs
Member APIs are module-level admin routes, not master routes.
Create Member With Business
POST /api/admin/members
Authorization: Bearer {admin_access_token}
Content-Type: multipart/form-data
This request creates the linked user, member, business, default chapter assignment, and approval record in one flow.
Members created by the admin portal are approved immediately through the same approval service used by POST /api/admin/members/{member}/approve. The flow generates the temporary password, sets requires_password_change = true, stores transaction proof, and sends the approval email in production.
After the approved member is assigned to the default chapter, the backend creates a NEW_MEMBER_JOINED notification for the other approved members and admins of that chapter. The newly created member and the creator/action taker are excluded.
Request fields:
| Field | Required | Notes |
|---|---|---|
name |
Yes | Member name. |
email |
Yes | Unique member email. |
phone_number |
Yes | Unique member phone number. |
date_of_birth |
No | Valid date. |
whatsapp |
No | Member WhatsApp number. |
profile_photo |
No | JPG/JPEG/PNG/WEBP, max 2 MB. |
chapter_id |
Yes | Active chapter UUID used as the member's default chapter. |
date_of_joining |
No | Default chapter joining date. Defaults to the current server date. |
transaction_id |
Yes | Payment or approval transaction reference. |
transaction_image |
Yes | JPG/JPEG/PNG/WEBP transaction proof, max 10 MB. Alias: image. |
business[business_category_id] |
Yes | Business category UUID. |
business[business_sub_category_id] |
Yes | Business sub-category UUID. |
business[country_id] |
Yes | Country UUID. |
business[state_id] |
Yes | State UUID. |
business[city_id] |
Yes | City UUID. |
business[name] |
Yes | Business name. |
business[gst_number] |
No | GST number. |
business[mobile_number] |
Yes | Business mobile number. |
business[whatsapp_number] |
Yes | Business WhatsApp number. |
business[email] |
Yes | Business email. |
business[website] |
No | Valid URL. |
business[description] |
Yes | Business description. |
business[full_address] |
Yes | Full business address. |
business[business_logo] |
No | JPG/JPEG/PNG/WEBP, max 2 MB. |
business[business_images][] |
No | Maximum 6 images, max 10 MB each. |
Success response:
{
"message": "Member created successfully.",
"data": {
"id": "member_uuid",
"member_number": "BCI000001",
"name": "Amit Shah",
"email": "amit@example.com",
"phone_number": "9876543210",
"date_of_birth": "1990-12-04",
"whatsapp": "9876543210",
"profile_image": null,
"profile_image_url": null,
"approval_status": "APPROVED",
"approved_at": "2026-06-09T10:00:00.000000Z",
"requires_password_change": true,
"business": {
"id": "business_uuid",
"member_id": "member_uuid",
"name": "Amit Electronics",
"business_logo": null,
"business_logo_url": null,
"images": []
},
"member_chapters": [
{
"member_chapter_id": "member_chapter_uuid",
"chapter_id": "chapter_uuid",
"date_of_joining": "2026-06-09",
"chapter": {
"id": "chapter_uuid",
"name": "Ahmedabad Chapter"
},
"member_type": {
"id": "member_type_uuid",
"code": "MEMBER",
"name": "Member"
}
}
]
}
}
Update Member With Business
PUT /api/admin/members/{member}
PATCH /api/admin/members/{member}
Authorization: Bearer {admin_access_token}
Content-Type: multipart/form-data
The personal and business fields are the same as create. The current API expects the complete member and business form for both PUT and PATCH.
Additional update field:
| Field | Required | Notes |
|---|---|---|
business[delete_business_image_ids][] |
No | Existing business image UUIDs to remove. |
Behavior:
- updates the linked
users,members, andmember_businessesrecords in one request - keeps the existing profile photo and business logo when replacement files are not sent
- appends new business images while enforcing a maximum of 6 remaining images
- removes only image IDs belonging to the selected member's business
- validates that the selected sub-category belongs to the category, state belongs to the country, and city belongs to the state
- does not change password, approval status, or chapter assignments
Delete Member
DELETE /api/admin/members/{member}
Authorization: Bearer {admin_access_token}
This soft-deletes the linked user, member, business, business images, and member chapter assignments. Uploaded files remain stored for audit/recovery.
Success response:
{
"message": "Member deleted successfully."
}
List Members
GET /api/admin/members
Authorization: Bearer {admin_access_token}
Examples:
GET /api/admin/members
GET /api/admin/members?limit=10
GET /api/admin/members?search=amit
GET /api/admin/members?chapter_id=chapter_uuid
GET /api/admin/members?member_type_id=member_type_uuid
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches member number, member name/email/mobile, and business name/email/mobile. |
q |
No | Alias for search. |
limit |
No | Pagination size, 1 to 100. Defaults to 10. |
per_page |
No | Backward-compatible alias for limit. |
page |
No | Page number. |
chapter_id |
No | Filter by assigned chapter. |
member_type_id |
No | Filter by assigned member type. |
approval_status |
No | Filter by approval status. Use PENDING or APPROVED. |
Success response:
{
"message": "Members found.",
"meta": {
"total_records": 2,
"page": 1,
"limit": 10,
"total_pages": 1,
"record_start": 1,
"record_end": 2
},
"data": [
{
"id": "member_uuid",
"member_number": "BCI000001",
"name": "Amit Shah",
"business_name": "Amit Electronics",
"mobile_number": "9876543210",
"email": "amit@example.com",
"profile_image": "member-profiles/photo.jpg",
"profile_image_url": "https://cdn.example.com/member-profiles/photo.jpg",
"approval_status": "PENDING",
"approved_at": null,
"requires_password_change": false,
"is_chapter_assigned": false
}
]
}
View Member
GET /api/admin/members/{member}
Authorization: Bearer {admin_access_token}
The data object uses the exact same payload structure as the create and update member responses. This keeps the view and edit forms on one frontend model.
{
"message": "Member found.",
"data": {
"id": "member_uuid",
"member_number": "BCI000001",
"name": "Amit Shah",
"email": "amit@example.com",
"phone_number": "9876543210",
"date_of_birth": "1990-12-04",
"whatsapp": "9876543210",
"profile_image": "member-profiles/photo.jpg",
"profile_image_url": "https://cdn.example.com/member-profiles/photo.jpg",
"approval_status": "APPROVED",
"approved_at": "2026-05-23T10:00:00.000000Z",
"requires_password_change": true,
"business": {
"id": "business_uuid",
"member_id": "member_uuid",
"name": "Amit Electronics",
"business_logo": "business-logos/business_uuid/logo.jpg",
"business_logo_url": "https://cdn.example.com/business-logos/business_uuid/logo.jpg",
"business_category": {
"id": "category_uuid",
"name": "Electronics"
},
"images": []
},
"member_chapters": [
{
"member_chapter_id": "member_chapter_uuid",
"chapter_id": "chapter_uuid",
"date_of_joining": "2026-05-27",
"chapter": {
"id": "chapter_uuid",
"name": "Ahmedabad Chapter"
},
"member_type": {
"id": "member_type_uuid",
"code": "MEMBER",
"name": "Member"
}
}
],
"created_at": "2026-05-23T10:00:00.000000Z",
"updated_at": "2026-06-18T10:00:00.000000Z"
}
}
Approve Member
POST /api/admin/members/{member}/approve
Authorization: Bearer {admin_access_token}
Content-Type: multipart/form-data
Validation:
| Field | Required | Notes |
|---|---|---|
transaction_id |
Yes | Payment or transaction reference entered by admin. |
transaction_image |
Yes | JPG/JPEG/PNG/WEBP image, max 10 MB. Alias: image. |
Behavior:
- Updates the member
approval_statustoAPPROVED. - Stores a
member_approval_recordsentry with transaction id, proof image path, approving admin, and approval time. - Generates a temporary password, updates the member user password, and emails it to the member.
- Sets
requires_password_change: true; the app should redirect the member to change password after login. - At least one chapter must already be assigned to the member before approval.
- Creates a
NEW_MEMBER_JOINEDnotification for the other approved members and admins of every assigned chapter. The approved member and the approving admin are excluded. - If the member is already approved, the API returns
422.
Validation error example when no chapter is assigned:
{
"message": "The given data was invalid.",
"errors": {
"member_id": [
"At least one chapter must be assigned before approval."
]
}
}
Success response:
{
"message": "Member approved successfully.",
"data": {
"member": {
"id": "member_uuid",
"member_number": "BCI000001",
"name": "Amit Shah",
"email": "amit@example.com",
"approval_status": "APPROVED",
"approved_at": "2026-05-23T10:00:00.000000Z",
"requires_password_change": true
},
"approval_record": {
"id": "approval_record_uuid",
"member_id": "member_uuid",
"approved_by_user_id": "admin_user_uuid",
"transaction_id": "TXN-10001",
"transaction_image_path": "member-approvals/member_uuid/proof.jpg",
"transaction_image_url": "https://cdn.example.com/member-approvals/member_uuid/proof.jpg",
"approved_at": "2026-05-23T10:00:00.000000Z"
}
}
}
Bulk Approve Members
POST /api/admin/members/bulk-approve
Authorization: Bearer {admin_access_token}
Content-Type: application/json
Permission:
MEMBER_APPROVE
No request body is required. The frontend only calls the endpoint when the bulk-approve button is clicked.
Backend selection:
- Selects every member whose approval status is
PENDINGorNULL. - Approves only members who have at least one assigned chapter.
- Already-approved members and members without a chapter are ignored.
The API uses these fixed approval values for every selected member:
{
"transaction_id": "admin_approve12345",
"transaction_image_path": "public/theme/images/dummy_image.jpg"
}
Before returning the response, the endpoint synchronously:
- Applies the same approval-state updates as the single-member approval API.
- Creates the
member_approval_recordsentry. - Uses chunked bulk database updates/inserts instead of approving members one by one, so the request can handle larger pending-member batches more efficiently.
After the approval database writes, the endpoint queues background communication jobs to:
- Sending the approval email.
- Sending the new-member notification.
- Handling email and notification failures independently, without rolling back the saved approval or stopping communication jobs for other members.
The communication job is dispatched once per approved chunk and receives that chunk's member_ids. It is not dispatched once per member. Use a real async queue connection such as database or redis; if QUEUE_CONNECTION=sync, email/notification work still runs inside the API request.
The API response intentionally does not return all approved member_ids; returning thousands of UUIDs makes the bulk request slower and is not needed by the portal list screen.
A queue worker must be running:
php artisan queue:work
Success response:
{
"message": "Members approved successfully. Email and notifications have been queued.",
"data": {
"approved_count": 2,
"queued_job_count": 1,
"transaction_id": "admin_approve12345",
"transaction_image_path": "public/theme/images/dummy_image.jpg"
}
}
Member Chapter Assignment
Use these APIs for the Chapter Assign page. The portal first selects a member, loads existing assigned chapters, allows the admin to delete rows or add new rows locally, then submits the final rows.
Member Type Dropdown
Use this API to fill the member type dropdown for each chapter assignment row.
GET /api/admin/masters/member-types?status=ACTIVE
Authorization: Bearer {admin_access_token}
Permission:
MEMBER_TYPE
Query parameters:
| Field | Required | Notes |
|---|---|---|
search |
No | Search by member type name, code, or description. |
q |
No | Alias for search. |
status |
No | ACTIVE or INACTIVE. For assignment dropdown use ACTIVE. |
limit |
No | Pagination size, defaults to global admin pagination. |
per_page |
No | Backward-compatible alias for limit. |
page |
No | Page number. |
Success response:
{
"message": "Member types found.",
"total_member_types": 2,
"meta": {
"total_records": 2,
"page": 1,
"limit": 10,
"total_pages": 1,
"record_start": 1,
"record_end": 2
},
"data": [
{
"id": "member_type_uuid",
"code": "CHAPTER_ADMIN",
"name": "Chapter Admin",
"description": null,
"status": "ACTIVE"
},
{
"id": "member_type_uuid",
"code": "MEMBER",
"name": "Member",
"description": null,
"status": "ACTIVE"
}
]
}
Event Type Dropdown
Use this API to fill the event type dropdown in event create and edit forms.
GET /api/admin/masters/event-types?status=ACTIVE
Authorization: Bearer {admin_access_token}
Permission:
EVENT_LISTorEVENT_ADDorEVENT_EDITorEVENT_VIEW
Query parameters:
| Field | Required | Notes |
|---|---|---|
search |
No | Search by event type name or code. |
q |
No | Alias for search. |
status |
No | ACTIVE or INACTIVE. For event forms use ACTIVE. |
limit |
No | Pagination size, defaults to global admin pagination. |
page |
No | Page number. |
Success response:
{
"message": "Event types found.",
"total_event_types": 3,
"meta": {
"total_records": 3,
"page": 1,
"limit": 10,
"total_pages": 1,
"record_start": 1,
"record_end": 3
},
"data": [
{
"id": "event_type_uuid",
"code": "WORKSHOP_VISIT",
"name": "Workshop",
"status": "ACTIVE"
}
]
}
View Assigned Chapters
GET /api/admin/members/{member}/chapters
Authorization: Bearer {admin_access_token}
Success response:
{
"message": "Member chapters found.",
"data": {
"member": {
"id": "member_uuid",
"member_number": "BCI000001",
"name": "Amit Shah",
"email": "amit@example.com",
"phone_number": "9876543210"
},
"member_chapters": [
{
"member_chapter_id": "member_chapter_uuid",
"chapter_id": "chapter_uuid",
"date_of_joining": "2026-05-27",
"chapter": {
"id": "chapter_uuid",
"name": "Ahmedabad Chapter",
"description": "Description"
},
"member_type_id": "member_type_uuid",
"member_type": {
"id": "member_type_uuid",
"code": "MEMBER",
"name": "Member"
}
}
]
}
}
Submit Assigned Chapters
POST /api/admin/members/{member}/chapters
Authorization: Bearer {admin_access_token}
Request:
{
"chapters": [
{
"chapter_id": "chapter_uuid",
"member_type_id": "member_type_uuid",
"date_of_joining": "2026-05-27"
}
]
}
Validation:
| Field | Required | Notes |
|---|---|---|
chapters |
Yes | Array, minimum 1 row. Alias: member_chapters or rows. |
chapters.*.chapter_id |
Yes | Active chapter id. Must be distinct. |
chapters.*.member_type_id |
Yes | Active member type id. |
chapters.*.date_of_joining |
No | Joining date for that member chapter row. Format: YYYY-MM-DD. |
Behavior:
- The submitted rows become the final chapter assignments for the member.
- Rows not submitted are removed.
- Deleted rows can be restored if submitted again.
- At least one chapter is required, so the portal should not allow final submit with zero rows.
Success response:
{
"message": "Member chapters updated successfully.",
"data": {
"member": {
"id": "member_uuid",
"member_number": "BCI000001",
"name": "Amit Shah",
"email": "amit@example.com",
"phone_number": "9876543210"
},
"member_chapters": [
{
"member_chapter_id": "member_chapter_uuid",
"chapter_id": "chapter_uuid",
"date_of_joining": "2026-05-27",
"chapter": {
"id": "chapter_uuid",
"name": "Ahmedabad Chapter",
"description": "Description"
},
"member_type_id": "member_type_uuid",
"member_type": {
"id": "member_type_uuid",
"code": "MEMBER",
"name": "Member"
}
}
]
}
}
One-to-One Session APIs
One-to-one session APIs are module-level admin routes for 1:1 member sessions.
List One-to-One Sessions
GET /api/admin/one-to-one-sessions
Authorization: Bearer {admin_access_token}
Examples:
GET /api/admin/one-to-one-sessions
GET /api/admin/one-to-one-sessions?limit=10
GET /api/admin/one-to-one-sessions?search=amit
GET /api/admin/one-to-one-sessions?chapter_id=chapter_uuid
GET /api/admin/one-to-one-sessions?member_id=member_uuid
GET /api/admin/one-to-one-sessions?session_with_member_id=member_uuid
GET /api/admin/one-to-one-sessions?from_date=2026-05-01&to_date=2026-05-31
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches both member names/emails/mobile numbers, business names, and remarks. |
q |
No | Alias for search. |
limit |
No | Pagination size, 1 to 100. Defaults to 10. |
per_page |
No | Backward-compatible alias for limit. |
page |
No | Page number. |
chapter_id |
No | Filters sessions by chapter. |
member_id |
No | Filter by the reporting/creator member. |
session_with_member_id |
No | Filter by the member met in the session. |
from_date |
No | Minimum visit date. Alias: date_from. |
to_date |
No | Maximum visit date. Alias: date_to. |
Success response:
{
"message": "One-to-one sessions found.",
"meta": {
"total_records": 1,
"page": 1,
"limit": 10,
"total_pages": 1,
"record_start": 1,
"record_end": 1
},
"data": [
{
"id": "session_uuid",
"chapter_id": "chapter_uuid",
"chapter_name": "Ahmedabad Chapter",
"member_id": "member_uuid",
"member_name": "Amit Shah",
"member_business_name": "Amit Electronics",
"session_with_member_id": "member_uuid",
"session_with_member_name": "Neha Patel",
"session_with_member_business_name": "Neha Fashion House",
"visit_date": "2026-05-20",
"remarks": "Discussed business referrals."
}
]
}
Chapter Member Options
Returns only members assigned to the selected chapter, sorted by member name.
GET /api/admin/one-to-one-sessions/member-options?chapter_id={chapter_uuid}
Authorization: Bearer {admin_access_token}
Optional query param: search.
Create One-to-One Session
POST /api/admin/one-to-one-sessions
Authorization: Bearer {admin_access_token}
Content-Type: application/json
{
"chapter_id": "chapter_uuid",
"member_id": "member_uuid",
"session_with_member_id": "member_uuid",
"visit_date": "2026-06-10",
"remarks": "Discussed business referrals."
}
Both members must be assigned to chapter_id, and the two member IDs must be different. Creation also records the chapter activity and creates the session notification.
View One-to-One Session
GET /api/admin/one-to-one-sessions/{one_to_one_session}
Authorization: Bearer {admin_access_token}
Success response:
{
"message": "One-to-one session found.",
"data": {
"id": "session_uuid",
"chapter_id": "chapter_uuid",
"member_id": "member_uuid",
"session_with_member_id": "member_uuid",
"visit_date": "2026-05-20",
"remarks": "Discussed business referrals.",
"chapter": {
"id": "chapter_uuid",
"name": "Ahmedabad Chapter",
"description": null
},
"member": {
"id": "member_uuid",
"member_number": "BCI000001",
"name": "Amit Shah",
"email": "amit@example.com",
"phone_number": "9876543210",
"business": {
"id": "business_uuid",
"name": "Amit Electronics"
}
},
"session_with_member": {
"id": "member_uuid",
"member_number": "BCI000002",
"name": "Neha Patel",
"email": "neha@example.com",
"phone_number": "9876543211",
"business": {
"id": "business_uuid",
"name": "Neha Fashion House"
}
},
"created_by": {
"id": "admin_user_uuid",
"name": "Portal Admin",
"email": "portal-admin@example.com",
"phone_number": "9000000001",
"type": "ADMIN"
},
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
}
Update One-to-One Session
PUT /api/admin/one-to-one-sessions/{one_to_one_session}
PATCH /api/admin/one-to-one-sessions/{one_to_one_session}
Authorization: Bearer {admin_access_token}
PUT accepts the complete create payload. PATCH accepts only changed fields; omitted fields retain their existing values. Send "remarks": null to clear remarks. Chapter/member validation remains the same as the create API. The existing chapter activity is synchronized; a duplicate push notification is not created.
Delete One-to-One Session
DELETE /api/admin/one-to-one-sessions/{one_to_one_session}
Authorization: Bearer {admin_access_token}
The session and its chapter activity are soft deleted.
Referral APIs
Referral APIs are module-level admin routes for member referrals.
List Referrals
GET /api/admin/referrals
Authorization: Bearer {admin_access_token}
Examples:
GET /api/admin/referrals
GET /api/admin/referrals?limit=10
GET /api/admin/referrals?search=rahul
GET /api/admin/referrals?chapter_id=chapter_uuid
GET /api/admin/referrals?member_id=member_uuid
GET /api/admin/referrals?referring_by_member_id=member_uuid
GET /api/admin/referrals?referring_to_member_id=member_uuid
GET /api/admin/referrals?from_date=2026-05-01&to_date=2026-05-31
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches referring from/to member names, emails, mobile numbers, referred person, referred person email/mobile, and referral details. |
q |
No | Alias for search. |
limit |
No | Pagination size, 1 to 100. Defaults to 10. |
per_page |
No | Backward-compatible alias for limit. |
page |
No | Page number. |
chapter_id |
No | Filter referrals by chapter. |
member_id |
No | Filter referrals where this member is either referring from or referring to. |
referring_by_member_id |
No | Filter by referring from member. |
referring_to_member_id |
No | Filter by referring to member. |
from_date |
No | Minimum created date. Alias: date_from. |
to_date |
No | Maximum created date. Alias: date_to. |
Success response:
{
"message": "Referrals found.",
"meta": {
"total_records": 1,
"page": 1,
"limit": 10,
"total_pages": 1,
"record_start": 1,
"record_end": 1
},
"data": [
{
"id": "referral_uuid",
"chapter_id": "chapter_uuid",
"chapter_name": "Ahmedabad Chapter",
"referring_by_member_id": "member_uuid",
"referring_from_name": "Amit Shah",
"referring_to_member_id": "member_uuid",
"referring_to_name": "Neha Patel",
"referring_person": "Rahul Mehta",
"email": "rahul@example.com",
"contact_number": "9876543210",
"referral_details": "Looking for a reliable supplier for mobile accessories.",
"date": "2026-05-12"
}
]
}
Chapter Member Options
Returns only members assigned to the selected chapter, sorted by member name.
GET /api/admin/referrals/member-options?chapter_id={chapter_uuid}
Authorization: Bearer {admin_access_token}
Optional query param: search.
Create Referral
POST /api/admin/referrals
Authorization: Bearer {admin_access_token}
Content-Type: application/json
{
"chapter_id": "chapter_uuid",
"referring_by_member_id": "member_uuid",
"referring_to_member_id": "member_uuid",
"referring_person": "Rahul Mehta",
"referral_details": "Looking for a reliable supplier.",
"contact_number": "9876543210",
"email": "rahul@example.com"
}
Both members must be assigned to chapter_id, and they must be different members. member_id is accepted as an alias for referring_by_member_id, and referring_to is accepted for referring_to_member_id. Creation records the chapter activity and creates the referral notification.
View Referral
GET /api/admin/referrals/{referral}
Authorization: Bearer {admin_access_token}
Success response:
{
"message": "Referral found.",
"data": {
"id": "referral_uuid",
"chapter_id": "chapter_uuid",
"referring_by_member_id": "member_uuid",
"referring_to_member_id": "member_uuid",
"referring_person": "Rahul Mehta",
"referral_details": "Looking for a reliable supplier for mobile accessories.",
"contact_number": "9876543210",
"email": "rahul@example.com",
"chapter": {
"id": "chapter_uuid",
"name": "Ahmedabad Chapter",
"description": null
},
"referring_by_member": {
"id": "member_uuid",
"member_number": "BCI000001",
"name": "Amit Shah",
"email": "amit@example.com",
"phone_number": "9876543210",
"business": {
"id": "business_uuid",
"name": "Amit Electronics"
}
},
"referring_to_member": {
"id": "member_uuid",
"member_number": "BCI000002",
"name": "Neha Patel",
"email": "neha@example.com",
"phone_number": "9876543211",
"business": {
"id": "business_uuid",
"name": "Neha Fashion House"
}
},
"created_by": {
"id": "admin_user_uuid",
"name": "Portal Admin",
"email": "portal-admin@example.com",
"phone_number": "9000000001",
"type": "ADMIN"
},
"created_at": "2026-05-12T10:00:00.000000Z",
"updated_at": "2026-05-12T10:00:00.000000Z"
}
}
Update Referral
PUT /api/admin/referrals/{referral}
PATCH /api/admin/referrals/{referral}
Authorization: Bearer {admin_access_token}
PUT accepts the complete create payload. PATCH accepts changed fields only; omitted fields retain their existing values. The chapter activity is synchronized without creating a duplicate notification.
Delete Referral
DELETE /api/admin/referrals/{referral}
Authorization: Bearer {admin_access_token}
The referral and its chapter activity are soft deleted.
Business Exchange APIs
Business exchange APIs are module-level admin routes for completed member business exchanges.
List Business Exchanges
GET /api/admin/business-exchanges
Authorization: Bearer {admin_access_token}
Examples:
GET /api/admin/business-exchanges
GET /api/admin/business-exchanges?limit=10
GET /api/admin/business-exchanges?search=mobile
GET /api/admin/business-exchanges?chapter_id=chapter_uuid
GET /api/admin/business-exchanges?member_id=member_uuid
GET /api/admin/business-exchanges?referred_by_member_id=member_uuid
GET /api/admin/business-exchanges?referred_to_member_id=member_uuid
GET /api/admin/business-exchanges?from_date=2026-05-01&to_date=2026-05-31
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches exchange from/to member names, emails, mobile numbers, and exchange details. |
q |
No | Alias for search. |
limit |
No | Pagination size, 1 to 100. Defaults to 10. |
per_page |
No | Backward-compatible alias for limit. |
page |
No | Page number. |
chapter_id |
No | Filter exchanges by chapter. |
member_id |
No | Filter exchanges where this member is either exchange from or exchange to. |
referred_by_member_id |
No | Filter by exchange from member. |
referred_to_member_id |
No | Filter by exchange to member. |
from_date |
No | Minimum created date. Alias: date_from. |
to_date |
No | Maximum created date. Alias: date_to. |
Success response:
{
"message": "Business exchanges found.",
"meta": {
"total_records": 1,
"page": 1,
"limit": 10,
"total_pages": 1,
"record_start": 1,
"record_end": 1
},
"data": [
{
"id": "business_exchange_uuid",
"chapter_id": "chapter_uuid",
"chapter_name": "Ahmedabad Chapter",
"exchange_from_member_id": "member_uuid",
"exchange_from_name": "Amit Shah",
"exchange_to_member_id": "member_uuid",
"exchange_to_name": "Neha Patel",
"amount_earned": "25000.00",
"exchange_details": "Bulk order for mobile accessories completed successfully.",
"date": "2026-05-12"
}
]
}
Chapter Member Options
Returns only members assigned to the selected chapter, sorted by member name.
GET /api/admin/business-exchanges/member-options?chapter_id={chapter_uuid}
Authorization: Bearer {admin_access_token}
Optional query param: search.
Create Business Exchange
POST /api/admin/business-exchanges
Authorization: Bearer {admin_access_token}
Content-Type: application/json
{
"chapter_id": "chapter_uuid",
"referred_by_member_id": "member_uuid",
"referred_to_member_id": "member_uuid",
"business_amount_earned": 25000,
"referral_details": "Bulk order completed."
}
Both members must be assigned to chapter_id, and they must be different members. Accepted aliases are exchange_from_member_id, exchange_to_member_id, amount_earned, and exchange_details. Creation records the chapter activity and creates the business exchange notification.
View Business Exchange
GET /api/admin/business-exchanges/{business_exchange}
Authorization: Bearer {admin_access_token}
Success response:
{
"message": "Business exchange found.",
"data": {
"id": "business_exchange_uuid",
"chapter_id": "chapter_uuid",
"referred_to_member_id": "member_uuid",
"referred_by_member_id": "member_uuid",
"business_amount_earned": "25000.00",
"referral_details": "Bulk order for mobile accessories completed successfully.",
"chapter": {
"id": "chapter_uuid",
"name": "Ahmedabad Chapter",
"description": null
},
"exchange_from_member": {
"id": "member_uuid",
"member_number": "BCI000001",
"name": "Amit Shah",
"email": "amit@example.com",
"phone_number": "9876543210",
"business": {
"id": "business_uuid",
"name": "Amit Electronics"
}
},
"exchange_to_member": {
"id": "member_uuid",
"member_number": "BCI000002",
"name": "Neha Patel",
"email": "neha@example.com",
"phone_number": "9876543211",
"business": {
"id": "business_uuid",
"name": "Neha Fashion House"
}
},
"created_by": {
"id": "admin_user_uuid",
"name": "Portal Admin",
"email": "portal-admin@example.com",
"phone_number": "9000000001",
"type": "ADMIN"
},
"created_at": "2026-05-12T10:00:00.000000Z",
"updated_at": "2026-05-12T10:00:00.000000Z"
}
}
Update Business Exchange
PUT /api/admin/business-exchanges/{business_exchange}
PATCH /api/admin/business-exchanges/{business_exchange}
Authorization: Bearer {admin_access_token}
PUT accepts the complete create payload. PATCH accepts changed fields only; omitted fields retain their existing values. The chapter activity is synchronized without creating a duplicate notification.
Delete Business Exchange
DELETE /api/admin/business-exchanges/{business_exchange}
Authorization: Bearer {admin_access_token}
The business exchange and its chapter activity are soft deleted.
Event APIs
Event APIs are module-level admin routes for event create, list, detail, update, and delete screens.
Admin event detail uses the shared app EventService payload, with admin route authorization applied at the controller layer.
Create Event
POST /api/admin/events
Authorization: Bearer {admin_access_token}
Content-Type: multipart/form-data
Request fields:
| Field | Required | Notes |
|---|---|---|
event_type_id |
Yes if event_type_code is not sent |
Existing event type UUID. |
event_type_code |
Yes if event_type_id is not sent |
Existing event type code. |
title |
Yes | Event title. Alias: event_name. |
description |
No | Event description. |
event_date |
Yes | Date in Y-m-d format. |
start_time |
Yes | Time in H:i format. |
end_time |
Yes | Time in H:i format and must be after start time. |
location_name |
No | Alias: location. |
location_address |
No | Alias: address. |
latitude |
No | Numeric latitude. |
longitude |
No | Numeric longitude. |
map_url |
No | String map URL. |
visibility_scope |
Yes | ALL or CHAPTER. |
chapter_id |
Yes when visibility_scope = CHAPTER |
Chapter UUID for chapter-visible events. |
banner_image |
No | JPG/JPEG/PNG/WEBP image, max 10 MB. |
photos[] |
No | Event gallery images, JPG/JPEG/PNG/WEBP, max 10 MB each. Alias array: images[]. |
status |
No | ACTIVE, CANCELLED, POSTPONED, COMPLETED. Defaults to ACTIVE. |
guests |
Yes | Array with at least one guest row. |
guests.*.name |
Yes | Guest name. |
guests.*.company_name |
Yes | Guest company. |
guests.*.designation |
Yes | Guest designation. |
Request example:
{
"event_type_code": "WORKSHOP_VISIT",
"title": "Business Growth Workshop",
"description": "A focused workshop on practical sales systems.",
"event_date": "2026-06-05",
"start_time": "10:00",
"end_time": "13:00",
"location_name": "Ahmedabad Convention Center",
"location_address": "Ahmedabad, Gujarat",
"visibility_scope": "CHAPTER",
"chapter_id": "chapter_uuid",
"guests": [
{
"name": "Dr. Mehul Shah",
"company_name": "Growth Labs",
"designation": "Business Coach"
}
]
}
Success response:
{
"message": "Event created successfully.",
"data": {
"id": "event_uuid",
"event_type_id": "event_type_uuid",
"event_type": {
"id": "event_type_uuid",
"code": "WORKSHOP_VISIT",
"name": "Workshop"
},
"created_by_chapter_id": "chapter_uuid",
"created_by_chapter": {
"id": "chapter_uuid",
"name": "Ahmedabad Chapter"
},
"title": "Business Growth Workshop",
"description": "A focused workshop on practical sales systems.",
"event_date": "2026-06-05",
"start_time": "10:00",
"end_time": "13:00",
"location_name": "Ahmedabad Convention Center",
"location_address": "Ahmedabad, Gujarat",
"visibility_scope": "CHAPTER",
"visible_to": "Chapter Members",
"banner_image": null,
"banner_image_url": null,
"status": "ACTIVE",
"guests": [],
"photos": []
}
}
List Events
GET /api/admin/events
Authorization: Bearer {admin_access_token}
Examples:
GET /api/admin/events
GET /api/admin/events?limit=10
GET /api/admin/events?search=growth
GET /api/admin/events?event_type_id=event_type_uuid
GET /api/admin/events?event_type_code=WORKSHOP_VISIT
GET /api/admin/events?visibility_scope=ALL
GET /api/admin/events?status=ACTIVE
GET /api/admin/events?from_date=2026-06-01&to_date=2026-06-30
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches event name, description, location, and event type. |
q |
No | Alias for search. |
limit |
No | Pagination size, 1 to 100. Defaults to 10. |
per_page |
No | Backward-compatible alias for limit. |
page |
No | Page number. |
event_type_id |
No | Filter by event type UUID. |
event_type_code |
No | Filter by event type code. |
visibility_scope |
No | ALL or CHAPTER. |
status |
No | ACTIVE, CANCELLED, POSTPONED, or COMPLETED. |
from_date |
No | Minimum event date. Alias: date_from. |
to_date |
No | Maximum event date. Alias: date_to. |
Success response:
{
"message": "Events found.",
"meta": {
"total_records": 1,
"page": 1,
"limit": 10,
"total_pages": 1,
"record_start": 1,
"record_end": 1
},
"data": [
{
"id": "event_uuid",
"name": "Business Growth Workshop",
"event_type_id": "event_type_uuid",
"event_type_name": "Workshop",
"event_type_code": "WORKSHOP_VISIT",
"event_date": "2026-06-05",
"start_time": "10:00",
"end_time": "13:00",
"date_time": "2026-06-05 10:00 - 13:00",
"location": "Ahmedabad Convention Center",
"visibility_scope": "ALL",
"visible_to": "All Members",
"status": "ACTIVE"
}
]
}
View Event
GET /api/admin/events/{event}
Authorization: Bearer {admin_access_token}
Success response:
{
"message": "Event found.",
"data": {
"id": "event_uuid",
"event_type_id": "event_type_uuid",
"event_type": {
"id": "event_type_uuid",
"code": "WORKSHOP_VISIT",
"name": "Workshop"
},
"created_by_chapter_id": "chapter_uuid",
"created_by_chapter": {
"id": "chapter_uuid",
"name": "Ahmedabad Chapter"
},
"title": "Business Growth Workshop",
"description": "A focused workshop on practical sales systems.",
"event_date": "2026-06-05",
"start_time": "10:00",
"end_time": "13:00",
"location_name": "Ahmedabad Convention Center",
"location_address": "Ahmedabad, Gujarat",
"latitude": null,
"longitude": null,
"map_url": null,
"visibility_scope": "ALL",
"visible_to": "All Members",
"banner_image": "event-banners/workshop.jpg",
"banner_image_url": "http://localhost/storage/event-banners/workshop.jpg",
"status": "ACTIVE",
"is_attended": false,
"attendance_id": null,
"attendance_status": null,
"total_attendees": 0,
"total_photos": 0,
"is_past": false,
"guests": [
{
"id": "guest_uuid",
"name": "Dr. Mehul Shah",
"company_name": "Growth Labs",
"designation": "Business Coach",
"sort_order": 1
}
],
"photos": [
{
"id": "photo_uuid",
"event_id": "event_uuid",
"image_path": "event-photos/workshop-1.jpg",
"image_url": "http://localhost/storage/event-photos/workshop-1.jpg",
"caption": "Workshop session",
"sort_order": 1,
"uploaded_by_member": {
"id": "member_uuid",
"member_number": "BCI000001",
"name": "Amit Shah",
"email": "amit@example.com",
"phone_number": "9876543210",
"business": {
"id": "business_uuid",
"name": "Amit Electronics"
}
},
"created_at": "2026-05-23T10:00:00.000000Z"
}
],
"created_at": "2026-05-23T10:00:00.000000Z",
"updated_at": "2026-05-23T10:00:00.000000Z"
}
}
Update Event
PUT /api/admin/events/{event}
PATCH /api/admin/events/{event}
Authorization: Bearer {admin_access_token}
Content-Type: multipart/form-data
Request fields are the same as create.
Behavior:
- updates event core fields
- replaces guest rows with the submitted guest list
- keeps the existing banner image if no new
banner_imageis uploaded - appends uploaded event gallery images into
event_photoswhenphotos[]orimages[]is sent - existing event gallery images are not deleted by this API
Success response:
{
"message": "Event updated successfully.",
"data": {
"id": "event_uuid",
"event_type_id": "event_type_uuid",
"event_type": {
"id": "event_type_uuid",
"code": "WORKSHOP_VISIT",
"name": "Workshop"
},
"created_by_chapter_id": "chapter_uuid",
"created_by_chapter": {
"id": "chapter_uuid",
"name": "Ahmedabad Chapter"
},
"title": "Business Growth Workshop",
"description": "A focused workshop on practical sales systems.",
"event_date": "2026-06-05",
"start_time": "10:00",
"end_time": "13:00",
"location_name": "Ahmedabad Convention Center",
"location_address": "Ahmedabad, Gujarat",
"visibility_scope": "CHAPTER",
"visible_to": "Chapter Members",
"banner_image": "event-banners/workshop.jpg",
"banner_image_url": "http://localhost/storage/event-banners/workshop.jpg",
"status": "ACTIVE",
"guests": [
{
"id": "guest_uuid",
"name": "Dr. Mehul Shah",
"company_name": "Growth Labs",
"designation": "Business Coach",
"sort_order": 1
}
],
"photos": [
{
"id": "photo_uuid",
"event_id": "event_uuid",
"image_path": "event-photos/event_uuid/workshop-1.jpg",
"image_url": "http://localhost/storage/event-photos/event_uuid/workshop-1.jpg",
"caption": null,
"sort_order": 1,
"uploaded_by_member": null,
"created_at": "2026-06-09T10:00:00.000000Z"
}
]
}
}
Delete Event
DELETE /api/admin/events/{event}
Authorization: Bearer {admin_access_token}
This performs a soft delete through the Event model.
Success response:
{
"message": "Event deleted successfully."
}
Admin User APIs
Admin user APIs manage users.type = ADMIN portal users and assign one Spatie role to each user.
These APIs do not create or update SUPER_ADMIN users. They also do not expose MEMBER users.
List Admin Users
GET /masters/users
Examples:
GET /masters/users
GET /masters/users?search=kailash
GET /masters/users?q=admin@example.com
GET /masters/users?role_id=role_uuid
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches name, email, and phone number. |
q |
No | Alias for search. |
role_id |
No | Filters users by assigned role UUID. |
Success response:
{
"message": "Admin users found.",
"total_users": 1,
"data": [
{
"id": "user_uuid",
"name": "Portal Admin",
"email": "portal-admin@example.com",
"phone_number": "9000000001",
"type": "ADMIN",
"role": {
"id": "role_uuid",
"code": "ADMIN",
"name": "Admin",
"guard_name": "web"
},
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
]
}
When no users are found:
{
"message": "Admin users not found.",
"total_users": 0,
"data": []
}
Create Admin User
POST /masters/users
Request:
{
"name": "Portal Admin",
"email": "portal-admin@example.com",
"phone_number": "9000000001",
"password": "password123",
"role_id": "role_uuid"
}
Validation:
| Field | Required | Notes |
|---|---|---|
name |
Yes | Admin user name. |
email |
Yes | Must be unique in users.email. |
phone_number |
No | Must be unique in users.phone_number if sent. |
password |
Yes | Minimum 8 characters. |
role_id |
Yes | Existing Spatie role UUID with guard_name = web. |
Behavior:
- Backend always stores
type = ADMIN. - Backend assigns the submitted role using
syncRoles.
Success response:
{
"message": "Admin user created successfully.",
"data": {
"id": "user_uuid",
"name": "Portal Admin",
"email": "portal-admin@example.com",
"phone_number": "9000000001",
"type": "ADMIN",
"role": {
"id": "role_uuid",
"code": "ADMIN",
"name": "Admin",
"guard_name": "web"
},
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
}
Admin User Details
GET /masters/users/{user}
Success response:
{
"message": "Admin user found.",
"data": {
"id": "user_uuid",
"name": "Portal Admin",
"email": "portal-admin@example.com",
"phone_number": "9000000001",
"type": "ADMIN",
"role": {
"id": "role_uuid",
"code": "ADMIN",
"name": "Admin",
"guard_name": "web"
},
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
}
If {user} is a MEMBER or SUPER_ADMIN, this API returns 404.
Update Admin User
PATCH /masters/users/{user}
PUT /masters/users/{user} is also accepted.
Request:
{
"name": "Portal Manager",
"email": "portal-manager@example.com",
"phone_number": "9000000002",
"password": "new-password123",
"role_id": "role_uuid"
}
Validation:
| Field | Required | Notes |
|---|---|---|
name |
Yes | Admin user name. |
email |
Yes | Must be unique except current user. |
phone_number |
No | Must be unique except current user if sent. |
password |
No | Minimum 8 characters if sent. If omitted, existing password is kept. |
role_id |
Yes | Existing Spatie role UUID with guard_name = web. |
Success response:
{
"message": "Admin user updated successfully.",
"data": {
"id": "user_uuid",
"name": "Portal Manager",
"email": "portal-manager@example.com",
"phone_number": "9000000002",
"type": "ADMIN",
"role": {
"id": "role_uuid",
"code": "ADMIN_MANAGER",
"name": "Admin Manager",
"guard_name": "web"
},
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:30:00.000000Z"
}
}
Delete Admin User
DELETE /masters/users/{user}
Success response:
{
"message": "Admin user deleted successfully."
}
Delete soft-deletes the admin user record.
If the user is referenced by members, notifications, grievance actions, or other module records, delete is blocked with 422 Unprocessable Entity and a validation-style message. Remove or reassign dependent records first.
Role APIs
Role APIs require:
Authorization: Bearer {admin_portal_access_token}
The token must have admin-portal ability and the authenticated user must be ADMIN or SUPER_ADMIN.
Roles use Spatie Laravel Permission. There is no display_name column. The role code is stored directly in roles.name.
The current role API response returns name as a display label generated from the stored role code. For example, stored roles.name = SUPER_NEW_ADMIN is returned as name = Super New Admin.
Examples:
| Frontend sends | Stored in roles.name |
Display shown by portal |
|---|---|---|
Super Admin |
SUPER_ADMIN |
Super Admin |
sup new admin |
SUP_NEW_ADMIN |
Sup New Admin |
Chapter Manager |
CHAPTER_MANAGER |
Chapter Manager |
List Roles
GET /masters/roles
Examples:
GET /masters/roles
GET /masters/roles?search=admin
GET /masters/roles?status=ACTIVE
GET /masters/roles?guard_name=web
Query params:
| Query param | Required | Notes |
|---|---|---|
search |
No | Searches stored role name/code and description. |
q |
No | Alias for search. |
status |
No | ACTIVE or INACTIVE. |
guard_name |
No | Defaults to web for created roles. |
Success response:
{
"message": "Roles found.",
"total_roles": 1,
"data": [
{
"id": "role_uuid",
"name": "Super New Admin",
"description": "Can manage portal settings.",
"guard_name": "web",
"status": "ACTIVE",
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
]
}
When no roles are found:
{
"message": "Roles not found.",
"total_roles": 0,
"data": []
}
Create Role
POST /masters/roles
Request:
{
"name": "sup new admin",
"description": "Can manage portal settings.",
"status": "ACTIVE"
}
Validation:
| Field | Required | Notes |
|---|---|---|
name |
Yes | Frontend sends only role name. Backend converts it to stored role code. |
description |
No | Role description. |
guard_name |
No | Defaults to web. |
status |
No | ACTIVE or INACTIVE. Defaults to ACTIVE. |
Do not send display_name. Do not send code from frontend.
Success response:
{
"message": "Role created successfully.",
"data": {
"id": "role_uuid",
"name": "Sup New Admin",
"description": "Can manage portal settings.",
"guard_name": "web",
"status": "ACTIVE",
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
}
Role Details
GET /masters/roles/{role}
Success response:
{
"message": "Role found.",
"data": {
"id": "role_uuid",
"name": "Sup New Admin",
"description": "Can manage portal settings.",
"guard_name": "web",
"status": "ACTIVE",
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:00:00.000000Z"
}
}
Update Role
PATCH /masters/roles/{role}
PUT /masters/roles/{role} is also accepted.
Request:
{
"name": "Chapter Lead",
"description": "Can manage chapter and member operations.",
"status": "INACTIVE"
}
Validation:
| Field | Required | Notes |
|---|---|---|
name |
Yes | Frontend sends role name. Backend converts it to stored role code. |
description |
No | Send null if empty. |
status |
Yes | ACTIVE or INACTIVE. |
Update is not partial. Send the full editable payload.
Success response:
{
"message": "Role updated successfully.",
"data": {
"id": "role_uuid",
"name": "Chapter Lead",
"description": "Can manage chapter and member operations.",
"guard_name": "web",
"status": "INACTIVE",
"created_at": "2026-05-20T10:00:00.000000Z",
"updated_at": "2026-05-20T10:30:00.000000Z"
}
}
Delete Role
DELETE /masters/roles/{role}
Success response:
{
"message": "Role deleted successfully."
}
Delete soft-deletes the role.
SUPER_ADMIN cannot be deleted. If the role is assigned to users or referenced by permissions/other records, delete is blocked with 422 Unprocessable Entity and a validation-style message. Remove or reassign dependent records first.
Role Permission APIs
List Role Permissions
GET /masters/roles/permissions/{role}
Use this API for the role permission assignment screen. It returns the full permission tree with selection state already resolved for the given role.
Success response:
{
"message": "Role permission assignment data found.",
"data": [
{
"id": "permission_uuid",
"parent_id": null,
"code": "SECURITY",
"name": "SECURITY",
"label": "Security",
"module": "SECURITY",
"icon": "fas fa-cubes",
"route_name": null,
"order": 1,
"is_selected": false,
"children": [
{
"id": "permission_uuid",
"parent_id": "permission_uuid",
"code": "ROLE",
"name": "ROLE",
"label": "Roles",
"module": "ROLE",
"icon": "fas fa-shield-alt",
"route_name": "roles.index",
"order": 1,
"is_selected": false,
"children": [
{
"id": "permission_uuid",
"parent_id": "permission_uuid",
"code": "ROLE_LIST",
"name": "ROLE_LIST",
"label": "List",
"module": "ROLE",
"icon": "fas fa-list",
"route_name": "roles.index",
"order": 1,
"is_selected": true,
"children": []
}
]
}
]
}
]
}
Notes:
- This API no longer returns a flat assigned-permissions list.
- The frontend should render the tree recursively using
children. is_selectedtells the frontend whether the role currently has that permission.nameis the stored permission code.labelis the display label.ordercomes frompermissions.menu_order.
Role Sidebar Permissions
GET /masters/roles/permissions/sidebar/{role?}
Use this API to build the admin portal sidebar. If {role} is not passed, backend resolves the authenticated admin user's role. It only returns visible menu permissions and keeps the nested structure needed for recursive rendering.
Required node fields returned by backend:
iconroute_namenamelabelorder
Success response:
{
"message": "Role sidebar permissions found.",
"data": [
{
"name": "MASTER",
"label": "Master",
"icon": "Database",
"route_name": null,
"order": 1,
"children": [
{
"name": "SETTINGS",
"label": "Settings",
"icon": "Settings",
"route_name": null,
"order": 1,
"children": [
{
"name": "ROLES",
"label": "Roles",
"icon": "Shield",
"route_name": "roles.index",
"order": 1,
"children": []
}
]
}
]
}
]
}
Notes:
- This API only returns permissions assigned to the role.
- Hidden permissions are excluded.
- Menu nodes without
route_nameare still returned when they have visible child nodes.
Assigned Permissions
GET /masters/roles/permissions/assigned/{role?}
Use this API when the frontend only needs the assigned permission codes instead of the full nested tree.
Success response:
{
"message": "Assigned permissions found.",
"data": [
{
"id": "permission_uuid",
"name": "MEMBER_LIST"
},
{
"id": "permission_uuid",
"name": "MEMBER_VIEW"
}
]
}
Sync Role Permissions
POST /api/admin/masters/roles/permissions/{role}/sync
Use this to replace the role's current permissions with the submitted permissions. Parent permissions are automatically included by the backend for any selected child permission. DASHBOARD is also added automatically.
Request:
{
"permission_ids": [
"permission_uuid_1",
"permission_uuid_2"
]
}
Validation:
| Field | Required | Notes |
|---|---|---|
permission_ids |
Yes if permission_codes is not sent |
Array of existing permission UUIDs. |
permission_ids.* |
Yes | Must exist in permissions.uuid. |
permission_codes |
Yes if permission_ids is not sent |
Array of existing permission codes such as MEMBER_LIST. |
permission_codes.* |
Yes | Must exist in permissions.name. |
Success response:
{
"message": "Role permissions assigned successfully.",
"data": [
{
"id": "permission_uuid",
"parent_id": null,
"code": "SECURITY",
"name": "Security",
"module": "SECURITY",
"is_selected": true,
"children": [
{
"id": "permission_uuid",
"parent_id": "permission_uuid",
"code": "ROLE",
"name": "Roles",
"module": "ROLE",
"is_selected": true,
"children": [
{
"id": "permission_uuid",
"parent_id": "permission_uuid",
"code": "ROLE_LIST",
"name": "List",
"module": "ROLE",
"is_selected": true,
"children": []
}
]
}
]
}
]
}
Grievance Action API
Updates a grievance workflow status and notifies the member who raised the grievance.
PATCH /api/admin/grievances/action/{grievance}
Authorization: Bearer {admin_access_token}
Permission: GRIEVANCE_EDIT. SUPER_ADMIN can access the endpoint through the global permission bypass.
Request:
{
"status": "IN_PROGRESS",
"remark": "Initial verification completed and assigned for review."
}
| Field | Required | Notes |
|---|---|---|
status |
Yes | Accepts IN_PROGRESS or RESOLVED according to the allowed transition. |
remark |
Yes | Internal remark entered by the action taker and stored in action history. Maximum 2000 characters. |
Allowed status transitions:
| Current status | Allowed next status |
|---|---|
OPEN |
IN_PROGRESS, RESOLVED |
IN_PROGRESS |
RESOLVED |
RESOLVED |
None |
CLOSED |
None |
Status input is normalized, so values such as In Progress, in-progress, and IN_PROGRESS are accepted as IN_PROGRESS. Invalid transitions return the actual current and requested statuses.
When the status changes to IN_PROGRESS or RESOLVED, a GRIEVANCE push notification is created for the member who raised the grievance. The action creator is excluded if the creator is linked to the same member.
Every successful action is stored in grievance_actions with the grievance, acting admin, previous status, new status, generated member notification message, action-taker remark, and timestamp. Grievance detail and action responses include the latest action history first.
Notification examples:
Your grievance request "Member conduct issue" has been marked as In Progress by BCI Super Admin. Remark: Initial verification completed and assigned for review.
Your grievance request "Member conduct issue" has been marked as Resolved by BCI Super Admin. Resolution: The reported issue was reviewed and resolved.
Success response:
{
"message": "Grievance status updated successfully.",
"data": {
"id": "grievance_uuid",
"status": "IN_PROGRESS",
"actions": [
{
"id": "grievance_action_uuid",
"from_status": "OPEN",
"to_status": "IN_PROGRESS",
"message": "Your grievance request \"Member conduct issue\" has been marked as In Progress by BCI Super Admin. Remark: Initial verification completed and assigned for review.",
"remark": "Initial verification completed and assigned for review.",
"action_by": {
"id": "admin_user_uuid",
"name": "BCI Super Admin"
},
"created_at": "2026-06-12T10:00:00.000000Z"
}
]
}
}
Admin Dropdown Option APIs
These endpoints are available to every authenticated admin portal user. They do not require master list permissions and are intended only for form dropdowns. Existing /masters/* CRUD APIs remain permission-protected.
GET /api/admin/options/categories
GET /api/admin/options/sub-categories
GET /api/admin/options/chapters
GET /api/admin/options/cities
GET /api/admin/options/countries
GET /api/admin/options/event-types
GET /api/admin/options/financial-years
GET /api/admin/options/member-types
GET /api/admin/options/members
GET /api/admin/options/point-types
GET /api/admin/options/roles
GET /api/admin/options/states
Authorization: Bearer {admin_access_token}
Common query parameters:
| Query param | Required | Notes |
|---|---|---|
search |
No | Case-insensitive name search. Code fields are also searched where available. |
q |
No | Alias for search. |
limit |
No | Records per page, 1 to 100. Defaults to 10. |
per_page |
No | Alias for limit. |
page |
No | Page number. Defaults to 1. |
parent_id |
Yes for sub-categories | Required by /api/admin/options/sub-categories. Returns sub-categories for the selected category. |
country_id |
Yes for states | Required by /api/admin/options/states. Returns states for the selected country. |
state_id |
Yes for cities | Required by /api/admin/options/cities. Returns cities for the selected state. |
chapter_id |
No | Members only. Returns approved members assigned to the selected chapter. |
Master option APIs return only active, non-deleted records. The categories endpoint returns parent categories only. The sub-categories endpoint returns child categories for the selected parent_id. The roles endpoint excludes SUPER_ADMIN. The members endpoint returns only approved members and searches member number, name, email, phone number, and business name. The point-types endpoint returns active point types. The financial-years endpoint returns active financial years ordered by latest start_date first.
Response:
{
"message": "Chapters found.",
"meta": {
"total_records": 24,
"page": 1,
"limit": 10,
"total_pages": 3,
"record_start": 1,
"record_end": 10
},
"data": [
{
"id": "chapter_uuid",
"name": "Ahmedabad Chapter"
}
]
}
Frontend Notes
/auth/loginsuccess means OTP was generated. It does not mean the admin is logged in.- Move to OTP screen after
/auth/loginsucceeds. - Save
access_tokenonly after/auth/login/verify. - Use
data.roleanddata.role_idfor portal role context after login. - Use role/permission APIs for action-level authorization after login.
- Convert stored role codes such as
SUPER_NEW_ADMINto labels such asSuper New Adminon the frontend when a display label is needed.
Error Handling
Unauthenticated
HTTP status: 401
{
"message": "Unauthenticated."
}