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.Managercannot seeOrganizationAdminusers.
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, andOwnermay retrieve any user in their scope.Managercannot retrieve anOrganizationAdmin.
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/Ownermay 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:
| 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, andOwnermay update other users.Managercannot update anOrganizationAdmin.- Only admins (
OrganizationAdminand above) may change thestatusfield.
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:
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: