# Guest

**Every route that requires zero authentication** — determined the literal way, by checking each route's actual middleware list for `auth:api` (not by guessing from the URL prefix). This is the true entry point for all four roles: register, verify OTP, resend code, log in, request/verify/complete a password reset — for Client, Company, Technician, and Admin alike. Once any of these returns a token, every subsequent call for that role moves to its own file (`02-client.md`, `03-technician.md`, `04-company.md`, `05-admin.md`). Grouped by role beneath this shared "no auth required" umbrella, since that's the one thing every route here has in common — the account-creation/session-start mechanics differ per role (see [AUTH.md](AUTH.md)).

**23 endpoints in this section.**

## 01. Client Auth (Register/Login/Verify/Reset)

#### Register New Account

**What this does:** Starts individual registration — caches the submitted profile + password under a short-lived pending-registration token and sends an OTP to the given email/phone. No `users` row is created yet.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/client/auth/register` |
| **Route name** | `client.auth.register` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `201 Created`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `full_name`
- **Description:** Full Name
- **Type:** `string`
- **Required:** required
- **Example:** `Example Name`
- **Validation rules:** `required|string|max:150`
- `required` — must be provided.
- `string` — must be text.
- `max:150` — must not exceed 150.
### `email`
- **Description:** Email
- **Type:** `string`
- **Required:** required
- **Example:** `user@example.com`
- **Validation rules:** `required|email|unique:users,email`
- `required` — must be provided.
- `email` — must be a valid email address.
- `unique:users,email` — must be unique (users, email).
### `phone`
- **Description:** Phone
- **Type:** `string`
- **Required:** optional
- **Example:** `1234567890`
- **Validation rules:** `nullable|string|max:20|unique:users,phone`
- `nullable` — may be null.
- `string` — must be text.
- `max:20` — must not exceed 20.
- `unique:users,phone` — must be unique (users, phone).
### `phone_code`
- **Description:** Phone Code
- **Type:** `string`
- **Required:** optional
- **Example:** `20`
- **Validation rules:** `nullable|string|max:5`
- `nullable` — may be null.
- `string` — must be text.
- `max:5` — must not exceed 5.
### `password`
- **Description:** Password
- **Type:** `string`
- **Required:** required
- **Example:** `Password123!`
- **Validation rules:** `required|string|min:8|confirmed`
- `required` — must be provided.
- `string` — must be text.
- `min:8` — must be at least 8.
- `confirmed` — requires a matching *_confirmation field.
### `device_token`
- **Description:** Device Token
- **Type:** `string`
- **Required:** optional
- **Example:** `fcm-token-here`
- **Validation rules:** `nullable|string`
- `nullable` — may be null.
- `string` — must be text.
### `device_type`
- **Description:** Device Type
- **Type:** `string`
- **Required:** optional
- **Example:** `ios`
- **Validation rules:** `nullable|in:ios,android,web,huawei`
- `nullable` — may be null.
- `in:ios,android,web,huawei` — must be one of: ios, android, web, huawei.

**Example Request Body:**
```json
{
    "full_name": "Karim Elkady",
    "email": "karim.register@example.com",
    "phone": "1055512345",
    "phone_code": "20",
    "password": "password123",
    "password_confirmation": "password123"
}
```

**Actual Success Response(s):**

*{ 201 Created — valid registration }*
```json
{
    "status": "success",
    "message": "A verification OTP has been sent to your email. Please verify your account to continue.",
    "data": {
        "verification_token": "736c7889-d429-4596-b7f2-312d281cc158"
    }
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Validation Error - missing fields }*
```json
{
    "status": "fail",
    "message": "Please enter your email address.",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Validation Error - duplicate email }*
```json
{
    "status": "fail",
    "message": "This email address is already registered.",
    "data": null
}
```

**What happens next:** Call `POST /client/auth/verify` with the OTP `code` and the returned `verification_token` to complete registration.

---

#### Verify Account (OTP)

**What this does:** Confirms the OTP `code` against the `verification_token` from register/login and creates the `User` row (plus the role-specific profile). Verify creates the account and **auto-logs in** — the response already carries a usable `token`.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/client/auth/verify` |
| **Route name** | `client.auth.verify` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email or Phone
- **Type:** `string`
- **Required:** required
- **Example:** `individual@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `code`
- **Description:** Verification Code
- **Type:** `string`
- **Required:** required
- **Example:** `123456`
- **Validation rules:** `required|string|size:6`
- `required` — must be provided.
- `string` — must be text.
- `size:6` — custom validation rule.
### `token`
- **Description:** Token
- **Type:** `string`
- **Required:** required
- **Example:** `<verification-token-from-previous-response>`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

**Example Request Body:**
```json
{
    "login": "karim.register@example.com",
    "code": "123456",
    "token": "736c7889-d429-4596-b7f2-312d281cc158"
}
```

**Actual Success Response(s):**

*{ 200 OK — valid registration code }*
```json
{
    "status": "success",
    "message": "Email verified successfully",
    "data": {
        "id": 59,
        "name": "Karim Elkady",
        "email": "karim.register@example.com",
        "phone": "1055512345",
        "user_type": "individual",
        "user_type_label": "Individual",
        "status": "active",
        "status_label": "Active",
        "is_active": true,
        "is_verified": true,
        "is_banned": false,
        "avatar": null,
        "settings": {
            "locale": "ar",
            "allow_notifications": true
        },
        "last_login_at": "2026-08-20T10:51:54.000000Z",
        "created_at": "2026-08-20T10:51:54.000000Z",
        "completed_orders_total": 0,
        "cancelled_orders_total": 0,
        "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0L2FwaS92MS9jbGllbnQvYXV0aC92ZXJpZnkiLCJpYXQiOjE3ODcyMjMxMTQsImV4cCI6MTc5MjQwNzExNCwibmJmIjoxNzg3MjIzMTE0LCJqdGkiOiJPc21BdGV2eGM0dGtORXJZIiwic3ViIjoiNTkiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.rZP9Cim6NuS64shPs9sUCk9mizmJv-qveLeoLMxKyo0"
    }
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Error - invalid/expired token }*
```json
{
    "status": "fail",
    "message": "Expired or invalid verification code.",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Validation Error - missing code }*
```json
{
    "status": "fail",
    "message": "Please enter the verification code.",
    "data": null
}
```

**What happens next:** Proceed to browsing/ordering — see the Requests / Jobs section for cart checkout.

---

#### Resend Verification Code

**What this does:** Resends the OTP code for an in-progress verification/reset flow, reusing the same `verification_token`.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/client/auth/resend-code` |
| **Route name** | `client.auth.resend` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `verification_token`
- **Description:** Token
- **Type:** `string`
- **Required:** required
- **Example:** `<verification-token-from-previous-response>`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

**Example Request Body:**
```json
{
    "verification_token": "cb750c98-0938-43b9-8247-2cc9f0c53a5b"
}
```

**Actual Success Response(s):**

*{ 200 OK — valid token }*
```json
{
    "status": "success",
    "message": "Verification code sent successfully",
    "data": {
        "verification_token": "cb750c98-0938-43b9-8247-2cc9f0c53a5b"
    }
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Error - invalid token }*
```json
{
    "status": "fail",
    "message": "Invalid or expired verification token.",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Validation Error - missing token }*
```json
{
    "status": "fail",
    "message": "The Token field is required.",
    "data": null
}
```

**What happens next:** Retry `POST /client/auth/verify` (or the relevant verify-reset-code endpoint) with the new code.

---

#### Login

**What this does:** Authenticates an existing, `active` individual account by email or phone (`login` field, auto-detected) + `password`, returning a JWT `token`.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/client/auth/login` |
| **Route name** | `client.auth.login` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email or Phone
- **Type:** `string`
- **Required:** required
- **Example:** `individual@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `password`
- **Description:** Password
- **Type:** `string`
- **Required:** required
- **Example:** `password`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `device_token`
- **Description:** Device Token
- **Type:** `string`
- **Required:** optional
- **Example:** `fcm-token-here`
- **Validation rules:** `nullable|string`
- `nullable` — may be null.
- `string` — must be text.
### `device_type`
- **Description:** Device Type
- **Type:** `string`
- **Required:** optional
- **Example:** `ios`
- **Validation rules:** `nullable|in:ios,android,web,huawei`
- `nullable` — may be null.
- `in:ios,android,web,huawei` — must be one of: ios, android, web, huawei.

**Example Request Body:**
```json
{
    "login": "individual@info.com",
    "password": "password"
}
```

**Actual Success Response(s):**

*{ 200 OK — valid credentials }*
```json
{
    "status": "success",
    "message": "You logged in successfully",
    "data": {
        "id": 3,
        "name": "Individual User",
        "email": "individual@info.com",
        "phone": "966500000003",
        "user_type": "individual",
        "user_type_label": "Individual",
        "status": "active",
        "status_label": "Active",
        "is_active": true,
        "is_verified": true,
        "is_banned": false,
        "avatar": null,
        "settings": {
            "locale": "ar",
            "allow_notifications": true
        },
        "last_login_at": null,
        "created_at": "2026-08-20T18:37:22.000000Z",
        "completed_orders_total": 0,
        "cancelled_orders_total": 0,
        "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgxMjMvYXBpL3YxL2NsaWVudC9hdXRoL2xvZ2luIiwiaWF0IjoxNzg3MzEwMzM2LCJleHAiOjE3OTI0OTQzMzYsIm5iZiI6MTc4NzMxMDMzNiwianRpIjoiZXZ6RU9xR2RkTjhRdVRnNiIsInN1YiI6IjMiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.erhLEZOqxYaIOqE9denJqxCUAAPNxyyjE9_gk8lvmco"
    }
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Error - wrong password }*
```json
{
    "status": "fail",
    "message": "Invalid email or password, please try again!",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Validation Error - missing password }*
```json
{
    "status": "fail",
    "message": "Please enter your password.",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Error - account not active }*
```json
{
    "status": "fail",
    "message": "Your account is pending activation. Please verify your account.",
    "data": null
}
```

**What happens next:** Use the returned `data.token` as `Authorization: Bearer <token>` on every subsequent authenticated call in this role's sections.

---

#### Request Password Reset

**What this does:** Starts password reset — sends an OTP to the account's email/phone and returns a `verification_token` identifying this reset session.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/client/auth/forgot-password` |
| **Route name** | `client.auth.forgot` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email or Phone
- **Type:** `string`
- **Required:** required
- **Example:** `individual@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

**Example Request Body:**
```json
{
    "login": "suspended.individual@info.com"
}
```

**Actual Success Response(s):**

*{ 200 OK — known login }*
```json
{
    "status": "success",
    "message": "Verification code sent successfully",
    "data": {
        "verification_token": "a7ee8ed1-ec5a-4653-8904-869d2ed2752c"
    }
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Validation Error - missing login }*
```json
{
    "status": "fail",
    "message": "Please enter your email or phone number.",
    "data": null
}
```

**What happens next:** Call `POST /client/auth/verify-reset-code` with the OTP to confirm it, then `POST /client/auth/reset-password`.

---

#### Verify Reset Code

**What this does:** Confirms the password-reset OTP without changing the password yet.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/client/auth/verify-reset-code` |
| **Route name** | `client.auth.verify-reset-code` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email or Phone
- **Type:** `string`
- **Required:** required
- **Example:** `individual@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `code`
- **Description:** Verification Code
- **Type:** `string`
- **Required:** required
- **Example:** `123456`
- **Validation rules:** `required|string|size:6`
- `required` — must be provided.
- `string` — must be text.
- `size:6` — custom validation rule.
### `token`
- **Description:** Token
- **Type:** `string`
- **Required:** required
- **Example:** `<verification-token-from-previous-response>`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

**Example Request Body:**
```json
{
    "login": "suspended.individual@info.com",
    "code": "123456",
    "token": "a7ee8ed1-ec5a-4653-8904-869d2ed2752c"
}
```

**Actual Success Response(s):**

*{ 200 OK — valid code }*
```json
{
    "status": "success",
    "message": "Code verified. You can now set a new password.",
    "data": null
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Error - invalid code (already verified/consumed) }*
```json
{
    "status": "fail",
    "message": "Verification code has already been verified.",
    "data": null
}
```

**What happens next:** Call `POST /client/auth/reset-password` with the same token and a new password.

---

#### Reset Password

**What this does:** Sets a new password using the already-verified reset token from `verify-reset-code`.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/client/auth/reset-password` |
| **Route name** | `client.auth.reset` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email or Phone
- **Type:** `string`
- **Required:** required
- **Example:** `individual@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `token`
- **Description:** Token
- **Type:** `string`
- **Required:** required
- **Example:** `<verification-token-from-previous-response>`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `password`
- **Description:** New Password
- **Type:** `string`
- **Required:** required
- **Example:** `Password123!`
- **Validation rules:** `required|string|min:8|confirmed`
- `required` — must be provided.
- `string` — must be text.
- `min:8` — must be at least 8.
- `confirmed` — requires a matching *_confirmation field.
### `password_confirmation`
- **Description:** Password confirmation
- **Type:** `string`
- **Required:** required
- **Example:** `Password123!`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

**Example Request Body:**
```json
{
    "login": "suspended.individual@info.com",
    "token": "a7ee8ed1-ec5a-4653-8904-869d2ed2752c",
    "password": "NewPassword123!",
    "password_confirmation": "NewPassword123!"
}
```

**Actual Success Response(s):**

*{ 200 OK — valid verified token }*
```json
{
    "status": "success",
    "message": "Password reset successfully.",
    "data": null
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Validation Error - password confirmation mismatch }*
```json
{
    "status": "fail",
    "message": "Password confirmation does not match.",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Error - code not verified yet (business rule) }*
```json
{
    "status": "fail",
    "message": "Please verify the code before continuing.",
    "data": null
}
```

**What happens next:** Log in again with `POST /client/auth/login` using the new password.

---

#### Contact Client

**What this does:** Public contact-form submission — creates a `ContactMessage` for admin review. No account needed.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/client/contact` |
| **Route name** | `client.contact` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `name`
- **Description:** Name
- **Type:** `string`
- **Required:** required
- **Example:** `Example Name`
- **Validation rules:** `required|string|max:100`
- `required` — must be provided.
- `string` — must be text.
- `max:100` — must not exceed 100.
### `email`
- **Description:** Email
- **Type:** `string`
- **Required:** required
- **Example:** `user@example.com`
- **Validation rules:** `required|email`
- `required` — must be provided.
- `email` — must be a valid email address.
### `phone`
- **Description:** Phone
- **Type:** `string`
- **Required:** optional
- **Example:** `1234567890`
- **Validation rules:** `nullable|string|max:20`
- `nullable` — may be null.
- `string` — must be text.
- `max:20` — must not exceed 20.
### `subject`
- **Description:** Subject
- **Type:** `string`
- **Required:** required
- **Example:** `Inquiry about your service`
- **Validation rules:** `required|string|max:200`
- `required` — must be provided.
- `string` — must be text.
- `max:200` — must not exceed 200.
### `message`
- **Description:** Message
- **Type:** `string`
- **Required:** required
- **Example:** `Example message body text.`
- **Validation rules:** `required|string|max:2000`
- `required` — must be provided.
- `string` — must be text.
- `max:2000` — must not exceed 2000.

**Example Request Body:**
```json
{
    "name": "Sara Ahmed",
    "email": "sara@example.com",
    "phone": "+201000000000",
    "subject": "Question about pricing",
    "message": "Do you offer discounts for repeat customers?"
}
```

**Actual Success Response(s):**

*{ 200 OK — OK }*
```json
{
    "status": "success",
    "message": "Your message has been sent successfully",
    "data": null
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Validation Error }*
```json
{
    "status": "fail",
    "message": "Please enter your email address.",
    "data": null
}
```

**What happens next:** An admin can review and reply via `GET /admin/contact-messages` and `POST /admin/contact-messages/{id}/reply`.

---

## 02. Company Auth (Register/Login/Verify/Reset)

#### Register New Account

**What this does:** Starts company registration — caches the submitted profile + password under a short-lived pending-registration token and sends an OTP to the given email/phone. A company account starts `status: pending` and cannot log in until an admin approves it. No `users` row is created yet.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/company/auth/register` |
| **Route name** | `company.auth.register` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `201 Created`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `legal_name`
- **Description:** Legal Company Name
- **Type:** `string`
- **Required:** required
- **Example:** `Example Name`
- **Validation rules:** `required|string|max:200`
- `required` — must be provided.
- `string` — must be text.
- `max:200` — must not exceed 200.
### `email`
- **Description:** Email
- **Type:** `string`
- **Required:** required
- **Example:** `user@example.com`
- **Validation rules:** `required|email|unique:users,email`
- `required` — must be provided.
- `email` — must be a valid email address.
- `unique:users,email` — must be unique (users, email).
### `phone`
- **Description:** Phone
- **Type:** `string`
- **Required:** required
- **Example:** `1234567890`
- **Validation rules:** `required|string|max:20|unique:users,phone`
- `required` — must be provided.
- `string` — must be text.
- `max:20` — must not exceed 20.
- `unique:users,phone` — must be unique (users, phone).
### `phone_code`
- **Description:** Phone Code
- **Type:** `string`
- **Required:** optional
- **Example:** `20`
- **Validation rules:** `nullable|string|max:5`
- `nullable` — may be null.
- `string` — must be text.
- `max:5` — must not exceed 5.
### `password`
- **Description:** Password
- **Type:** `string`
- **Required:** required
- **Example:** `Password123!`
- **Validation rules:** `required|string|min:8|confirmed`
- `required` — must be provided.
- `string` — must be text.
- `min:8` — must be at least 8.
- `confirmed` — requires a matching *_confirmation field.
### `vat_number`
- **Description:** VAT Number
- **Type:** `string`
- **Required:** optional
- **Example:** `300012345600003`
- **Validation rules:** `nullable|string|max:50`
- `nullable` — may be null.
- `string` — must be text.
- `max:50` — must not exceed 50.
### `commercial_registration_no`
- **Description:** Commercial Registration Number
- **Type:** `string`
- **Required:** optional
- **Example:** `1010101010`
- **Validation rules:** `nullable|string|max:50`
- `nullable` — may be null.
- `string` — must be text.
- `max:50` — must not exceed 50.
### `account_owner_phone`
- **Description:** Account Owner Phone
- **Type:** `string`
- **Required:** optional
- **Example:** `1234567890`
- **Validation rules:** `nullable|string|max:20`
- `nullable` — may be null.
- `string` — must be text.
- `max:20` — must not exceed 20.
### `device_id`
- **Description:** Device id
- **Type:** `integer`
- **Required:** optional
- **Example:** `device-uuid-123`
- **Validation rules:** `nullable|string|max:255`
- `nullable` — may be null.
- `string` — must be text.
- `max:255` — must not exceed 255.
### `device_token`
- **Description:** Device token
- **Type:** `string`
- **Required:** optional
- **Example:** `fcm-token-here`
- **Validation rules:** `nullable|string|max:500`
- `nullable` — may be null.
- `string` — must be text.
- `max:500` — must not exceed 500.
### `device_type`
- **Description:** Device Type
- **Type:** `string`
- **Required:** optional
- **Example:** `ios`
- **Validation rules:** `nullable|in:ios,android,web,huawei`
- `nullable` — may be null.
- `in:ios,android,web,huawei` — must be one of: ios, android, web, huawei.

**Example Request Body:**
```json
{
    "legal_name": "Acme Maintenance Co",
    "email": "acme.register@example.com",
    "phone": "1155512345",
    "phone_code": "20",
    "password": "password123",
    "password_confirmation": "password123",
    "vat_number": "VAT-123456",
    "commercial_registration_no": "CR-987654",
    "account_owner_phone": "1155512399"
}
```

**Actual Success Response(s):**

*{ 201 Created — valid registration }*
```json
{
    "status": "success",
    "message": "A verification OTP has been sent to your email. Please verify your account to continue.",
    "data": {
        "verification_token": "a3df4b3a-5baa-481e-b43e-ebc857fe6151"
    }
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Validation Error - missing fields }*
```json
{
    "status": "fail",
    "message": "Please enter your email address.",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Validation Error - duplicate email }*
```json
{
    "status": "fail",
    "message": "This email address is already registered.",
    "data": null
}
```

**What happens next:** Call `POST /company/auth/verify` with the OTP `code` and the returned `verification_token` to complete registration.

---

#### Verify Account (OTP)

**What this does:** Confirms the OTP `code` against the `verification_token` from register/login and creates the `User` row (plus the role-specific profile). Verify creates the account (`status: pending`) but does **not** auto-login — no `token` is returned. Continue to admin approval (`POST /admin/companies/{id}/activate` in the Admin section) before attempting login.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/company/auth/verify` |
| **Route name** | `company.auth.verify` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email or Phone
- **Type:** `string`
- **Required:** required
- **Example:** `gulf.maintenance@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `code`
- **Description:** Verification Code
- **Type:** `string`
- **Required:** required
- **Example:** `123456`
- **Validation rules:** `required|string|size:6`
- `required` — must be provided.
- `string` — must be text.
- `size:6` — custom validation rule.
### `token`
- **Description:** Token
- **Type:** `string`
- **Required:** required
- **Example:** `<verification-token-from-previous-response>`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

**Example Request Body:**
```json
{
    "login": "acme.register@example.com",
    "code": "123456",
    "token": "a3df4b3a-5baa-481e-b43e-ebc857fe6151"
}
```

**Actual Success Response(s):**

*{ 200 OK — valid registration code (account created, pending admin activation) }*
```json
{
    "status": "success",
    "message": "Your account is awaiting admin approval. You will be notified once it is activated.",
    "data": null
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Error - invalid/expired token }*
```json
{
    "status": "fail",
    "message": "Expired or invalid verification code.",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Validation Error - missing code }*
```json
{
    "status": "fail",
    "message": "Please enter the verification code.",
    "data": null
}
```

**What happens next:** Individual clients can start using the API immediately (see Client section). Company/technician accounts must wait for admin approval before `POST /company/auth/login` will succeed.

---

#### Resend Verification Code

**What this does:** Resends the OTP code for an in-progress verification/reset flow, reusing the same `verification_token`.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/company/auth/resend-code` |
| **Route name** | `company.auth.resend` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `verification_token`
- **Description:** Token
- **Type:** `string`
- **Required:** required
- **Example:** `<verification-token-from-previous-response>`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

**Example Request Body:**
```json
{
    "verification_token": "d0f54e9c-b0c1-449f-bced-e2e3592d9eaf"
}
```

**Actual Success Response(s):**

*{ 200 OK — valid token }*
```json
{
    "status": "success",
    "message": "Verification code sent successfully",
    "data": {
        "verification_token": "d0f54e9c-b0c1-449f-bced-e2e3592d9eaf"
    }
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Error - invalid token }*
```json
{
    "status": "fail",
    "message": "Invalid or expired verification token.",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Validation Error - missing token }*
```json
{
    "status": "fail",
    "message": "The Token field is required.",
    "data": null
}
```

**What happens next:** Retry `POST /company/auth/verify` (or the relevant verify-reset-code endpoint) with the new code.

---

#### Login

**What this does:** Authenticates an existing, `active` company account by email or phone (`login` field, auto-detected) + `password`, returning a JWT `token`.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/company/auth/login` |
| **Route name** | `company.auth.login` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `403 Forbidden`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email
- **Type:** `string`
- **Required:** required
- **Example:** `gulf.maintenance@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `password`
- **Description:** Password
- **Type:** `string`
- **Required:** required
- **Example:** `password`
- **Validation rules:** `required|string|min:6`
- `required` — must be provided.
- `string` — must be text.
- `min:6` — must be at least 6.

> **Fixed:** an earlier generator pass captured this example against a throwaway factory-created user (`login.co@example.com`, id 113) that doesn't exist in the seeded database — it would 422 (`invalid credentials`) if actually sent. Replaced with a live capture against the real seeded company login.

**Example Request Body:**
```json
{
    "login": "gulf.maintenance@info.com",
    "password": "password"
}
```

**Actual Success Response(s):**

*{ 200 OK — valid credentials, live capture 2026-08-22 }*
```json
{
    "status": "success",
    "message": "You logged in successfully",
    "data": {
        "id": 10,
        "name": "Gulf Maintenance Group",
        "email": "gulf.maintenance@info.com",
        "phone": "966500000102",
        "user_type": "company",
        "user_type_label": "Company",
        "status": "active",
        "status_label": "Active",
        "is_active": true,
        "is_verified": true,
        "is_banned": false,
        "avatar": null,
        "settings": {
            "locale": "ar",
            "allow_notifications": true
        },
        "last_login_at": null,
        "created_at": "2026-08-22T19:08:32.000000Z",
        "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgxMjMvYXBpL3YxL2NvbXBhbnkvYXV0aC9sb2dpbiIsImlhdCI6MTc4NzQyODI2MCwiZXhwIjoxNzkyNjEyMjYwLCJuYmYiOjE3ODc0MjgyNjAsImp0aSI6IkxkdnRpdG1JaHp5MEZpM0MiLCJzdWIiOiIxMCIsInBydiI6IjIzYmQ1Yzg5NDlmNjAwYWRiMzllNzAxYzQwMDg3MmRiN2E1OTc2ZjcifQ.AoYcRl8H6BBIAxSbrz2nve656ZHqsIP6iGKqja7wIi4"
    }
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Error - wrong password }*
```json
{
    "status": "fail",
    "message": "Invalid email or password, please try again!",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Validation Error - missing password }*
```json
{
    "status": "fail",
    "message": "Please enter your password.",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Error - company account pending admin activation }*
```json
{
    "status": "fail",
    "message": "Your account is awaiting admin approval. You will be notified once it is activated.",
    "data": null
}
```

*{ 403 Forbidden — 403 Error - non-company account rejected }*
```json
{
    "status": "fail",
    "message": "Access denied. Company account required.",
    "data": null
}
```

**What happens next:** Use the returned `data.token` as `Authorization: Bearer <token>` on every subsequent authenticated call in this role's sections.

---

#### Request Password Reset

**What this does:** Starts password reset — sends an OTP to the account's email/phone and returns a `verification_token` identifying this reset session.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/company/auth/forgot-password` |
| **Route name** | `company.auth.forgot` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email or Phone
- **Type:** `string`
- **Required:** required
- **Example:** `gulf.maintenance@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

> **Fixed:** an earlier generator pass captured this whole 3-step flow (this endpoint, `verify-reset-code`, `reset-password`) against `reset.co@example.com`, a throwaway factory-created user that doesn't exist in the seeded database — `forgotPassword()` throws for an unknown login, so it would 422 if actually sent. Replaced across all 3 steps with a live capture against `gulf.maintenance@info.com` (the account's password was reset back to `password` immediately after capture, so every other example in these docs that logs in as gulf.maintenance still works).

**Example Request Body:**
```json
{
    "login": "gulf.maintenance@info.com"
}
```

**Actual Success Response(s):**

*{ 200 OK — known login, live capture 2026-08-22 }*
```json
{
    "status": "success",
    "message": "Verification code sent successfully",
    "data": {
        "verification_token": "80673f93-fa84-4297-8886-60619feb2018"
    }
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Validation Error - missing login }*
```json
{
    "status": "fail",
    "message": "Please enter your email or phone number.",
    "data": null
}
```

**What happens next:** Call `POST /company/auth/verify-reset-code` with the OTP to confirm it, then `POST /company/auth/reset-password`.

---

#### Verify Reset Code

**What this does:** Confirms the password-reset OTP without changing the password yet.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/company/auth/verify-reset-code` |
| **Route name** | `company.auth.verify-reset-code` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email or Phone
- **Type:** `string`
- **Required:** required
- **Example:** `gulf.maintenance@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `code`
- **Description:** Verification Code
- **Type:** `string`
- **Required:** required
- **Example:** `123456`
- **Validation rules:** `required|string|size:6`
- `required` — must be provided.
- `string` — must be text.
- `size:6` — custom validation rule.
### `token`
- **Description:** Token
- **Type:** `string`
- **Required:** required
- **Example:** `<verification-token-from-previous-response>`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

**Example Request Body:**
```json
{
    "login": "gulf.maintenance@info.com",
    "code": "123456",
    "token": "80673f93-fa84-4297-8886-60619feb2018"
}
```

**Actual Success Response(s):**

*{ 200 OK — valid code, live capture 2026-08-22 }*
```json
{
    "status": "success",
    "message": "Code verified. You can now set a new password.",
    "data": null
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Error - invalid code (already verified/consumed) }*
```json
{
    "status": "fail",
    "message": "Verification code has already been verified.",
    "data": null
}
```

**What happens next:** Call `POST /company/auth/reset-password` with the same token and a new password.

---

#### Reset Password

**What this does:** Sets a new password using the already-verified reset token from `verify-reset-code`.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/company/auth/reset-password` |
| **Route name** | `company.auth.reset` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email or Phone
- **Type:** `string`
- **Required:** required
- **Example:** `gulf.maintenance@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `token`
- **Description:** Token
- **Type:** `string`
- **Required:** required
- **Example:** `<verification-token-from-previous-response>`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `password`
- **Description:** New Password
- **Type:** `string`
- **Required:** required
- **Example:** `Password123!`
- **Validation rules:** `required|string|min:8|confirmed`
- `required` — must be provided.
- `string` — must be text.
- `min:8` — must be at least 8.
- `confirmed` — requires a matching *_confirmation field.
### `password_confirmation`
- **Description:** Password confirmation
- **Type:** `string`
- **Required:** required
- **Example:** `Password123!`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

**Example Request Body:**
```json
{
    "login": "gulf.maintenance@info.com",
    "token": "80673f93-fa84-4297-8886-60619feb2018",
    "password": "newpassword123",
    "password_confirmation": "newpassword123"
}
```

**Actual Success Response(s):**

*{ 200 OK — valid verified token, live capture 2026-08-22 }*
```json
{
    "status": "success",
    "message": "Password reset successfully.",
    "data": null
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Validation Error - password confirmation mismatch }*
```json
{
    "status": "fail",
    "message": "Password confirmation does not match.",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Error - code not verified yet (business rule) }*
```json
{
    "status": "fail",
    "message": "Please verify the code before continuing.",
    "data": null
}
```

**What happens next:** Log in again with `POST /company/auth/login` using the new password.

---

## 03. Technician Auth (Register/Login/Verify/Reset)

#### Register New Account

**What this does:** Starts technician registration — caches the submitted profile + password under a short-lived pending-registration token and sends an OTP to the given email/phone. A technician account starts `status: pending` and cannot log in until an admin approves it. No `users` row is created yet.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/technician/auth/register` |
| **Route name** | `technician.auth.register` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `201 Created`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `full_name`
- **Description:** Full Name
- **Type:** `string`
- **Required:** required
- **Example:** `Example Name`
- **Validation rules:** `required|string|max:100`
- `required` — must be provided.
- `string` — must be text.
- `max:100` — must not exceed 100.
### `email`
- **Description:** Email
- **Type:** `string`
- **Required:** required
- **Example:** `user@example.com`
- **Validation rules:** `required|email|unique:users,email`
- `required` — must be provided.
- `email` — must be a valid email address.
- `unique:users,email` — must be unique (users, email).
### `phone`
- **Description:** Phone
- **Type:** `string`
- **Required:** required
- **Example:** `1234567890`
- **Validation rules:** `required|string|max:20|unique:users,phone`
- `required` — must be provided.
- `string` — must be text.
- `max:20` — must not exceed 20.
- `unique:users,phone` — must be unique (users, phone).
### `phone_code`
- **Description:** Phone Code
- **Type:** `string`
- **Required:** optional
- **Example:** `20`
- **Validation rules:** `nullable|string|max:5`
- `nullable` — may be null.
- `string` — must be text.
- `max:5` — must not exceed 5.
### `id_number`
- **Description:** ID Number
- **Type:** `string`
- **Required:** optional
- **Example:** `1234567890`
- **Validation rules:** `nullable|string|max:50`
- `nullable` — may be null.
- `string` — must be text.
- `max:50` — must not exceed 50.
### `address`
- **Description:** Address
- **Type:** `string`
- **Required:** optional
- **Example:** `123 Main Street, Building A`
- **Validation rules:** `nullable|string|max:255`
- `nullable` — may be null.
- `string` — must be text.
- `max:255` — must not exceed 255.
### `country_id`
- **Description:** Country
- **Type:** `integer`
- **Required:** optional
- **Example:** `1`
- **Validation rules:** `nullable|integer|exists:countries,id`
- `nullable` — may be null.
- `integer` — must be a whole number.
- `exists:countries,id` — must reference an existing record (countries, id).
### `governorate_id`
- **Description:** City
- **Type:** `integer`
- **Required:** optional
- **Example:** `1`
- **Validation rules:** `nullable|integer|exists:governorates,id`
- `nullable` — may be null.
- `integer` — must be a whole number.
- `exists:governorates,id` — must reference an existing record (governorates, id).
### `region_id`
- **Description:** District
- **Type:** `integer`
- **Required:** optional
- **Example:** `1`
- **Validation rules:** `nullable|integer|exists:regions,id`
- `nullable` — may be null.
- `integer` — must be a whole number.
- `exists:regions,id` — must reference an existing record (regions, id).
### `password`
- **Description:** Password
- **Type:** `string`
- **Required:** required
- **Example:** `Password123!`
- **Validation rules:** `required|string|min:8|confirmed`
- `required` — must be provided.
- `string` — must be text.
- `min:8` — must be at least 8.
- `confirmed` — requires a matching *_confirmation field.
### `category_id`
- **Description:** Service Category
- **Type:** `integer`
- **Required:** required
- **Example:** `2`
- **Validation rules:** `required|integer|exists:categories,id`
- `required` — must be provided.
- `integer` — must be a whole number.
- `exists:categories,id` — must reference an existing record (categories, id).
### `region_ids`
- **Description:** Coverage Areas
- **Type:** `array`
- **Required:** optional
- **Example:** `[1]`
- **Validation rules:** `nullable|array`
- `nullable` — may be null.
- `array` — must be an array.
### `region_ids.*`
- **Description:** Region ids item
- **Type:** `integer`
- **Required:** optional
- **Example:** `1`
- **Validation rules:** `integer|exists:regions,id`
- `integer` — must be a whole number.
- `exists:regions,id` — must reference an existing record (regions, id).
### `years_experience`
- **Description:** Years of Experience
- **Type:** `integer`
- **Required:** optional
- **Example:** `1`
- **Validation rules:** `nullable|integer|min:0|max:50`
- `nullable` — may be null.
- `integer` — must be a whole number.
- `min:0` — must be at least .
- `max:50` — must not exceed 50.
### `daily_order_capacity`
- **Description:** Daily Order Capacity
- **Type:** `integer`
- **Required:** optional
- **Example:** `1`
- **Validation rules:** `nullable|integer|min:1|max:50`
- `nullable` — may be null.
- `integer` — must be a whole number.
- `min:1` — must be at least 1.
- `max:50` — must not exceed 50.
### `personal_photo`
- **Description:** Personal Photo
- **Type:** `integer`
- **Required:** optional
- **Example:** `1`
- **Validation rules:** `nullable|integer|exists:media,id`
- `nullable` — may be null.
- `integer` — must be a whole number.
- `exists:media,id` — must reference an existing record (media, id).
### `national_id_front`
- **Description:** National ID (Front)
- **Type:** `integer`
- **Required:** optional
- **Example:** `1`
- **Validation rules:** `nullable|integer|exists:media,id`
- `nullable` — may be null.
- `integer` — must be a whole number.
- `exists:media,id` — must reference an existing record (media, id).
### `national_id_back`
- **Description:** National ID (Back)
- **Type:** `integer`
- **Required:** optional
- **Example:** `1`
- **Validation rules:** `nullable|integer|exists:media,id`
- `nullable` — may be null.
- `integer` — must be a whole number.
- `exists:media,id` — must reference an existing record (media, id).
### `cv_file`
- **Description:** CV/Resume
- **Type:** `integer`
- **Required:** optional
- **Example:** `1`
- **Validation rules:** `nullable|integer|exists:media,id`
- `nullable` — may be null.
- `integer` — must be a whole number.
- `exists:media,id` — must reference an existing record (media, id).
### `device_id`
- **Description:** Device id
- **Type:** `integer`
- **Required:** optional
- **Example:** `device-uuid-123`
- **Validation rules:** `nullable|string|max:255`
- `nullable` — may be null.
- `string` — must be text.
- `max:255` — must not exceed 255.
### `device_token`
- **Description:** Device token
- **Type:** `string`
- **Required:** optional
- **Example:** `fcm-token-here`
- **Validation rules:** `nullable|string|max:500`
- `nullable` — may be null.
- `string` — must be text.
- `max:500` — must not exceed 500.
### `device_type`
- **Description:** Device type
- **Type:** `string`
- **Required:** optional
- **Example:** `ios`
- **Validation rules:** `nullable|in:ios,android,web,huawei`
- `nullable` — may be null.
- `in:ios,android,web,huawei` — must be one of: ios, android, web, huawei.

**Example Request Body:**
```json
{
    "full_name": "Ahmed Ali",
    "email": "ahmed.tech@example.com",
    "phone": "0501234567",
    "phone_code": "966",
    "id_number": "1234567890",
    "address": "123 Main St",
    "country_id": 1,
    "governorate_id": 1,
    "region_id": 1,
    "password": "password123",
    "password_confirmation": "password123",
    "category_id": 2,
    "region_ids": [
        1
    ],
    "years_experience": 5,
    "daily_order_capacity": 5
}
```

**Actual Success Response(s):**

*{ 201 Created — valid registration }*
```json
{
    "status": "success",
    "message": "A verification OTP has been sent to your email. Please verify your account to continue.",
    "data": {
        "verification_token": "0e7c4554-a2f3-4374-9f02-aa14342fdb39"
    }
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Validation Error - missing fields }*
```json
{
    "status": "fail",
    "message": "Please enter your full name.",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Validation Error - duplicate email }*
```json
{
    "status": "fail",
    "message": "This email address is already registered.",
    "data": null
}
```

**What happens next:** Call `POST /technician/auth/verify` with the OTP `code` and the returned `verification_token` to complete registration.

---

#### Verify Account (OTP)

**What this does:** Confirms the OTP `code` against the `verification_token` from register/login and creates the `User` row (plus the role-specific profile). Verify creates the account (`status: pending`) but does **not** auto-login — no `token` is returned. Continue to admin approval (`POST /admin/users/{id}/activate` in the Admin section) before attempting login.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/technician/auth/verify` |
| **Route name** | `technician.auth.verify` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email or Phone
- **Type:** `string`
- **Required:** required
- **Example:** `technician@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `code`
- **Description:** Verification Code
- **Type:** `string`
- **Required:** required
- **Example:** `123456`
- **Validation rules:** `required|string|size:6`
- `required` — must be provided.
- `string` — must be text.
- `size:6` — custom validation rule.
### `token`
- **Description:** Token
- **Type:** `string`
- **Required:** required
- **Example:** `<verification-token-from-previous-response>`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

**Example Request Body:**
```json
{
    "login": "sara.tech@example.com",
    "code": "123456",
    "token": "5e64afaf-b2e5-423b-9ef7-29935ec41187"
}
```

**Actual Success Response(s):**

*{ 200 OK — valid code }*
```json
{
    "status": "success",
    "message": "Your account is pending activation. Please verify your account.",
    "data": null
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Validation Error - code already used }*
```json
{
    "status": "fail",
    "message": "Verification code has already been verified.",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Validation Error - wrong code }*
```json
{
    "status": "fail",
    "message": "Invalid verification code.",
    "data": null
}
```

**What happens next:** Individual clients can start using the API immediately (see Client section). Company/technician accounts must wait for admin approval before `POST /technician/auth/login` will succeed.

---

#### Resend Verification Code

**What this does:** Resends the OTP code for an in-progress verification/reset flow, reusing the same `verification_token`.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/technician/auth/resend-code` |
| **Route name** | `technician.auth.resend` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `verification_token`
- **Description:** Token
- **Type:** `string`
- **Required:** required
- **Example:** `<verification-token-from-previous-response>`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

**Example Request Body:**
```json
{
    "verification_token": "5e64afaf-b2e5-423b-9ef7-29935ec41187"
}
```

**Actual Success Response(s):**

*{ 200 OK — resend code }*
```json
{
    "status": "success",
    "message": "Verification code sent successfully",
    "data": {
        "verification_token": "5e64afaf-b2e5-423b-9ef7-29935ec41187"
    }
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Validation Error - invalid token }*
```json
{
    "status": "fail",
    "message": "Invalid or expired verification token.",
    "data": null
}
```

**What happens next:** Retry `POST /technician/auth/verify` (or the relevant verify-reset-code endpoint) with the new code.

---

#### Login

**What this does:** Authenticates an existing, `active` technician account by email or phone (`login` field, auto-detected) + `password`, returning a JWT `token`.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/technician/auth/login` |
| **Route name** | `technician.auth.login` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `403 Forbidden`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email
- **Type:** `string`
- **Required:** required
- **Example:** `technician@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `password`
- **Description:** Password
- **Type:** `string`
- **Required:** required
- **Example:** `password`
- **Validation rules:** `required|string|min:6`
- `required` — must be provided.
- `string` — must be text.
- `min:6` — must be at least 6.

> **Fixed:** an earlier generator pass captured this example against a throwaway factory-created user (`login.tech@example.com`, id 207) that doesn't exist in the seeded database — it would 422 (`invalid credentials`) if actually sent. Replaced with a live capture against the real seeded technician login.

**Example Request Body:**
```json
{
    "login": "technician@info.com",
    "password": "password"
}
```

**Actual Success Response(s):**

*{ 200 OK — valid credentials, live capture 2026-08-22 }*
```json
{
    "status": "success",
    "message": "You logged in successfully",
    "data": {
        "id": 5,
        "name": "Technician User",
        "email": "technician@info.com",
        "phone": "966500000005",
        "user_type": "technician",
        "user_type_label": "Technician",
        "status": "active",
        "status_label": "Active",
        "is_active": true,
        "is_verified": true,
        "is_banned": false,
        "avatar": null,
        "settings": {
            "locale": "ar",
            "allow_notifications": true
        },
        "last_login_at": null,
        "created_at": "2026-08-22T19:08:30.000000Z",
        "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgxMjMvYXBpL3YxL3RlY2huaWNpYW4vYXV0aC9sb2dpbiIsImlhdCI6MTc4NzQyODI2MSwiZXhwIjoxNzkyNjEyMjYxLCJuYmYiOjE3ODc0MjgyNjEsImp0aSI6IlVISHIzNFNSNUhkalRMd2wiLCJzdWIiOiI1IiwicHJ2IjoiMjNiZDVjODk0OWY2MDBhZGIzOWU3MDFjNDAwODcyZGI3YTU5NzZmNyJ9.odjRZ6Smmnoz7CM4xO32zhdIqjKvoOM4WJMJndJCxeA"
    }
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Validation Error - wrong password }*
```json
{
    "status": "fail",
    "message": "Invalid email or password, please try again!",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Validation Error - missing password }*
```json
{
    "status": "fail",
    "message": "Please enter your password.",
    "data": null
}
```

*{ 403 Forbidden — non-technician account }*
```json
{
    "status": "fail",
    "message": "Access denied. Technician account required.",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Validation Error - pending account }*
```json
{
    "status": "fail",
    "message": "Your account is pending activation. Please verify your account.",
    "data": null
}
```

**What happens next:** Use the returned `data.token` as `Authorization: Bearer <token>` on every subsequent authenticated call in this role's sections.

---

#### Request Password Reset

**What this does:** Starts password reset — sends an OTP to the account's email/phone and returns a `verification_token` identifying this reset session.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/technician/auth/forgot-password` |
| **Route name** | `technician.auth.forgot` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email or Phone
- **Type:** `string`
- **Required:** required
- **Example:** `technician@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

> **Fixed:** an earlier generator pass captured this whole 3-step flow (this endpoint, `verify-reset-code`, `reset-password`) against `reset.tech@example.com`, a throwaway factory-created user that doesn't exist in the seeded database — `forgotPassword()` throws for an unknown login, so it would 422 if actually sent. Replaced across all 3 steps with a live capture against `technician@info.com` (the account's password was reset back to `password` immediately after capture, so every other example in these docs that logs in as this technician still works).

**Example Request Body:**
```json
{
    "login": "technician@info.com"
}
```

**Actual Success Response(s):**

*{ 200 OK — reset code sent, live capture 2026-08-22 }*
```json
{
    "status": "success",
    "message": "Verification code sent successfully",
    "data": {
        "verification_token": "f76fb2ab-54d1-4c53-8498-8262ffd04532"
    }
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Validation Error - missing login }*
```json
{
    "status": "fail",
    "message": "Please enter your email or phone number.",
    "data": null
}
```

**What happens next:** Call `POST /technician/auth/verify-reset-code` with the OTP to confirm it, then `POST /technician/auth/reset-password`.

---

#### Verify Reset Code

**What this does:** Confirms the password-reset OTP without changing the password yet.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/technician/auth/verify-reset-code` |
| **Route name** | `technician.auth.verify-reset-code` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email or Phone
- **Type:** `string`
- **Required:** required
- **Example:** `technician@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `code`
- **Description:** Verification Code
- **Type:** `string`
- **Required:** required
- **Example:** `123456`
- **Validation rules:** `required|string|size:6`
- `required` — must be provided.
- `string` — must be text.
- `size:6` — custom validation rule.
### `token`
- **Description:** Token
- **Type:** `string`
- **Required:** required
- **Example:** `<verification-token-from-previous-response>`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

**Example Request Body:**
```json
{
    "login": "technician@info.com",
    "code": "123456",
    "token": "f76fb2ab-54d1-4c53-8498-8262ffd04532"
}
```

**Actual Success Response(s):**

*{ 200 OK — valid code, live capture 2026-08-22 }*
```json
{
    "status": "success",
    "message": "Code verified. You can now set a new password.",
    "data": null
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Validation Error - wrong code }*
```json
{
    "status": "fail",
    "message": "Invalid verification code.",
    "data": null
}
```

**What happens next:** Call `POST /technician/auth/reset-password` with the same token and a new password.

---

#### Reset Password

**What this does:** Sets a new password using the already-verified reset token from `verify-reset-code`.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/technician/auth/reset-password` |
| **Route name** | `technician.auth.reset` |
| **Guard** | None — public endpoint |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `422 Unprocessable Entity` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email or Phone
- **Type:** `string`
- **Required:** required
- **Example:** `technician@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `token`
- **Description:** Token
- **Type:** `string`
- **Required:** required
- **Example:** `<verification-token-from-previous-response>`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `password`
- **Description:** New Password
- **Type:** `string`
- **Required:** required
- **Example:** `Password123!`
- **Validation rules:** `required|string|min:8|confirmed`
- `required` — must be provided.
- `string` — must be text.
- `min:8` — must be at least 8.
- `confirmed` — requires a matching *_confirmation field.
### `password_confirmation`
- **Description:** Password confirmation
- **Type:** `string`
- **Required:** required
- **Example:** `Password123!`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.

**Example Request Body:**
```json
{
    "login": "technician@info.com",
    "token": "f76fb2ab-54d1-4c53-8498-8262ffd04532",
    "password": "newpassword123",
    "password_confirmation": "newpassword123"
}
```

**Actual Success Response(s):**

*{ 200 OK — password reset, live capture 2026-08-22 }*
```json
{
    "status": "success",
    "message": "Password reset successfully.",
    "data": null
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Validation Error - token never verified }*
```json
{
    "status": "fail",
    "message": "Invalid or expired verification token.",
    "data": null
}
```

**What happens next:** Log in again with `POST /technician/auth/login` using the new password.

---

## 04. Admin Login

#### Login

**What this does:** Authenticates an admin/super_admin account. Double rate-limited: 5 attempts/60s per IP (`admin.login_throttle` middleware) **and** 5 attempts/60s per submitted login string (service-level) — both must pass.

| | |
|---|---|
| **Method** | `POST` |
| **Endpoint** | `{{base_url}}/api/v1/admin/auth/login` |
| **Route name** | `admin.auth.login` |
| **Guard** | None — public endpoint; rate-limited 5 attempts/60s per IP (`admin.login_throttle`) |
| **Allowed roles** | Public — no authentication required |
| **Status codes** | `200 OK`, `403 Forbidden`, `422 Unprocessable Entity`, `429 Too Many Requests` |

**Headers:**
- `Accept: application/json`
- `Content-Type: application/json`
- `Accept-Language: {{locale}}`

**Payload:**
Send as `application/json`.

**Field documentation:**
### `login`
- **Description:** Email
- **Type:** `string`
- **Required:** required
- **Example:** `admin@info.com`
- **Validation rules:** `required|string`
- `required` — must be provided.
- `string` — must be text.
### `password`
- **Description:** Password
- **Type:** `string`
- **Required:** required
- **Example:** `password`
- **Validation rules:** `required|string|min:6`
- `required` — must be provided.
- `string` — must be text.
- `min:6` — must be at least 6.

> **Fixed:** an earlier generator pass captured this example against a throwaway factory-created user (`admin.core@example.com`, id 87) that doesn't exist in the seeded database — it would 422 (`invalid credentials`) if actually sent. Replaced with a live capture against `admin@info.com`, `database/seeders/UserSeeder.php`'s super_admin account — the account this documentation standardizes on throughout (see [README.md](README.md#seeded-test-credentials)). `database/seeders/AdminSeeder.php`'s `admin@maintenance.sa` and `UserSeeder.php`'s `admin2@info.com` also work with the same `password`, but aren't the one used here.

**Example Request Body:**
```json
{
    "login": "admin@info.com",
    "password": "password"
}
```

**Actual Success Response(s):**

*{ 200 OK — valid credentials, live capture 2026-08-22 }*
```json
{
    "status": "success",
    "message": "Login successful.",
    "data": {
        "id": 1,
        "name": "Super Admin",
        "email": "admin@info.com",
        "phone": "966500000001",
        "user_type": "super_admin",
        "user_type_label": "Super Admin",
        "status": "active",
        "status_label": "Active",
        "is_active": true,
        "is_verified": true,
        "is_banned": false,
        "avatar": null,
        "settings": {
            "locale": "ar",
            "allow_notifications": true
        },
        "last_login_at": "2026-08-22T20:02:56.000000Z",
        "created_at": "2026-08-22T19:08:29.000000Z",
        "roles": [
            {
                "id": 1,
                "name": "Super Admin"
            }
        ],
        "permissions": [
            "operational-reports-view"
        ],
        "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgxMjMvYXBpL3YxL2FkbWluL2F1dGgvbG9naW4iLCJpYXQiOjE3ODc0Mjg5NzYsImV4cCI6MTc5MjYxMjk3NiwibmJmIjoxNzg3NDI4OTc2LCJqdGkiOiJDMDQ1aUVmYTZ6R1FibjlUIiwic3ViIjoiMSIsInBydiI6IjIzYmQ1Yzg5NDlmNjAwYWRiMzllNzAxYzQwMDg3MmRiN2E1OTc2ZjcifQ.xCto4LrYd5dC2vQSkAuUrgvIUqaOzHiRPfOh4hWK7O4"
    }
}
```

**Actual Error Response(s):**

*{ 422 Unprocessable Entity — 422 Unprocessable - wrong password }*
```json
{
    "status": "fail",
    "message": "Invalid credentials. Please check your email/phone and password.",
    "data": null
}
```

*{ 422 Unprocessable Entity — 422 Validation Error - missing password }*
```json
{
    "status": "fail",
    "message": "Please enter your password.",
    "data": null
}
```

*{ 403 Forbidden — non-admin user type }*
```json
{
    "status": "fail",
    "message": "Access denied. Admin account required.",
    "data": null
}
```

*{ 429 Too Many Requests — IP throttle exceeded }*
```json
{
    "status": "fail",
    "message": "Too many login attempts from this IP. Please try again in 60 seconds.",
    "data": null
}
```

**What happens next:** Use the returned `token` as bearer auth for every admin endpoint. In `staging`/`production` (not `local`/`testing`), every subsequent admin call is additionally checked against the resolved RBAC permission — see the Authentication section.

---
