Skip to content

Users

Endpoints for reading and managing user accounts and profiles.

Base path: /api/users

Authentication: All endpoints require a valid session.


Endpoints

Method Path Min Role Description
GET /api/users/me Any Get own profile
PATCH /api/users/me Any Update own profile
POST /api/users/me/password Any Change own password
GET /api/users Manager List users
POST /api/users Manager Create a new user
GET /api/users/:id/presence Self or Manager Get user presence
GET /api/users/:id Self or Manager Get user by ID
PATCH /api/users/:id Self or Manager Update user
DELETE /api/users/:id OrganizationAdmin Archive user

GET /api/users/me

Returns the full profile of the currently authenticated user, including their effective role in the current org.

Response 200:

{
  "data": {
    "id": "user-cuid",
    "email": "jane@example.com",
    "status": "active",
    "globalRole": null,
    "preferredLanguage": "es",
    "firstName": "Jane",
    "lastName": "Smith",
    "phone": "+1 555-0100",
    "birthDate": "1990-06-15",
    "countryCode": "US",
    "timezone": "America/New_York",
    "avatarUrl": "https://...",
    "currentOrganizationId": "org-uuid",
    "role": "Coach",
    "orgId": "org-uuid"
  }
}

Response fields:

Field Type Description
id string User CUID (Better-auth format)
email string Email address
status string active, inactive, suspended, or archived
globalRole string | null Owner or PlatformAdmin; null for org-scoped users
preferredLanguage string | null Preferred language (en or es) used for localized emails
firstName string | null First name
lastName string | null Last name
phone string | null Phone number
birthDate string | null Birth date (YYYY-MM-DD)
countryCode string | null ISO 3166-1 alpha-2 country code
timezone string | null IANA timezone identifier (e.g. America/New_York)
avatarUrl string | null Presigned avatar image URL
currentOrganizationId string | null Active org set in profile
role string | null Effective role: globalRole if set, otherwise orgRole
orgId string | null Active org resolved from header or profile

Error responses:

Code Condition
404 User record not found (should not happen with a valid session)

PATCH /api/users/me

Update the current user's own profile fields.

Request body (all fields optional):

{
  "firstName": "Jane",
  "lastName": "Smith",
  "preferredLanguage": "es",
  "phone": "+1 555-0100",
  "birthDate": "1990-06-15",
  "countryCode": "US"
}
Field Type Constraints
firstName string 1–100 characters
lastName string 1–100 characters
preferredLanguage string en or es
phone string National or international format; validated against countryCode via libphonenumber-js. Pass "" to clear.
birthDate string YYYY-MM-DD format
countryCode string ISO 3166-1 alpha-2
timezone string IANA timezone identifier (e.g. America/New_York, UTC)

Response 200:

{
  "data": {
    "id": "user-cuid",
    "firstName": "Jane",
    "lastName": "Smith",
    "phone": "+1 555-0100",
    "birthDate": "1990-06-15",
    "countryCode": "US",
    "avatarUrl": null,
    "currentOrganizationId": "org-uuid",
    "createdAt": "2025-01-01T00:00:00.000Z",
    "updatedAt": "2025-04-01T12:00:00.000Z"
  }
}

GET /api/users

List users. Behavior differs by caller role:

  • PlatformAdmin / Owner: Returns all users in the system with their org memberships.
  • OrganizationAdmin / Manager: Returns users in the active organization only. Manager cannot see OrganizationAdmin users.

Query parameters:

Parameter Type Description
limit integer Max records to return (default: 100, max: 1000)
offset integer Records to skip (default: 0)
search string Filter by email or display name (case-insensitive, partial match)
status string Filter by status: active, inactive, suspended, or archived
role string Filter by role (e.g. Coach, Manager, OrganizationAdmin)

Response 200:

{
  "data": [
    {
      "id": "user-cuid",
      "email": "jane@example.com",
      "status": "active",
      "globalRole": null,
      "firstName": "Jane",
      "lastName": "Smith",
      "avatarUrl": null,
      "role": "Coach",
      "orgRoles": [
        {
          "orgId": "org-uuid",
          "orgName": "Praxia Academy",
          "role": "Coach",
          "membershipStatus": "active"
        }
      ]
    }
  ],
  "meta": {
    "total_count": 42
  }
}

org-scoped list

When called by an OrganizationAdmin or Manager, the orgRoles field is not included. Instead a role field directly contains the user's role within the current org.

Error responses:

Code Condition
400 No organization context (org-scoped callers must have X-Organization-Id)

POST /api/users

Create a new user account. The user is created without an organization role — assign them afterward using POST /api/organizations/:id/users or send an invite.

Min role: Manager

Request body:

{
  "email": "newuser@example.com",
  "firstName": "John",
  "lastName": "Doe",
  "password": "TempPassword123!",
  "preferredLanguage": "es"
}
Field Type Required Constraints
email string Yes Valid email address
firstName string Yes 1–100 characters
lastName string Yes 1–100 characters
password string Yes Minimum 8 characters
preferredLanguage string No en or es (fallback: Accept-Language, then en)

Response 201:

{
  "data": {
    "id": "user-cuid",
    "email": "newuser@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "preferredLanguage": "es"
  }
}

Error responses:

Code Condition
409 A user with this email already exists
500 Internal error during account creation

GET /api/users/:id

Get a single user's profile by their ID.

Access rules:

  • A user may always retrieve their own record.
  • Manager, OrganizationAdmin, PlatformAdmin, and Owner may retrieve any user in their scope.
  • Manager cannot retrieve an OrganizationAdmin.

Path parameters:

Parameter Type Description
id string User CUID

Response 200:

{
  "data": {
    "id": "user-cuid",
    "email": "jane@example.com",
    "status": "active",
    "globalRole": null,
    "firstName": "Jane",
    "lastName": "Smith",
    "avatarUrl": null,
    "role": "Coach",
    "orgRoles": [
      {
        "orgId": "org-uuid",
        "orgName": "Praxia Academy",
        "role": "Coach"
      }
    ]
  }
}

Sensitive fields

phone and birthDate are only included when a user retrieves their own record.

Error responses:

Code Condition
403 Caller does not have permission to view this user
404 User not found

GET /api/users/:id/presence

Returns lightweight online/offline presence for a user based on SSE heartbeat updates.

Access rules:

  • A user may always query their own presence.
  • PlatformAdmin / Owner may query any user.
  • Org-scoped users may query users in the same active organization only.

Path parameters:

Parameter Type Description
id string User CUID

Response 200:

{
  "data": {
    "userId": "user-cuid",
    "online": true,
    "lastSeen": "2026-04-25T12:00:00.000Z"
  }
}
Field Type Description
userId string User CUID
online boolean true when the user has a non-expired heartbeat key
lastSeen string | null ISO timestamp from Redis heartbeat, or null if unknown

Error responses:

Code Condition
403 Caller is not allowed to query this user's presence

PATCH /api/users/:id

Update a user's profile or status.

Access rules:

  • A user may update their own profile fields.
  • Manager, OrganizationAdmin, PlatformAdmin, and Owner may update other users.
  • Manager cannot update an OrganizationAdmin.
  • Only admins (OrganizationAdmin and above) may change the status field.

Path parameters:

Parameter Type Description
id string User CUID

Request body (all fields optional):

{
  "firstName": "Jane",
  "lastName": "Smith",
  "phone": "+1 555-0100",
  "birthDate": "1990-06-15",
  "avatar": "file-cuid",
  "status": "inactive"
}
Field Type Constraints
firstName string 1–100 characters
lastName string 1–100 characters
phone string Maximum 50 characters
birthDate string YYYY-MM-DD format
avatar string File ID returned by POST /api/files/upload
status string active, inactive, suspended, or archived; admin-only

Response 200:

{
  "success": true
}

Error responses:

Code Condition
403 Caller does not have permission to update this user

DELETE /api/users/:id

Archive a user account (soft delete). Sets status = 'archived'; the record is retained.

Min role: OrganizationAdmin

Path parameters:

Parameter Type Description
id string User CUID

Response 200:

{
  "success": true
}