> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zyeta.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Users Service API

> API reference for managing users and organization memberships

The Users Service provides endpoints for viewing user information, inviting users to organizations, and managing user roles within organizations.

## Authentication

All endpoints require a valid Bearer token in the Authorization header.

## Base URL

```
/api/users
```

## Endpoints Overview

| Endpoint            | Method | Description                   | Required Permission        |
| ------------------- | ------ | ----------------------------- | -------------------------- |
| `/api/users/me`     | GET    | Get current user's details    | Authenticated user         |
| `/api/users/list`   | GET    | List users in an organization | `users:read`               |
| `/api/users/invite` | POST   | Invite a user to organization | `users:write`              |
| `/api/users/update` | PUT    | Update a user's role          | `users:write` + owner role |

## User Endpoints

### Get Current User

Get the authenticated user's details including all organization memberships.

<CodeGroup>
  ```bash Request theme={null}
  curl -X GET "{{baseUrl}}/api/users/me" \
    -H "Authorization: Bearer YOUR_TOKEN"
  ```

  ```python Python theme={null}
  import requests

  url = "{{baseUrl}}/api/users/me"
  headers = {
      "Authorization": "Bearer YOUR_TOKEN"
  }

  response = requests.get(url, headers=headers)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const axios = require('axios');

  const url = "{{baseUrl}}/api/users/me";
  const headers = {
      Authorization: "Bearer YOUR_TOKEN"
  };

  axios.get(url, { headers })
      .then(response => console.log(response.data))
      .catch(error => console.error(error));
  ```

  ```json Response theme={null}
  {
    "id": "a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6",
    "email": "john.doe@example.com",
    "first_name": "John",
    "last_name": "Doe",
    "full_name": "John Doe",
    "organizations": [
      {
        "id": "d07897e2-1ddf-4f11-9434-5fb6f2f5c20d",
        "name": "Acme Corporation",
        "slug": "acme-corp-abc123",
        "role_id": "8c9d0e1f-2a3b-4c5d-6e7f-8g9h0i1j2k3l",
        "role_name": "owner"
      },
      {
        "id": "e18908f3-2eef-5f22-0545-6fc7f3f6d31e",
        "name": "Personal Workspace",
        "slug": "personal-xyz789",
        "role_id": "9d0e1f2a-3b4c-5d6e-7f8g-9h0i1j2k3l4m",
        "role_name": "admin"
      }
    ]
  }
  ```
</CodeGroup>

**Endpoint:** `GET /api/users/me`

**Response:**

| Field                       | Type          | Description                               |
| --------------------------- | ------------- | ----------------------------------------- |
| `id`                        | string (UUID) | User's unique identifier                  |
| `email`                     | string        | User's email address                      |
| `first_name`                | string        | User's first name                         |
| `last_name`                 | string        | User's last name                          |
| `full_name`                 | string        | User's full name                          |
| `organizations`             | array         | List of organizations the user belongs to |
| `organizations[].id`        | string (UUID) | Organization ID                           |
| `organizations[].name`      | string        | Organization name                         |
| `organizations[].slug`      | string        | Organization URL slug                     |
| `organizations[].role_id`   | string (UUID) | User's role ID in this organization       |
| `organizations[].role_name` | string        | User's role name in this organization     |

<Note>
  Returns only **active** organization memberships. Invited, suspended, or deleted memberships are not included.
</Note>

### List Users in Organization

Get a paginated list of users in an organization, including both active users and pending invitations.

<CodeGroup>
  ```bash Request theme={null}
  curl -X GET "{{baseUrl}}/api/users/list?org_id=your-org-id&offset=0&limit=10" \
    -H "Authorization: Bearer YOUR_TOKEN"
  ```

  ```python Python theme={null}
  import requests

  url = "{{baseUrl}}/api/users/list"
  headers = {
      "Authorization": "Bearer YOUR_TOKEN"
  }
  params = {
      "org_id": "your-org-id",
      "offset": 0,
      "limit": 10
  }

  response = requests.get(url, headers=headers, params=params)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const axios = require('axios');

  const url = "{{baseUrl}}/api/users/list";
  const headers = {
      Authorization: "Bearer YOUR_TOKEN"
  };
  const params = {
      org_id: "your-org-id",
      offset: 0,
      limit: 10
  };

  axios.get(url, { headers, params })
      .then(response => console.log(response.data))
      .catch(error => console.error(error));
  ```

  ```json Response theme={null}
  {
    "users": [
      {
        "id": "a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6",
        "email": "john.doe@example.com",
        "first_name": "John",
        "last_name": "Doe",
        "full_name": "John Doe",
        "status": "active",
        "invite_id": null,
        "invited_at": null,
        "organizations": [
          {
            "id": "d07897e2-1ddf-4f11-9434-5fb6f2f5c20d",
            "name": "Acme Corporation",
            "slug": "acme-corp-abc123",
            "role_id": "8c9d0e1f-2a3b-4c5d-6e7f-8g9h0i1j2k3l",
            "role_name": "owner"
          }
        ]
      },
      {
        "id": null,
        "email": "jane.smith@example.com",
        "first_name": "Jane",
        "last_name": "Smith",
        "full_name": "Jane Smith",
        "status": "pending",
        "invite_id": "b2c3d4e5-f6g7-h8i9-j0k1-l2m3n4o5p6q7",
        "invited_at": "2023-11-01T10:30:00Z",
        "organizations": [
          {
            "id": "d07897e2-1ddf-4f11-9434-5fb6f2f5c20d",
            "name": "Acme Corporation",
            "slug": "acme-corp-abc123",
            "role_id": "9d0e1f2a-3b4c-5d6e-7f8g-9h0i1j2k3l4m",
            "role_name": "admin"
          }
        ]
      }
    ],
    "total": 15
  }
  ```
</CodeGroup>

**Endpoint:** `GET /api/users/list`

**Query Parameters:**

| Parameter | Required | Description                       |
| --------- | -------- | --------------------------------- |
| `org_id`  | Yes      | Organization ID                   |
| `offset`  | No       | Page number (0-based, default: 0) |
| `limit`   | No       | Items per page (default: 10)      |

**Response:**

| Field                   | Type                      | Description                                |
| ----------------------- | ------------------------- | ------------------------------------------ |
| `users`                 | array                     | List of users and pending invitations      |
| `users[].id`            | string (UUID) or null     | User ID (null for pending invitations)     |
| `users[].email`         | string                    | User's email address                       |
| `users[].first_name`    | string                    | User's first name                          |
| `users[].last_name`     | string                    | User's last name                           |
| `users[].full_name`     | string                    | User's full name                           |
| `users[].status`        | string                    | Status: `active`, `pending`, or `expired`  |
| `users[].invite_id`     | string (UUID) or null     | Invitation ID (null for active users)      |
| `users[].invited_at`    | string (ISO 8601) or null | Invitation timestamp                       |
| `users[].organizations` | array                     | Organization and role information          |
| `total`                 | integer                   | Total count of users + pending invitations |

<Note>
  This endpoint returns a **hybrid list** combining:

  * Active users (status: "active")
  * Pending invitations (status: "pending" or "expired")

  Results are sorted with active users first, then by creation date (newest first).
</Note>

### Invite User to Organization

Send an invitation to a user to join an organization with a specific role.

<CodeGroup>
  ```bash Request theme={null}
  curl -X POST "{{baseUrl}}/api/users/invite?org_id=your-org-id" \
    -H "Authorization: Bearer YOUR_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "email": "newuser@example.com",
      "first_name": "New",
      "last_name": "User",
      "role": "9d0e1f2a-3b4c-5d6e-7f8g-9h0i1j2k3l4m"
    }'
  ```

  ```python Python theme={null}
  import requests

  url = "{{baseUrl}}/api/users/invite"
  headers = {
      "Authorization": "Bearer YOUR_TOKEN",
      "Content-Type": "application/json"
  }
  params = {"org_id": "your-org-id"}
  data = {
      "email": "newuser@example.com",
      "first_name": "New",
      "last_name": "User",
      "role": "9d0e1f2a-3b4c-5d6e-7f8g-9h0i1j2k3l4m"
  }

  response = requests.post(url, headers=headers, params=params, json=data)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const axios = require('axios');

  const url = "{{baseUrl}}/api/users/invite";
  const headers = {
      Authorization: "Bearer YOUR_TOKEN",
      "Content-Type": "application/json"
  };
  const params = { org_id: "your-org-id" };
  const data = {
      email: "newuser@example.com",
      first_name: "New",
      last_name: "User",
      role: "9d0e1f2a-3b4c-5d6e-7f8g-9h0i1j2k3l4m"
  };

  axios.post(url, data, { headers, params })
      .then(response => console.log(response.data))
      .catch(error => console.error(error));
  ```

  ```json Response theme={null}
  {
    "id": "b2c3d4e5-f6g7-h8i9-j0k1-l2m3n4o5p6q7",
    "email": "newuser@example.com",
    "first_name": "New",
    "last_name": "User",
    "full_name": "New User",
    "organizations": {
      "id": "d07897e2-1ddf-4f11-9434-5fb6f2f5c20d",
      "name": "Acme Corporation",
      "slug": "acme-corp-abc123",
      "role_name": "admin",
      "role_id": "9d0e1f2a-3b4c-5d6e-7f8g-9h0i1j2k3l4m"
    }
  }
  ```
</CodeGroup>

**Endpoint:** `POST /api/users/invite`

**Query Parameters:**

| Parameter | Required | Description     |
| --------- | -------- | --------------- |
| `org_id`  | Yes      | Organization ID |

**Request Body:**

| Field        | Type          | Required | Description                   |
| ------------ | ------------- | -------- | ----------------------------- |
| `email`      | string        | Yes      | Invitee's email address       |
| `first_name` | string        | Yes      | Invitee's first name          |
| `last_name`  | string        | Yes      | Invitee's last name           |
| `role`       | string (UUID) | Yes      | Role ID to assign to the user |

**Response:**

| Field           | Type          | Description                   |
| --------------- | ------------- | ----------------------------- |
| `id`            | string (UUID) | Invitation ID                 |
| `email`         | string        | Invitee's email address       |
| `first_name`    | string        | Invitee's first name          |
| `last_name`     | string        | Invitee's last name           |
| `full_name`     | string        | Invitee's full name           |
| `organizations` | object        | Organization and role details |

<Note>
  **Invitation Flow:**

  1. User record is pre-created with status "invited"
  2. Invitation email is sent via Stytch with a magic link
  3. Organization member record is created with status "invited"
  4. Invitation expires after 7 days
  5. When user accepts, their status changes to "active"

  See [Invitation Flow](../authentication/invitation-flow) for complete details.
</Note>

### Update User Role

Update a user's role within an organization.

<CodeGroup>
  ```bash Request theme={null}
  curl -X PUT "{{baseUrl}}/api/users/update?org_id=your-org-id&user_id=a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6" \
    -H "Authorization: Bearer YOUR_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "role_id": "0e1f2a3b-4c5d-6e7f-8g9h-0i1j2k3l4m5n"
    }'
  ```

  ```python Python theme={null}
  import requests

  url = "{{baseUrl}}/api/users/update"
  headers = {
      "Authorization": "Bearer YOUR_TOKEN",
      "Content-Type": "application/json"
  }
  params = {
      "org_id": "your-org-id",
      "user_id": "a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6"
  }
  data = {
      "role_id": "0e1f2a3b-4c5d-6e7f-8g9h-0i1j2k3l4m5n"
  }

  response = requests.put(url, headers=headers, params=params, json=data)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const axios = require('axios');

  const url = "{{baseUrl}}/api/users/update";
  const headers = {
      Authorization: "Bearer YOUR_TOKEN",
      "Content-Type": "application/json"
  };
  const params = {
      org_id: "your-org-id",
      user_id: "a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6"
  };
  const data = {
      role_id: "0e1f2a3b-4c5d-6e7f-8g9h-0i1j2k3l4m5n"
  };

  axios.put(url, data, { headers, params })
      .then(response => console.log(response.data))
      .catch(error => console.error(error));
  ```

  ```json Response theme={null}
  {
    "id": "a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6",
    "email": "john.doe@example.com",
    "first_name": "John",
    "last_name": "Doe",
    "full_name": "John Doe",
    "organizations": [
      {
        "id": "d07897e2-1ddf-4f11-9434-5fb6f2f5c20d",
        "name": "Acme Corporation",
        "slug": "acme-corp-abc123",
        "role_name": "member",
        "role_id": "0e1f2a3b-4c5d-6e7f-8g9h-0i1j2k3l4m5n"
      }
    ],
    "invite_id": null,
    "last_active_at": "2023-11-03T15:45:00Z",
    "created_at": "2023-08-01T10:00:00Z"
  }
  ```
</CodeGroup>

**Endpoint:** `PUT /api/users/update`

**Query Parameters:**

| Parameter | Required | Description       |
| --------- | -------- | ----------------- |
| `org_id`  | Yes      | Organization ID   |
| `user_id` | Yes      | User ID to update |

**Request Body:**

| Field     | Type          | Required | Description                       |
| --------- | ------------- | -------- | --------------------------------- |
| `role_id` | string (UUID) | Yes      | New role ID to assign to the user |

**Response:**

| Field            | Type                      | Description                               |
| ---------------- | ------------------------- | ----------------------------------------- |
| `id`             | string (UUID)             | User's unique identifier                  |
| `email`          | string                    | User's email address                      |
| `first_name`     | string                    | User's first name                         |
| `last_name`      | string                    | User's last name                          |
| `full_name`      | string                    | User's full name                          |
| `organizations`  | array                     | Updated organization and role information |
| `invite_id`      | null                      | Always null for active users              |
| `last_active_at` | string (ISO 8601) or null | Last activity timestamp                   |
| `created_at`     | string (ISO 8601)         | Account creation timestamp                |

<Warning>
  **Restrictions:**

  * Only users with **owner** role can update user roles
  * The **owner** role cannot be assigned via this endpoint (reserved for organization creators)
  * Only **active** members can have their roles updated
  * Cannot assign the same role a user already has
  * Requires both `users:write` permission AND owner role
</Warning>

<Note>
  To change a user's role, the authenticated user must:

  1. Have `users:write` permission
  2. Be an **owner** in the organization

  This is more restrictive than other `users:write` operations.
</Note>

## Error Responses

| Status Code | Description                                                             |
| ----------- | ----------------------------------------------------------------------- |
| 400         | Bad Request - Invalid input, same role assignment, or non-active member |
| 401         | Unauthorized - Invalid or missing token                                 |
| 403         | Forbidden - Insufficient permissions or not an owner                    |
| 404         | Not Found - User, organization, or role doesn't exist                   |
| 500         | Internal Server Error - Server-side error                               |

### Common Error Scenarios

<AccordionGroup>
  <Accordion title="Cannot Update Role - Not an Owner">
    **Error:** 403 Forbidden - "Only users with 'owner' role can update user roles"

    **Solution:** Only organization owners can update user roles. If you need to change roles, ask an owner to do it.
  </Accordion>

  <Accordion title="Cannot Assign Owner Role">
    **Error:** 403 Forbidden - "The 'owner' role cannot be assigned to users"

    **Solution:** The owner role is reserved for organization creators and cannot be assigned via the API. To transfer ownership, contact support.
  </Accordion>

  <Accordion title="User Already Has Role">
    **Error:** 400 Bad Request - "User already has the 'admin' role assigned"

    **Solution:** Check the user's current role before updating. You cannot assign the same role a user already has.
  </Accordion>

  <Accordion title="Cannot Update Invited User">
    **Error:** 400 Bad Request - "Cannot update role for user with status 'invited'"

    **Solution:** Wait for the user to accept the invitation first. Only active members can have their roles updated.
  </Accordion>
</AccordionGroup>

## Implementation Notes

### User Statuses

Users can have different statuses in an organization:

| Status        | Description                                   | Can Update Role? |
| ------------- | --------------------------------------------- | ---------------- |
| **active**    | User has accepted invitation and is active    | Yes              |
| **invited**   | User has been invited but hasn't accepted yet | No               |
| **suspended** | User access has been temporarily disabled     | No               |
| **deleted**   | User has been removed from organization       | No               |

### Role Assignment Rules

1. **Owner Role Special Case:**
   * Owner role is automatically assigned when creating an organization
   * Cannot be assigned via API
   * Cannot be removed (organization must have at least one owner)

2. **Role Hierarchy:**
   * Roles have hierarchy levels (0-100)
   * Higher levels generally have more permissions
   * Check role hierarchy before assignment

3. **Permission Requirements:**
   * Viewing users: `users:read` permission
   * Inviting users: `users:write` permission
   * Updating roles: `users:write` permission **AND** owner role

### Pagination

The `/api/users/list` endpoint uses offset-based pagination:

* `offset`: Page number (0-based)
* `limit`: Items per page
* `total`: Total count of items

Example for page 3 with 10 items per page:

```
offset=2, limit=10  // Returns items 21-30
```

### Invitation Expiry

* Invitations expire after **7 days**
* Expired invitations show status "expired" in user list
* To resend an expired invitation, create a new invitation

## Related Documentation

* [Invitation Flow](../authentication/invitation-flow) - Complete invitation process
* [Roles Service](roles-service) - Managing roles and permissions
* [Organization Service](org-service) - Managing organizations
* [RBAC Permissions](../authentication/rbac-permissions) - Understanding permissions
