# Merchant Users

Manage merchant user accounts (the merchant's team) and their access.

Team members are user accounts in the auth service, managed under `/api/v1/users`. A member belongs to a merchant through its `merchantId` (or an additional merchant link). All endpoints require `Authorization: Bearer <JWT>`; the tenant comes from the token.

- Tenant admins can manage users of any merchant in the tenant.
- Merchant users only see and manage members of their own (active) merchant, and can only invite into a role below their own (an Admin invites Managers, Staff and custom roles, never another Admin or an Owner).
- A merchant can have only one Merchant Owner.

---

## Create User

```http
POST /api/v1/users
```

Creates a team member and sends an invitation email with a password-setup link. The member stays **Pending** until the link is used.

```bash
curl -X POST https://uat.auxcore.net/api/v1/users \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "TempPassw0rd!",
    "firstName": "Sam",
    "lastName": "Rivera",
    "role": "merchant_staff",
    "merchantId": "8f3c2a1e-5b7d-4e9a-9c1f-2d6e8a4b7c30",
    "branchIds": ["3d1f8e2a-7c4b-4a9e-8f2d-1b6c9e0a5d47"],
    "sendInvite": true
  }'
```

**Request Body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `email` | email | Yes | Must be a valid email address |
| `password` | string | Yes | Initial password (the member normally sets their own through the invite link) |
| `merchantId` | uuid | No | Merchant the member joins. Required to add someone to a merchant team. |
| `role` | string | No | Role code, e.g. `merchant_owner`, `merchant_admin`, `merchant_staff`, or a custom role. Default `user`. |
| `firstName`, `lastName` | string | No | `fullName` is composed from these |
| `fullName` | string | No | Used when first/last name are not sent |
| `jobTitle` | string | No | |
| `phone` | string | No | `phoneNumber` is accepted as an alias |
| `branchIds` | uuid[] | No | Locations the member is assigned to. Empty or omitted means all locations. |
| `mfaRequired` | boolean | No | Require MFA at login |
| `sendInvite` | boolean | No | Default `true`. `false` creates the Pending member without emailing; send the invite later. |
| `permissionOverrides` | object | No | `{ "granted": string[], "revoked": string[] }` — per-member changes on top of the role. Needs `users.manage`; not allowed for a Merchant Owner. See [User Permissions](./permissions.md). |

**Response (201 Created):**

```json
{
  "success": true,
  "data": {
    "id": "c2b7e4a9-1d3f-4e8b-9a6c-5f0d2e7b8a13",
    "email": "user@example.com",
    "fullName": "Sam Rivera",
    "role": "merchant_staff",
    "merchantId": "8f3c2a1e-5b7d-4e9a-9c1f-2d6e8a4b7c30",
    "isActive": true,
    "mfaEnabled": false,
    "invitedAt": "2026-01-28T12:00:00.000Z",
    "inviteAcceptedAt": null
  }
}
```

If the email already belongs to a merchant-side account in this tenant, that account is **linked** to the merchant instead and the response is `200 OK` with `"message": "User linked to merchant successfully"`.

**Errors:** `400 VALIDATION_ERROR` (missing email/password, invalid email, `User with this email already exists` when no `merchantId` is sent, `User already exists for this merchant`, platform account email), `403 FORBIDDEN` (role you may not grant, second Owner), `409 EMAIL_IN_USE`.

---

## List Users

```http
GET /api/v1/users
```

Requires `users.view`.

| Query | Description |
|-------|-------------|
| `merchantId` | Admins only: limit to one merchant's team (primary members and linked members) |
| `scope` | Admins only: `platform` returns internal staff instead of merchant users |

Merchant users always get their own merchant's team, regardless of the query.

**Response:**

```json
{
  "success": true,
  "data": [
    {
      "id": "c2b7e4a9-1d3f-4e8b-9a6c-5f0d2e7b8a13",
      "email": "user@example.com",
      "fullName": "Sam Rivera",
      "role": "merchant_staff",
      "merchantId": "8f3c2a1e-5b7d-4e9a-9c1f-2d6e8a4b7c30",
      "isActive": true
    }
  ]
}
```

### List a Merchant's Team

```http
GET /api/v1/merchants/:merchantId/users
```

Returns the same team list for one merchant, served by the merchant service. Items carry a shorter projection: `id`, `email`, `fullName`, `role`, `merchantId`, `isActive`, `mfaEnabled`, `createdAt`, `updatedAt`.

---

## Get User

```http
GET /api/v1/users/:userId
```

Requires `users.view`. Returns `404` if the user does not exist in the tenant.

---

## Update User

```http
PUT /api/v1/users/:userId
```

All fields are optional: `firstName`, `lastName`, `fullName`, `jobTitle`, `phone`, `email`, `role`, `isActive`, `mfaEnabled`, `mfaRequired`, `branchIds`, `permissionOverrides`.

```bash
curl -X PUT https://uat.auxcore.net/api/v1/users/c2b7e4a9-1d3f-4e8b-9a6c-5f0d2e7b8a13 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"role": "merchant_admin"}'
```

- Users can edit their own profile but cannot change their own role or permissions.
- Only admins, or merchant admins for members of their own merchant, can change `role`, `isActive`, `branchIds` and `mfaRequired`.
- Merchant users cannot assign platform admin roles.

---

## Activate / Deactivate User

```http
PATCH /api/v1/users/:userId/status
```

```json
{ "isActive": false }
```

Same rules as Update User.

---

## Resend Invitation

```http
POST /api/v1/users/:userId/send-invite
```

Emails a fresh password-setup link. `POST /api/v1/users/:userId/invite-link` returns the link instead of emailing it.

---

## Delete User

```http
DELETE /api/v1/users/:userId
```

Soft-deletes the user. When a merchant user removes a member who also belongs to other merchants, the member is only removed from the caller's merchant (`"message": "User removed from this merchant successfully"`); the account itself stays. You cannot delete your own account or the protected Super Admin, and only a tenant admin can remove a merchant's Owner (transfer ownership first). Merchant admins cannot remove a peer of the same tier.

---

**Need help?** Contact support@auxcore.net
