Back to landing page
Last updated: 09 Jul 2026, 10:15 AM
Documentation

Admin Portal API Flow

Next.js admin portal APIs, permissions, modules, and reports.

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

  1. Admin enters username and password.
  2. username currently supports email and also maps to email_or_phone.
  3. Backend checks password and confirms the user is ADMIN or SUPER_ADMIN.
  4. Backend creates a LOGIN OTP.
  5. If username is an email and the app is running in production, backend sends the OTP by Laravel mail.
  6. Admin submits OTP.
  7. Backend verifies OTP and returns a Sanctum token with admin-portal ability.

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:

  • counters is for the top summary cards.
  • this_month is for current month growth summary.
  • Member and business totals include approved members only.
  • upcoming_events is limited to 5 records.
  • recent_activities is limited to 5 records.
  • grievances is 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_date comes from member_chapters.date_of_joining
  • duration comes from the selected chapter's current active fee
  • expiry_date is calculated from joining_date + chapter fee duration
  • expired_in is calculated against the current server time
  • membership_status is returned as ACTIVE, EXPIRING_SOON, or EXPIRED

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 SUBSCRIPTION notifications to members whose chapter subscription is expiring within 7 days or already expired.
  • RENEWED notification 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-12 resolves to FY 2026-27.

Expiry logic:

  • Point rule expiry supports NONE, DAYS, MONTHS, QUARTERLY, HALF_YEARLY, YEARLY, and FINANCIAL_YEAR.
  • Current seeded point rules use FINANCIAL_YEAR, so expired_at is the linked financial year end date.
  • Dashboard point totals include only rows where earned_at <= now and expired_at is null or in the future.

Duplicate rule:

  • The ledger has a unique constraint on point_type_id, source_type, source_id, and member_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.
  • ADMIN and SUPER_ADMIN can receive OTP.
  • MEMBER is 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-portal ability.
  • role is the assigned Spatie role code from roles.name.
  • role_id is 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_id
  • state_id
  • city_id
  • business_category_id
  • business_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, and member_businesses records 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_status to APPROVED.
  • Stores a member_approval_records entry 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_JOINED notification 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 PENDING or NULL.
  • 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_records entry.
  • 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_LIST or EVENT_ADD or EVENT_EDIT or EVENT_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_image is uploaded
  • appends uploaded event gallery images into event_photos when photos[] or images[] 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_selected tells the frontend whether the role currently has that permission.
  • name is the stored permission code.
  • label is the display label.
  • order comes from permissions.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:

  • icon
  • route_name
  • name
  • label
  • order

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_name are 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/login success means OTP was generated. It does not mean the admin is logged in.
  • Move to OTP screen after /auth/login succeeds.
  • Save access_token only after /auth/login/verify.
  • Use data.role and data.role_id for portal role context after login.
  • Use role/permission APIs for action-level authorization after login.
  • Convert stored role codes such as SUPER_NEW_ADMIN to labels such as Super New Admin on the frontend when a display label is needed.

Error Handling

Unauthenticated

HTTP status: 401

{
  "message": "Unauthenticated."
}