> ## Documentation Index
> Fetch the complete documentation index at: https://theteam-13fc7f49.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication and Account Endpoints (v2)

This file documents the authentication and account endpoints currently exposed by the application.

## **Table of Contents**

1. **Overview**
2. **Base URL and Headers**
3. **Register**
4. **Login**
5. **Verify Email**
6. **Resend Verification Email**
7. **Send Forgot Password Email**
8. **Reset Password**
9. **Get Authenticated User**
10. **Update Password**
11. **Logout**
12. **Logout From All Devices**
13. **Delete My Account**
14. **Restore Soft-Deleted User**
15. **Admin Verify User Email**
16. **Generate Impersonation Link**
17. **Internal Reused Methods**
18. **Known Implementation Notes**

***

## **1. Overview**

These endpoints cover:

* **account registration**
* **login and token issuance**
* **email verification**
* **forgot-password and reset-password flow**
* **current authenticated user payload**
* **password changes**
* **logout flows**
* **self-account deletion**
* **admin restore and admin email verification**
* **admin impersonation link generation**

The login flow uses Laravel Sanctum tokens.

***

## **2. Base URL and Headers**

Base API URL:

```text theme={null}
{{base_url}}/api
```

Suggested Postman variables:

```text theme={null}
{{base_url}} = http://127.0.0.1:8000
{{token}} = your_sanctum_token
```

Default JSON headers:

```text theme={null}
Accept: application/json
Content-Type: application/json
```

Protected endpoints also need:

```text theme={null}
Authorization: Bearer {{token}}
```

***

## **3. Register**

Creates a new default user account and sends a verification email.

| **Field** | **Value** |
| :- | :- |
| Method | `POST` |
| URL | `/api/auth/register` |
| Auth | No |

### **Request JSON**

```text theme={null}
{
  "company_id": 1,
  "first_name": "Jane",
  "last_name": "Doe",
  "email": "jane.doe@example.com",
  "password": "secret123",
  "phone_number": "+21611111111",
  "image_id": 12
}
```

### **Success Response**

```text theme={null}
{
  "error": false,
  "message": "User created successfully, please verify your email before login."
}
```

### **Validation Error Example**

```text theme={null}
{
  "error": true,
  "message": "The company id field is required."
}
```

### **Duplicate Email Example**

```text theme={null}
{
  "error": true,
  "message": "This email is already registered and verified. Please log in or reset your password."
}
```

Important notes:

* this endpoint creates the account with `role_id = 1`
* `phone_number` is optional
* `image_id` is optional

***

## **4. Login**

Authenticates the user and returns a Sanctum token.

| **Field** | **Value** |
| :- | :- |
| Method | `POST` |
| URL | `/api/auth/login` |
| Auth | No |

### **Basic Request JSON**

```text theme={null}
{
  "email": "jane.doe@example.com",
  "password": "secret123"
}
```

### **Success Response**

Since 19/10/2026 ([Auth User Super Fast](https://theteam-13fc7f49.mintlify.site/clean-up/auth-user-super-fast.md), step 4) the success
response also carries **`user`**: the same slim boot payload as `GET /auth/user` (§9), built by the
same `AuthUserPayloadService`, so the apps paint the dashboard after one round trip instead of
calling `GET /auth/user` right after the login. A staff login carries the profile, `company`,
`role.permissions` and `notifications` only; a student login also has `my_trainings`, `my_exams`,
`validated_lesson_ids`, `certifications` and `cart_summary`. Older app builds ignore the key.
`?legacy=1` does not apply here.

```json theme={null}
{
  "error": false,
  "message": "Login successful.",
  "token": "1|mYExampleSanctumTokenHere",
  "two_factor_enabled": false,
  "user": {
    "id": 12,
    "company_id": 1,
    "role_id": 1,
    "first_name": "Jane",
    "last_name": "Doe",
    "email": "jane.doe@example.com",
    "image": null,
    "company": { "id": 1, "label": "Acme", "currency": "TND" },
    "role": { "id": 1, "label": "Student", "permissions": [] },
    "notifications": [],
    "notification_count": 0,
    "my_trainings": [],
    "my_exams": [],
    "validated_lesson_ids": [],
    "certifications": [],
    "cart_summary": { "items_count": 0, "total_price": 0 }
  }
}
```

The `user` block is abridged here: its full shape, key by key, is in §9.

### **2FA Request Example**

```text theme={null}
{
  "email": "jane.doe@example.com",
  "password": "secret123",
  "2fa_code": "123456"
}
```

### **2FA Required Example**

```text theme={null}
{
  "error": true,
  "message": "Two-factor authentication code is required."
}
```

### **Invalid Credentials Example**

```text theme={null}
{
  "error": true,
  "message": "Invalid credentials."
}
```

### **Unverified Email Example**

```text theme={null}
{
  "error": true,
  "message": "Email not verified."
}
```

### **Captcha Failure Example**

```text theme={null}
{
  "error": true,
  "message": "Captcha verification failed. Please try again or reload page."
}
```

Important notes:

* if `TURNSTILE_BYPASS=false`, the initial login request also needs `turnstileToken`
* if 2FA is enabled, you can use either `2fa_code` or `2fa_recovery_code`
* blocked users are rejected

***

## **5. Verify Email**

Marks the user email as verified through a signed URL.

| **Field** | **Value** |
| :- | :- |
| Method | `GET` |
| URL | `/api/auth/email/verify_email/{id}` |
| Auth | No |

This route uses Laravel signed middleware, so Postman needs the full signed URL from the email.

### **Example Request**

```text theme={null}
GET /api/auth/email/verify_email/7?expires=1770000000&signature=example_signature_here
Accept: text/html
```

### **Already Verified Example**

```text theme={null}
{
  "message": "Email already verified."
}
```

### **User Not Found Example**

```text theme={null}
{
  "message": "User not found."
}
```

Important note:

* on success, this endpoint returns an HTML verification view, not a JSON payload

***

## **6. Resend Verification Email**

Resends the verification email.

| **Field** | **Value** |
| :- | :- |
| Method | `GET` |
| URL | `/api/auth/email/resend_verification_email?email={email}` |
| Auth | No |

### **Example Request**

```text theme={null}
GET /api/auth/email/resend_verification_email?email=jane.doe@example.com
Accept: application/json
```

### **Success Response**

```text theme={null}
{
  "message": "Verification email has been resent."
}
```

### **Already Verified Example**

```text theme={null}
{
  "message": "Email is already verified."
}
```

Note:

* this route is throttled with `throttle:6,1`

***

## **7. Send Forgot Password Email**

Sends a reset-password email.

| **Field** | **Value** |
| :- | :- |
| Method | `GET` |
| URL | `/api/auth/email/send_forgot_password_email?email={email}` |
| Auth | No |

### **Example Request**

```text theme={null}
GET /api/auth/email/send_forgot_password_email?email=jane.doe@example.com
Accept: application/json
```

### **Success Response**

```text theme={null}
{
  "message": "Reset password link sent on your email."
}
```

### **Failure Example**

```text theme={null}
{
  "message": "An error occured, please try again later."
}
```

***

## **8. Reset Password**

Resets the password using the token received by email.

| **Field** | **Value** |
| :- | :- |
| Method | `POST` |
| URL | `/api/auth/email/reset_password` |
| Auth | No |

### **Request JSON**

```text theme={null}
{
  "token": "reset-token-from-email",
  "email": "jane.doe@example.com",
  "password": "newSecret123",
  "password_confirmation": "newSecret123"
}
```

### **Success Response**

```text theme={null}
{
  "message": "Your password has been successfully reset."
}
```

### **Failure Example**

```text theme={null}
{
  "message": "An error occured, please try again later."
}
```

***

## **9. Get Authenticated User**

Returns the boot payload of the apps: the current user, their company and role, the newest
notifications and, for a **student**, one card per owned training / exam with the student's
progress and attempt summary computed server-side.

| **Field** | **Value** |
| :- | :- |
| Method | `GET` |
| URL | `/api/auth/user` |
| Auth | Bearer Token |
| Query | `legacy=1` (optional, transition only) — returns the pre-19/10/2026 `UserResource` graph |

**Changed on 19/10/2026 — slim payload (`AuthUserPayloadService`).** The response went from \~1.5 MB
to \~20 KB for the sample student: it carries **no** question banks, **no** lesson bodies, **no** order
history and **no** attempt history any more. Removed keys: `orders`, `cart`, `wishlist`, `lessons`,
`quiz_student_attempts`, `reviews`, `trainings_as_instructor`, `images_uploaded`, `applied_coupons`,
`classroom_joined_slugs`, `classroom_admin_slugs`, `owned_trainings`, `owned_exams`,
`public_profile_website` / `_description` / `_phone_number` / `_location` / `_email`. New keys (students):
`my_trainings`, `my_exams`, `validated_lesson_ids`, `cart_summary`; `certifications` is flat
(`training_id` / `quiz_id`). `notifications` holds the **3** newest items (was 10). Every date is
`Y-m-d H:i:s`. Older app builds can keep the old shape with `?legacy=1` for one release.
See [Auth User Optimize](https://theteam-13fc7f49.mintlify.site/clean-up/auth-user-optimize.md) for the full plan and what shipped.

### **Example Request**

```text theme={null}
GET /api/auth/user
Authorization: Bearer {{token}}
Accept: application/json
```

### **Response Shape**

The body is never wrapped in a `data` key. Every role gets the first three groups; the student
blocks are present **only** when `role_id` is `1`.

| **Group** | **Keys** |
| :- | :- |
| Identity | `id`, `company_id`, `role_id`, `first_name`, `last_name`, `email`, `phone_number`, `image_id`, `image` (`id`, `type`, `url`), `birthday`, `bio`, `blocked_at`, `email_verified_at`, `created_at`, `two_factor_enabled`, `default_revenue_percentage` (the instructor create screens read it), `loyalty_points`, `public_profile_about_me`, `public_profile_skills`, `public_profile_linkedin_url`, `public_profile_verification`, `hide_public_profile` |
| Nested | `company` (`id`, `label`, `description`, `currency`, `company_phone_number`, `company_email`, `company_image`), `role` (`id`, `label`, `permissions[]` filtered by the user's company) |
| Notifications | `notifications` (3 newest, non-archived: `id`, `student_id`, `title`, `message`, `notification_type_id`, `notif_read_status`, `notif_archive_status`, `created_at`), `notification_count` (unread count). The bell popup pages the rest with `GET /notifications/user/{id}` |
| Student: `my_trainings[]` | one card per owned training (paid order, access not revoked, deduplicated; archived trainings stay listed, like the old `orders[].trainings`): the marketplace card fields (`id`, `label`, `slug`, `description`, `level`, `duration`, `price`, `discounted_price`, `final_price`, `is_public`, `published`, `views_count`, `image`, `badge_logo`, `instructor` (safe columns only), `categories`, `created_at`, `updated_at`), the counters `total_chapters_count`, `total_lessons_count`, `total_quizzes_count`, a `progress` block, `certification_id` (issued certificate id or `null`) and `is_owned: true` |
| Student: `my_trainings[].progress` | `lessons_completed`, `lessons_total`, `quizzes_passed`, `quizzes_total`, `overall_progress_percentage`, `is_completed`, `last_activity_at`, `next_item` (`type` `lesson` / `quiz`, `number` (1-based ordinal), `id`, `label`; `null` when everything is done). Quizzes = the training's final quiz + its **published** chapter quizzes; a quiz is passed when one finished attempt scored at least its `passing_percentage` |
| Student: `my_exams[]` | one card per owned, published exam: the exam card fields (`id`, `label`, `slug`, `description`, `duration`, `break_interval`, `break_duration`, `max_attempts`, `passing_percentage`, `is_published`, `views_count`, `price`, `discounted_price`, `final_price`, `image`, `instructor`, `categories`, `created_at`, `updated_at`), `quiz_questions_count` (a count, never the questions), an `attempts` block, `certification_id`, `is_owned: true` |
| Student: `my_exams[].attempts` | `used` (`MAX(attempt)`), `allowed` (`max_attempts * (reset_count + 1)`, `null` = unlimited), `remaining` (`null` when unlimited), `reset_count`, `best_score_percentage`, `passed`, `last_attempt_at`. Same arithmetic as `start_student_quiz`; `GET /quiz/{id}` returns the same block to a student caller |
| Student: others | `validated_lesson_ids` (ids of the lessons in `student_lesson`), `certifications[]` (`id`, `certification_id`, `training_id`, `quiz_id`, `created_at`), `cart_summary` (`id`, `items_count`, `coupon_id`, `total_price`, `discounted_price`, or `null` without a cart — the cart page fetches `GET /user/{id}/cart/2` on open) |

`categories` on the `my_trainings[]` and `my_exams[]` cards (since 01/11/2026) is the `[{ "id", "label" }]` list of the
marketplace preview cards: one item, since a training or an exam has one category
([Trainings → Assign categories](https://theteam-13fc7f49.mintlify.site/api-reference/trainings.md#assign-categories),
[Quizzes & Exams §3.3](https://theteam-13fc7f49.mintlify.site/api-reference/quizzes-and-exams-endpoints.md)), or `[]` when it has none. The apps use it for the
My Trainings category filter and the My Exam Simulator groups.

What is **never** in this payload: `quiz_questions`, `is_correct`, `explanation`, lesson `content`,
`revenue_percentage`, the instructor's `email` / `default_revenue_percentage`, order rows, attempt rows.

### **Case A — Student (**`role_id: 1`**)**

Captured on 19/10/2026 against a local backend (lists shortened to one item; the card `categories` added on 01/11/2026):

```json theme={null}
{
  "id": 2,
  "company_id": 1,
  "role_id": 1,
  "first_name": "Jane",
  "last_name": "Doe",
  "email": "jane.doe@example.com",
  "phone_number": null,
  "image_id": null,
  "image": null,
  "birthday": null,
  "bio": null,
  "blocked_at": null,
  "email_verified_at": "2026-10-19 16:54:53",
  "created_at": "2026-10-19 16:54:53",
  "two_factor_enabled": 0,
  "default_revenue_percentage": null,
  "loyalty_points": 0,
  "public_profile_about_me": null,
  "public_profile_skills": null,
  "public_profile_linkedin_url": null,
  "public_profile_verification": 0,
  "hide_public_profile": 1,
  "company": {
    "id": 1,
    "label": "THE TEAM",
    "description": null,
    "currency": "TND",
    "company_phone_number": null,
    "company_email": null,
    "company_image": null
  },
  "role": {
    "id": 1,
    "label": "User",
    "permissions": [
      {
        "id": 31,
        "key": "categories-viewany",
        "label": "categories-viewany"
      },
      {
        "id": 33,
        "key": "categories-view",
        "label": "categories-view"
      }
    ]
  },
  "notifications": [
    {
      "id": 5,
      "student_id": 2,
      "title": "Reminder: finish Business Analysis",
      "message": "Reminder: finish Business Analysis — message body.",
      "notification_type_id": 4,
      "notif_read_status": false,
      "notif_archive_status": false,
      "created_at": "2026-10-19 11:54:54"
    }
  ],
  "notification_count": 2,
  "my_trainings": [
    {
      "id": 1,
      "company_id": 1,
      "instructor_id": 1,
      "label": "Business Analysis",
      "slug": "business-analysis",
      "description": "<p>Marketing description</p>",
      "level": "Beginner",
      "duration": 1200,
      "price": 199.5,
      "discounted_price": null,
      "final_price": 199.5,
      "is_public": 1,
      "published": true,
      "views_count": 0,
      "image_id": null,
      "video_id": null,
      "badge_logo_id": null,
      "created_at": "2026-10-19 16:54:53",
      "updated_at": "2026-10-19 16:54:53",
      "image": null,
      "badge_logo": null,
      "instructor": {
        "id": 1,
        "first_name": "Mohamed",
        "last_name": "Haddad",
        "fullname": "Mohamed Haddad",
        "image_id": null,
        "image": null,
        "bio": "PMP trainer",
        "hide_public_profile": true,
        "public_profile_about_me": null,
        "public_profile_skills": null,
        "public_profile_verification": null
      },
      "categories": [{ "id": 2, "label": "Business Analysis" }],
      "chapters_count": 3,
      "total_chapters_count": 3,
      "total_lessons_count": 8,
      "total_quizzes_count": 2,
      "progress": {
        "lessons_completed": 4,
        "lessons_total": 8,
        "quizzes_passed": 1,
        "quizzes_total": 2,
        "overall_progress_percentage": 50,
        "is_completed": false,
        "last_activity_at": "2026-10-19 16:12:24",
        "next_item": {
          "type": "lesson",
          "number": 5,
          "id": 5,
          "label": "Documentation"
        }
      },
      "certification_id": null,
      "is_owned": true
    },
    {
      "id": 2,
      "company_id": 1,
      "instructor_id": 1,
      "label": "Project Management Essentials",
      "slug": "project-management-essentials",
      "description": "<p>Marketing description</p>",
      "level": "Beginner",
      "duration": 600,
      "price": 199.5,
      "discounted_price": null,
      "final_price": 199.5,
      "is_public": 1,
      "published": true,
      "views_count": 0,
      "image_id": null,
      "video_id": null,
      "badge_logo_id": null,
      "created_at": "2026-10-19 16:54:53",
      "updated_at": "2026-10-19 16:54:53",
      "image": null,
      "badge_logo": null,
      "instructor": {
        "id": 1,
        "first_name": "Mohamed",
        "last_name": "Haddad",
        "fullname": "Mohamed Haddad",
        "image_id": null,
        "image": null,
        "bio": "PMP trainer",
        "hide_public_profile": true,
        "public_profile_about_me": null,
        "public_profile_skills": null,
        "public_profile_verification": null
      },
      "categories": [],
      "chapters_count": 2,
      "total_chapters_count": 2,
      "total_lessons_count": 4,
      "total_quizzes_count": 0,
      "progress": {
        "lessons_completed": 0,
        "lessons_total": 4,
        "quizzes_passed": 0,
        "quizzes_total": 0,
        "overall_progress_percentage": 0,
        "is_completed": false,
        "last_activity_at": null,
        "next_item": {
          "type": "lesson",
          "number": 1,
          "id": 9,
          "label": "Charter"
        }
      },
      "certification_id": null,
      "is_owned": true
    }
  ],
  "my_exams": [
    {
      "id": 3,
      "company_id": 1,
      "training_id": null,
      "chapter_id": null,
      "instructor_id": 1,
      "image_id": null,
      "label": "PMP Mock Exam 1",
      "slug": "pmp-mock-exam-1",
      "description": "Exam description",
      "duration": 210,
      "break_interval": 10,
      "break_duration": 5,
      "max_attempts": 3,
      "passing_percentage": 70,
      "is_published": 1,
      "views_count": 0,
      "price": 49,
      "discounted_price": null,
      "final_price": 49,
      "created_at": "2026-10-19 16:54:54",
      "updated_at": "2026-10-19 16:54:54",
      "quiz_questions_count": 5,
      "image": null,
      "instructor": {
        "id": 1,
        "first_name": "Mohamed",
        "last_name": "Haddad",
        "fullname": "Mohamed Haddad",
        "image_id": null,
        "image": null,
        "bio": "PMP trainer",
        "hide_public_profile": true,
        "public_profile_about_me": null,
        "public_profile_skills": null,
        "public_profile_verification": null
      },
      "categories": [{ "id": 3, "label": "PMP" }],
      "attempts": {
        "used": 2,
        "allowed": 3,
        "remaining": 1,
        "reset_count": 0,
        "best_score_percentage": 80,
        "passed": true,
        "last_attempt_at": "2026-10-18 16:54:54"
      },
      "certification_id": null,
      "is_owned": true
    }
  ],
  "validated_lesson_ids": [
    1,
    2,
    3,
    4
  ],
  "certifications": [],
  "cart_summary": {
    "id": 3,
    "items_count": 1,
    "coupon_id": null,
    "total_price": 10,
    "discounted_price": 10
  }
}
```

Reading it: the student validated 4 of the 8 lessons of *Business Analysis* and passed 1 of its 2
quizzes, so the next thing to do is lesson 5 ("Documentation"); on *PMP Mock Exam 1* they used 2 of
3 attempts (no reset yet), the best score is 80 % and the exam is passed; one exam sits in the cart.

### **Case B — Staff (instructor / content manager / admin / super-admin)**

Same identity, `company`, `role` and notification keys; **no** `my_trainings`, `my_exams`,
`validated_lesson_ids`, `certifications` or `cart_summary`. Staff screens use their own endpoints
(instructor dashboard, admin lists). `role.permissions` carries the rows granted to that role **in
that company**; a super-admin bypasses permission checks and may still get `[]`.

### **Case C — Account with 2FA enabled**

The only field that changes is `two_factor_enabled` (`1`). The TOTP secret and recovery codes are
stored encrypted and never returned here; 2FA is enforced at `/api/auth/login` (§4).

### **Legacy shape (**`?legacy=1`**)**

Returns the pre-19/10/2026 `UserResource` graph (`orders[]` with every chapter, lesson and
question, `cart`, `wishlist`, `lessons[]`, `quiz_student_attempts[]`, nested `certifications[].training`
/ `.quiz`). Kept for one release so a stale mobile build keeps working; do not build anything new on it.

### **Error Response**

```text theme={null}
{
  "message": "<exception message>"
}
```

Returned with HTTP `500` if the lookup throws. Since 19/10/2026 the raw exception text (which can carry
SQL) is only echoed when `APP_DEBUG` is on; otherwise the message is a generic "Unable to load…" and the
exception goes to the log / `report()`.

***

## **10. Update Password**

Changes the password for the authenticated account.

| **Field** | **Value** |
| :- | :- |
| Method | `POST` |
| URL | `/api/auth/update_password` |
| Auth | Bearer Token |

### **Request JSON**

```text theme={null}
{
  "current_password": "secret123",
  "new_password": "newSecret123",
  "new_password_confirmation": "newSecret123"
}
```

### **Success Response**

```text theme={null}
{
  "message": "Your password has been successfully reset."
}
```

### **Invalid Current Password Example**

```text theme={null}
{
  "message": "Please verify your old password."
}
```

***

## **11. Logout**

Deletes the current access token.

| **Field** | **Value** |
| :- | :- |
| Method | `POST` |
| URL | `/api/auth/logout` |
| Auth | Bearer Token |

### **Success Response**

```text theme={null}
{
  "message": "Successfully logged out."
}
```

***

## **12. Logout From All Devices**

Deletes all personal access tokens for the target user.

| **Field** | **Value** |
| :- | :- |
| Method | `POST` |
| URL | `/api/auth/logout_all_devices/{user}` |
| Auth | Bearer Token |

### **Example Request**

```text theme={null}
POST /api/auth/logout_all_devices/7
Authorization: Bearer {{token}}
Accept: application/json
```

### **Success Response**

```text theme={null}
{
  "message": "Logged out from all devices."
}
```

***

## **13. Delete My Account**

Soft-deletes the currently authenticated account.

| **Field** | **Value** |
| :- | :- |
| Method | `POST` |
| URL | `/api/delete_my_account` |
| Auth | Bearer Token |

### **Request JSON**

```text theme={null}
{
  "password": "newSecret123",
  "reason": "No longer using the platform."
}
```

### **Success Response**

```text theme={null}
{
  "message": "Your account has been deleted (soft). You can no longer sign in."
}
```

### **Forbidden Example**

```text theme={null}
{
  "message": "Admins and instructors cannot delete their own accounts. Please ask another Super-Admin to deactivate you."
}
```

### **Wrong Password Example**

```text theme={null}
{
  "message": "Invalid password."
}
```

***

## **14. Restore Soft-Deleted User**

Restores a user deleted through Laravel soft delete.

| **Field** | **Value** |
| :- | :- |
| Method | `POST` |
| URL | `/api/users/{userId}/restore` |
| Auth | Bearer Token |

### **Example Request**

```text theme={null}
POST /api/users/7/restore
Authorization: Bearer {{token}}
Accept: application/json
```

### **Success Response**

```text theme={null}
{
  "message": "User has been restored successfully.",
  "user_id": 7
}
```

### **Not Deleted Example**

```text theme={null}
{
  "message": "User is not deleted."
}
```

### **Unauthorized Example**

```text theme={null}
{
  "message": "Unauthorized."
}
```

***

## **15. Admin Verify User Email**

Marks a target user's email as verified.

| **Field** | **Value** |
| :- | :- |
| Method | `POST` |
| URL | `/api/admin/verify-user-email` |
| Auth | Bearer Token in practice |

### **Request JSON by User ID**

```text theme={null}
{
  "user_id": 7
}
```

### **Request JSON by Email**

```text theme={null}
{
  "user_email": "jane.doe@example.com"
}
```

### **Success Response**

```text theme={null}
{
  "message": "Email verified successfully."
}
```

### **Already Verified Example**

```text theme={null}
{
  "message": "Email already verified."
}
```

### **Company Restriction Example**

```text theme={null}
{
  "message": "You can only verify users from your own company."
}
```

***

## **16. Generate Impersonation Link**

Generates a temporary signed impersonation URL for an admin or super-admin.

| **Field** | **Value** |
| :- | :- |
| Method | `POST` |
| URL | `/api/auth/admin/impersonate/generate` |
| Auth | Bearer Token in practice |

### **Request JSON by User ID**

```text theme={null}
{
  "user_id": 15,
  "expires": 30
}
```

### **Request JSON by Email**

```text theme={null}
{
  "user_email": "student1@example.com",
  "expires": 45
}
```

### **Success Response**

```text theme={null}
{
  "url": "http://127.0.0.1:8000/impersonate/15?expires=1770000000&signature=example",
  "expires_in_minutes": 30,
  "user": {
    "id": 15,
    "name": "Student One",
    "email": "student1@example.com"
  }
}
```

### **Forbidden Example**

```text theme={null}
{
  "message": "Impersonating admin users is not allowed."
}
```

***

## **17. Internal Reused Methods**

These methods are part of the same controller logic but do not have their own API route:

* `formatTrainings($trainings)` Brief role: helper for nested training formatting inside authentication/account payload preparation.

***

## **18. Known Implementation Notes**

* `verify_email` returns an HTML view on success, not JSON.
* `adminVerifyEmail` is declared outside the main authenticated route group, but the controller code expects an authenticated admin or super-admin.
* `generateImpersonateLink` is also declared without explicit route middleware in `api.php`, but the controller expects an authenticated admin or super-admin.
* Login uses Sanctum tokens even though the `User` model also implements `JWTSubject`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.