MeetyyAPI
Documentation / Admin API Reference / Account Moderation

Admin Account Moderation API

Admin endpoints for account moderation — browsing mentee accounts, and suspending, banning, or reinstating any account. Implements admin-brd.md §6, expressed on the existing is_active / is_banned columns (no new user columns).

The surface is split to match the platform's mentor/mentee vocabulary:

  • /admin/mentees — role-scoped browsing of mentee accounts (this document)

  • /admin/mentors — mentor application browsing and lifecycle (existing, keyed by profile id; application suspension there is independent of account moderation)

  • /admin/accounts — the moderation actions, role-agnostic. A target may be a mentee, a mentor, or both — the account is moderated once, whatever roles it holds.

  • Base URL: /api/v1/admin

  • Auth: Sanctum bearer token — Authorization: Bearer {token}

  • Role: all endpoints require the admin role — non-admins receive 403

Moderation rules

  • Suspend sets is_active = false. Indefinite — there is no auto-expiry; only an admin reinstate lifts it.
  • Ban sets is_banned = true. Indefinite; only an admin reinstate lifts it.
  • Reinstate restores is_active = true, is_banned = false and lifts either state.
  • Suspend and ban both revoke every Sanctum token, killing open sessions immediately, and block login (401). Moderated accounts are excluded from feed suggestions.
  • A reason is required for every action and is emailed to the user (account.suspended / account.banned / account.reinstated — mail only, since a moderated user cannot log in to read an in-app notification).
  • Every action writes an admin_audit_logs entry in the same transaction as the state change — actor, before/after flags, reason, IP, user agent. If the audit write fails, the action rolls back. Who/when/why lives only in the audit log, never on the users table.
  • Policy guards: an admin cannot moderate themselves, and cannot moderate another admin — both return 403.
  • Account moderation is independent of the mentor application lifecycle — banning a mentor's account does not change their approval_status, and vice versa.
  • Banning does not touch the account's content — content takedown is a separate, separately-audited decision (admin-brd.md §7, pending).

Account status is derived from the two flags (ban wins):

is_banned is_active account_status Login
false true active
false false suspended
true banned

Response envelope

Success

{ "status": "success", "message": "…", "data": { } }

Error

{ "status": "error", "message": "…", "data": { } }

Status codes

Code Meaning
200 OK (list, show, action applied)
401 Missing/invalid token
403 Caller lacks the admin role, or the policy forbids the target (self / another admin)
404 Account not found (or not a mentee, on the mentee routes)
422 Validation error, or illegal state transition
500 Server error

Admin account object

{
  "id": "9b6f…",
  "full_name": "John Doe",
  "username": "johndoe",
  "email": "john@example.com",
  "avatar_url": null,
  "is_active": true,
  "is_banned": false,
  "account_status": "active",
  "account_status_label": "Active",
  "email_verified_at": "2026-07-01T09:00:00+00:00",
  "roles": ["mentee"],
  "mentee_profile": { "…mentee profile object…" },
  "moderation_history": [
    {
      "id": "8a2c…",
      "action": "user.banned",
      "action_label": "User Banned",
      "reason": "Fraudulent activity.",
      "changes": {
        "before": { "is_active": true, "is_banned": false },
        "after": { "is_active": true, "is_banned": true }
      },
      "ip_address": "203.0.113.7",
      "admin": { "id": "7c1d…", "full_name": "Admin One", "username": "admin1" },
      "created_at": "2026-07-23T10:15:00+00:00"
    }
  ],
  "created_at": "2026-07-01T09:00:00+00:00",
  "updated_at": "2026-07-23T10:15:00+00:00"
}
  • account_status / account_status_label are derived from the flags (table above).
  • moderation_history is present on mentee detail and action responses — the 20 most recent audit entries against this account, newest first, each with the acting admin.
  • mentee_profile is present when the relation is loaded (mentee detail).

1. List mentees

GET /api/v1/admin/mentees

Returns accounts holding the mentee role, newest first, paginated. A dual-role user (mentor who is also a mentee) appears here too; accounts without the mentee role — e.g. mentor-only or admin accounts — do not.

Query parameters

Param Type Default Notes
status string Filter: active, suspended, or banned. Invalid values → 422.
search string Matches full_name, username, or email (partial)
date_from date Registered on/after this date (Y-m-d). Invalid dates → 422.
date_to date Registered on/before this date; must be ≥ date_from
per_page int 15 Page size (max 100)

Example

GET /api/v1/admin/mentees?status=banned&search=john&date_from=2026-07-01&per_page=20

200 Response

{
  "status": "success",
  "message": "Mentees retrieved.",
  "data": {
    "mentees": [ { "…admin account object…" } ],
    "pagination": { "total": 42, "per_page": 20, "current_page": 1, "last_page": 3 }
  }
}

Errors: 401 unauthenticated · 403 not an admin


2. Show a mentee

GET /api/v1/admin/mentees/{userId}

Returns the account with its mentee_profile and moderation_history embedded, so prior admin actions are visible without a second request. 404 if the user does not exist or does not hold the mentee role.

200 Response

{
  "status": "success",
  "message": "Mentee details retrieved.",
  "data": { "mentee": { "…admin account object with moderation_history…" } }
}

Errors: 401 unauthenticated · 403 not an admin · 404 not found / not a mentee


3. Suspend an account

PATCH /api/v1/admin/accounts/{userId}/suspend

Role-agnostic — works for mentees and mentors alike. Sets is_active = false, revokes all tokens, writes a user.suspended audit entry, and emails the user with the reason.

Body

Field Type Required Notes
reason string yes Max 1000 chars — A reason is required to suspend an account.

Example

{ "reason": "Repeated abusive behaviour." }

200 Response

{
  "status": "success",
  "message": "Account suspended.",
  "data": { "account": { "…admin account object, account_status: suspended…" } }
}

Errors

Code When
422 reason missing; account already suspended — This account is already suspended.; account is banned — This account is banned. Reinstate it before applying a suspension.
403 Target is the caller or another admin — You cannot moderate this account.
404 Account not found
401 Unauthenticated

4. Ban an account

PATCH /api/v1/admin/accounts/{userId}/ban

Sets is_banned = true, revokes all tokens, writes a user.banned audit entry, and emails the user with the reason. A suspended account can be banned (escalation).

Body

Field Type Required Notes
reason string yes Max 1000 chars — A reason is required to ban an account.

200 Response

{
  "status": "success",
  "message": "Account banned.",
  "data": { "account": { "…admin account object, account_status: banned…" } }
}

Errors

Code When
422 reason missing; account already banned — This account is already banned.
403 Target is the caller or another admin
404 Account not found
401 Unauthenticated

5. Reinstate an account

PATCH /api/v1/admin/accounts/{userId}/reinstate

Restores is_active = true, is_banned = false (lifts a suspension or a ban), writes a user.reinstated audit entry, and emails the user. Tokens are not restored — the user logs in again normally.

Body

Field Type Required Notes
reason string yes Max 1000 chars — A reason is required to reinstate an account.

200 Response

{
  "status": "success",
  "message": "Account reinstated.",
  "data": { "account": { "…admin account object, account_status: active…" } }
}

Errors

Code When
422 reason missing; account not moderated — This account is neither suspended nor banned.
403 Target is the caller or another admin
404 Account not found
401 Unauthenticated

Related

  • GET /api/v1/admin/mentors — mentor application browsing and lifecycle. The list accepts status (approval status), account_status (active / suspended / banned), search (legal name + applicant name/username/email), date_from / date_to, and per_page. The application detail (GET /admin/mentors/{profileId}) embeds the same admin account object as data.account — with moderation_history — so a mentor's account state is visible there; to moderate it, take the user.id and call the /admin/accounts/{userId}/* actions above.
  • admin-brd.md §6 (User Account Moderation), §8 (Admin Audit Log) — note the §6.2 account_status column proposal was not adopted; moderation state stays on the existing is_active / is_banned booleans, with UserAccountStatus derived in code.
  • Admin Verification Document API — mentor KYC document review.