User Management Endpoints Index

User Authentication Types

The system supports different authentication types based on the authentication method used during registration:

Code Name Description Set During
EMAI Email Traditional email/password authentication POST /api/auth/register
GOOG Google OAuth Google Sign-In authentication POST /api/auth/google/register
APPE Apple OAuth Apple Sign-In authentication (future) Future implementation
FACE Facebook OAuth Facebook authentication (future) Future implementation
XXXX X OAuth X (formerly Twitter) authentication (future) Future implementation
LINK LinkedIn OAuth LinkedIn authentication (future) Future implementation

Important Notes:

User Endpoints

GET /api/users

List all users (requires authentication)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...

Query Parameters:

  • sort - JSON array with field name and direction ["fieldName","ASC|DESC"]
    Available sort fields: id, username, email, firstName, lastName, createdAt, updatedAt, verifiedAt, typeName, authTypeName, isActive, userTypeCode, authTypeCode, lastLoginAt
    Example: sort=["username","ASC"] - Sort by username in ascending order
  • page - Page number (1-based)
    Example: page=1 - Get the first page
  • perPage - Number of items per page
    Example: perPage=10 - Show 10 items per page
  • filter - JSON object with field name/value pairs for filtering {"fieldName1":"value1","fieldName2":value2}
    Example: filter={"username":"John","isActive":true} - Filter active users with username containing "John"

    Available filters:
    • q - Global search across username, email, first name, last name, user type name, and authentication type name
    • username - Filter by username (partial match, case-insensitive)
    • email - Filter by email address (partial match, case-insensitive)
    • firstName - Filter by first name (partial match, case-insensitive)
    • lastName - Filter by last name (partial match, case-insensitive)
    • isActive - Filter by active status (true/false)
    • userTypeCode - Filter by user type code (SUBS, NONS, ADMI)
    • typeName - Filter by user type name (partial match, case-insensitive)
    • authTypeCode - Filter by authentication type (EMAI, GOOG, APPE, FACE, XXXX, LINK)
    • authTypeName - Filter by authentication type name (partial match, case-insensitive)
    • verifiedAt - Filter by verification status
      Use "null" for unverified users
      Use "!null" for verified users
      Use a date string for specific date matching
    • lastLoginAt - Filter by last login
      Use "null" for users who never logged in
      Use "!null" for users who have logged in
      Use a date string for specific date matching
    • legacyUserId - Filter by legacy user ID
      Use "null" for non-legacy users
      Use "!null" for legacy users
      Use a number for specific legacy user ID
    • subscriptionExemptionStartsAt - Filter by subscription exemption start date (exact date or date range, or "null"/"!null")
    • subscriptionExemptionEndsAt - Filter by subscription exemption end date (exact date or date range, or "null"/"!null")
    • createdAt - Filter by creation date (exact date or date range)
    • updatedAt - Filter by last update date (exact date or date range)


    Special filter: Global Search
    Using the key q in the filter will search across all text columns:
    Example: filter={"q":"John","isActive":true} - Find active users with "John" in any text field

    Special filter: NULL/NOT NULL Values
    For nullable fields, use "null" to find records with NULL values or "!null" to find records with non-NULL values:
    Example: filter={"verifiedAt":"null"} - Find unverified users
    Example: filter={"lastLoginAt":"!null"} - Find users who have logged in
    Example: filter={"legacyUserId":"null"} - Find non-legacy users
    Example: filter={"legacyUserId":"!null"} - Find legacy users

Example Requests:

GET /api/users?page=1&perPage=10&sort=["username","ASC"]&filter={"userTypeCode":"SUBS"}
GET /api/users?page=2&perPage=20&filter={"q":"John","isActive":true}
GET /api/users?filter={"email":"gmail.com","verifiedAt":"null"}
GET /api/users?filter={"verifiedAt":"!null","isActive":true}
GET /api/users?filter={"lastLoginAt":"null"}
GET /api/users?filter={"lastLoginAt":"!null","userTypeCode":"SUBS"}
GET /api/users?filter={"legacyUserId":"null"}
GET /api/users?filter={"legacyUserId":"!null","isActive":true}
GET /api/users?filter={"typeName":"Subscribed","isActive":true}
GET /api/users?filter={"authTypeCode":"GOOG","isActive":true}
GET /api/users?filter={"authTypeName":"Google OAuth","isActive":true}
GET /api/users?sort=["authTypeName","ASC"]&filter={"isActive":true}

Response (200 OK):

{
    "data": [
        {
            "id": 1,
            "username": "John Doe",
            "email": "john@example.com",
            "typeCode": "SUBS",
            "typeName": "Subscribed",
            "firstName": "John",
            "lastName": "Doe",
            "authTypeCode": "EMAI",
            "authTypeName": "Email",
            "isActive": true,
            "createdAt": "2023-01-15T08:30:00Z",
            "updatedAt": "2023-01-15T08:30:00Z",
            "verifiedAt": "2023-01-15T08:35:00Z",
            "lastLoginAt": "2023-06-15T14:20:00Z",
            "subscriptionExemptionStartsAt": null,
            "subscriptionExemptionEndsAt": null,
            "legacyUserId": null
        },
        {
            "id": 2,
            "username": "Jane Smith",
            "email": "jane@gmail.com",
            "typeCode": "SUBS",
            "typeName": "Subscribed",
            "firstName": "Jane",
            "lastName": "Smith",
            "authTypeCode": "GOOG",
            "authTypeName": "Google OAuth",
            "isActive": true,
            "createdAt": "2023-02-20T10:15:00Z",
            "updatedAt": "2023-02-20T10:15:00Z",
            "verifiedAt": "2023-02-20T10:20:00Z",
            "lastLoginAt": "2023-06-14T09:30:00Z",
            "subscriptionExemptionStartsAt": null,
            "subscriptionExemptionEndsAt": null,
            "legacyUserId": 42
        }
    ],
    "total": 42
}

Headers:

Content-Range: items 0-9/42
Accept-Range: items
Access-Control-Expose-Headers: Content-Range
X-Total-Count: 42

Note: The Content-Range header indicates the range of items returned and the total count.

GET /api/users/full

List all users with full details including subscription and promotion information (requires admin authentication)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...

Query Parameters:

Same as /api/users endpoint, with additional filterable fields for subscription and promotion data.

  • sort - JSON array with field name and direction
  • page - Page number (1-based)
  • perPage - Number of items per page
  • filter - JSON object with filters for user, subscription, and promotion fields

Additional Available Filters:

  • Subscription filters: subscriptionStatus, subscriptionPlanName, subscriptionPlanAmount, subscriptionChargedAmount, stripeCustomerId, stripeSubscriptionId
  • Promotion filters: promotionName, promotionCode, couponName, couponTypeCode, discountPercentage

Additional Sortable Fields:

  • subscriptionPlanAmount - Sort by plan amount (in cents)
  • subscriptionChargedAmount - Sort by actual amount charged after discounts (in cents)
  • subscriptionStatus - Sort by subscription status
  • promotionCode - Sort by promotion code
  • discountPercentage - Sort by discount percentage

Response (200 OK):

{
    "data": [
        {
            "id": 1,
            "username": "John Doe",
            "email": "john@example.com",
            "typeCode": "SUBS",
            "typeName": "Subscribed",
            "firstName": "John",
            "lastName": "Doe",
            "authTypeCode": "EMAI",
            "authTypeName": "Email",
            "isActive": true,
            "createdAt": "2023-01-15T08:30:00Z",
            "updatedAt": "2023-01-15T08:30:00Z",
            "verifiedAt": "2023-01-15T08:35:00Z",
            "subscriptionExemptionStartsAt": null,
            "subscriptionExemptionEndsAt": null,
            "legacyUserId": null,
            "lastLoginAt": "2023-06-15T14:20:00Z",
            "subscriptionId": 42,
            "subscriptionPlanId": 1,
            "subscriptionPlanName": "Monthly",
            "subscriptionPlanInterval": "month",
            "subscriptionPlanAmount": 1500,
            "subscriptionPlanCurrency": "usd",
            "subscriptionPlanTrialPeriodDays": 14,
            "subscriptionChargedAmount": 1200,
            "stripeCustomerId": "cus_abc123",
            "stripeSubscriptionId": "sub_xyz789",
            "subscriptionStatus": "active",
            "currentPeriodStart": "2023-06-01T00:00:00Z",
            "currentPeriodEnd": "2023-07-01T00:00:00Z",
            "trialStart": "2023-01-15T08:35:00Z",
            "trialEnd": "2023-01-29T08:35:00Z",
            "cancelAtPeriodEnd": false,
            "canceledAt": null,
            "subscriptionCreatedAt": "2023-01-15T08:40:00Z",
            "subscriptionUpdatedAt": "2023-06-01T00:00:00Z",
            "promotionRedemptionId": 5,
            "promotionId": 3,
            "promotionName": "Summer Special",
            "promotionCode": "SUMMER20",
            "promotionDescription": "20% off for summer",
            "couponId": 2,
            "couponName": "Summer Discount",
            "couponTypeCode": "DISC",
            "couponTypeName": "Discount Percentage",
            "discountPercentage": 20,
            "trialDays": null,
            "couponDurationTypeCode": "ONCE",
            "couponDurationTypeName": "Once",
            "durationInMonths": null,
            "stripeDiscountId": "di_abc123",
            "promotionAppliedAt": "2023-01-15T08:40:00Z"
        }
    ],
    "total": 42
}

Headers:

Content-Range: items 0-9/42
Accept-Range: items
Access-Control-Expose-Headers: Content-Range
X-Total-Count: 42

Error Responses:

{
    "message": "Administrator access required"
}

Notes:

  • Admin-only endpoint - requires ADMI user type
  • Returns flattened data structure with all user, subscription, and promotion fields at the same level
  • Subscription and promotion fields will be null if the user doesn't have a subscription or applied promotion
  • subscriptionChargedAmount shows the actual amount the user is charged after applying any discount (in cents). If there's a 20% discount on a $15 plan, subscriptionChargedAmount would be 1200 (cents)
  • Data is fetched directly from database in a single query for performance
  • Does not sync with Stripe - shows database state only
GET /api/users/me

Get current user profile (requires authentication)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...

Response (200 OK):

{
    "id": 1,
    "username": "John Doe",
    "email": "john@example.com",
    "typeCode": "SUBS",
    "typeName": "Subscribed",
    "firstName": "John",
    "lastName": "Doe",
    "authTypeCode": "EMAI",
    "authTypeName": "Email",
    "isActive": true,
    "createdAt": "2023-01-15T08:30:00Z",
    "updatedAt": "2023-01-15T08:30:00Z",
    "verifiedAt": "2023-01-15T08:35:00Z",
    "lastLoginAt": "2023-06-15T14:20:00Z",
    "subscriptionExemptionStartsAt": null,
    "subscriptionExemptionEndsAt": null,
    "legacyUserId": null
}

Error Responses:

{
    "message": "User not authenticated"
}

Notes:

  • Does not require CSRF token as it's a GET request (only mutation methods require CSRF)
GET /api/users/metrics

Gets a list of overall numbers on user data (requires authentication)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...

Response (200 OK):

{
    "data": [
        {
            "id": 0,
            "monthlyCreatedUsers": 0,
            "monthlyPaidUsers": 0,
            "monthlyFreeUsers": 0,
            "monthlyTrialUsers": 5,
            "monthlyUsersWithNoSubscription": 0,
            "monthlyIncompleteRegs": 0,
            "monthlyIncompleteVerification": 0,
            "monthlyIncompleteSubscrptions": 0,
            "monthlySubscriptionIncome": 14500,
            "monthlyNewMonthlySubscriptionIncome": 4500,
            "monthlyNewAnnualSubscriptionIncome": 28800,
            "yearlyCreatedUsers": 3,
            "yearlyPaidUsers": 12,
            "yearlyFreeUsers": 0,
            "yearlyTrialUsers": 8,
            "yearlyUsersWithNoSubscription": 0,
            "yearlySubscriptionIncome": 145000,
            "yearlyNewMonthlySubscriptionIncome": 54000,
            "yearlyNewAnnualSubscriptionIncome": 288000,
            "totalCreatedUsers": 120,
            "totalPaidUsers": 45,
            "totalFreeUsers": 0,
            "totalTrialUsers": 15,
            "totalUsersWithNoSubscription": 2,
            "totalMonthlySubscriptionIncome": 125000,
            "totalAnnualSubscriptionIncome": 720000,
            "totalLegacyUsers": 1500,
            "totalMigratedLegacyUsers": 1400,
            "totalNonMigratedLegacyUsers": 100
        }
    ],
    "total": 1
}

Error Responses:

{
    "message": "We could not collect any data on the users."
}

Notes:

  • This endpoint gets total numbers for different user metrics for the dashboard
  • Does not require CSRF token as it's a GET request (only mutation methods require CSRF)
  • Returns metrics including:
    • id - Placeholder ID (always 0)
    • monthlyCreatedUsers - Total active users created in current month who have logged in at least once
    • monthlyPaidUsers - Total paid users created in current month with active subscriptions (excludes 100% discount coupons and $0 plans)
    • monthlyFreeUsers - Total free users created in current month with NONS user type or users with NONS promotion or 100% discount promotion applied
    • monthlyTrialUsers - Total users created in current month with active $0 plan subscriptions without NONS promotion or 100% discount
    • monthlyUsersWithNoSubscription - Active users created in current month with expired subscriptions, canceled subscriptions, or SUBS users with no subscription (excludes users with NONS promotion)
    • monthlyIncompleteRegs - Active users created in current month with incomplete registration (no verified_at or no subscription)
    • monthlyIncompleteVerification - Active users created in current month who have not verified their email
    • monthlyIncompleteSubscrptions - Active users created in current month who verified email but have not subscribed
    • monthlySubscriptionIncome - Total revenue in cents expected from subscriptions whose billing period ends in current month (after applying discounts)
    • monthlyNewMonthlySubscriptionIncome - Total revenue in cents from new monthly subscriptions created in current month (after applying discounts)
    • monthlyNewAnnualSubscriptionIncome - Total revenue in cents from new annual subscriptions created in current month (after applying discounts)
    • yearlyCreatedUsers - Total active users created in current year who have logged in at least once
    • yearlyPaidUsers - Total paid users created in current year with active subscriptions (excludes 100% discount coupons and $0 plans)
    • yearlyFreeUsers - Total free users created in current year with NONS user type or users with NONS promotion or 100% discount promotion applied
    • yearlyTrialUsers - Total users created in current year with active $0 plan subscriptions without NONS promotion or 100% discount
    • yearlyUsersWithNoSubscription - Active users created in current year with expired subscriptions, canceled subscriptions, or SUBS users with no subscription (excludes users with NONS promotion)
    • yearlySubscriptionIncome - Total revenue in cents RECEIVED year-to-date (Jan 1 to current date). For monthly subscriptions, counts each monthly payment received this year. For yearly subscriptions, counts payment if it was received this year. Calculated by counting billing cycles based on current_period_end dates (after applying discounts)
    • yearlyNewMonthlySubscriptionIncome - Total revenue in cents from new monthly subscriptions created in current year (after applying discounts)
    • yearlyNewAnnualSubscriptionIncome - Total revenue in cents from new annual subscriptions created in current year (after applying discounts)
    • totalCreatedUsers - Total active users who have logged in at least once
    • totalPaidUsers - Total users with active paid subscriptions (excludes 100% discount coupons and $0 plans)
    • totalFreeUsers - Total active users with NONS user type or users with NONS promotion or 100% discount promotion applied
    • totalTrialUsers - Total users with active $0 plan subscriptions without NONS promotion or 100% discount
    • totalUsersWithNoSubscription - Active users with expired subscriptions, canceled subscriptions, or SUBS users with no subscription (excludes users with NONS promotion)
    • totalMonthlySubscriptionIncome - Total recurring revenue in cents from all active monthly subscriptions (after applying discounts)
    • totalAnnualSubscriptionIncome - Total recurring revenue in cents from all active annual subscriptions (after applying discounts)
    • totalLegacyUsers - Total active users with legacy accounts
    • totalMigratedLegacyUsers - Total active users with legacy accounts who have migrated to the new system
    • totalNonMigratedLegacyUsers - Total active users with legacy accounts who have not migrated to the new system
GET /api/users/verification-requests

List all user verification requests with filtering, sorting, and pagination (requires authentication)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...

Query Parameters:

  • sort - JSON array with field name and direction ["fieldName","ASC|DESC"]
    Available sort fields: id, userId, requestTypeCode, requestTypeName, verificationCode, createdOn, attempts, username, email, firstName, lastName, isActive
    Example: sort=["createdOn","DESC"] - Sort by creation date in descending order
  • page - Page number (1-based)
    Example: page=1 - Get the first page
  • perPage - Number of items per page
    Example: perPage=10 - Show 10 items per page
  • filter - JSON object with field name/value pairs for filtering {"fieldName1":"value1","fieldName2":value2}
    Example: filter={"requestTypeCode":"RPWR"} - Filter verification requests with type code "RPWR" (password reset)

    Available filters:
    • q - Global search across verification code, request type code, request type name, username, email, first name, and last name
    • requestTypeCode - Filter by request type code (REGR for registration, RPWR for password reset)
    • requestTypeName - Filter by request type name (partial match, case-insensitive)
    • verificationCode - Filter by verification code (partial match, case-insensitive)
    • username - Filter by username (partial match, case-insensitive)
    • email - Filter by email address (partial match, case-insensitive)
    • firstName - Filter by first name (partial match, case-insensitive)
    • lastName - Filter by last name (partial match, case-insensitive)
    • isActive - Filter by user active status (true/false)
    • userId - Filter by specific user ID (exact match)
    • attempts - Filter by number of verification attempts (exact match)
    • createdOn - Filter by creation date (exact date or date range)


    Special filter: Global Search
    Using the key q in the filter will search across key text columns:
    Example: filter={"q":"reset"} - Find verification requests with "reset" in any searchable field (verificationCode, requestTypeCode, requestTypeName, username, email, firstName, lastName)

Example Requests:

GET /api/users/verification-requests?page=1&perPage=10&sort=["createdOn","DESC"]&filter={"requestTypeCode":"RPWR"}
GET /api/users/verification-requests?page=1&perPage=20&filter={"q":"john","isActive":true}
GET /api/users/verification-requests?filter={"requestTypeName":"Password","username":"smith"}
GET /api/users/verification-requests?filter={"userId":42,"attempts":0}

Response (200 OK):

{
    "data": [
        {
            "id": 1,
            "userId": 42,
            "requestTypeCode": "RPWR",
            "requestTypeName": "Reset Password",
            "verificationCode": "abc123def456", 
            "createdOn": "2023-06-15T14:30:00Z",
            "attempts": 0,
            "username": "johndoe",
            "email": "john@example.com",
            "firstName": "John",
            "lastName": "Doe",
            "isActive": true
        },
        {
            "id": 2,
            "userId": 37,
            "requestTypeCode": "REGR",
            "requestTypeName": "Email Verification",
            "verificationCode": "xyz789uvw456",
            "createdOn": "2023-06-14T10:15:00Z",
            "attempts": 1,
            "username": "janesmith",
            "email": "jane@example.com",
            "firstName": "Jane",
            "lastName": "Smith",
            "isActive": true
        }
    ],
    "total": 42
}

Headers:

Content-Range: items 0-9/42
Accept-Range: items
Access-Control-Expose-Headers: Content-Range
X-Total-Count: 42

Note: This endpoint includes verification codes and user details along with verification request information for improved usability and administration.

Notes:

  • Does not require CSRF token as it's a GET request (only mutation methods require CSRF)
GET /api/users/:id

Get a specific user by ID (requires authentication)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...

Response (200 OK):

{
    "id": 2,
    "username": "Jane Doe",
    "email": "jane@example.com",
    "typeCode": "SUBS",
    "typeName": "Subscribed",
    "firstName": "Jane",
    "lastName": "Doe",
    "authTypeCode": "GOOG",
    "authTypeName": "Google OAuth",
    "isActive": true,
    "createdAt": "2023-02-20T10:15:00Z",
    "updatedAt": "2023-02-20T10:15:00Z",
    "verifiedAt": "2023-02-20T10:20:00Z",
    "lastLoginAt": "2023-06-14T09:30:00Z",
    "subscriptionExemptionStartsAt": null,
    "subscriptionExemptionEndsAt": null,
    "legacyUserId": null
}

Error Responses:

{
    "message": "User not found"
}

{
    "message": "User not authenticated"
}

Notes:

  • Does not require CSRF token as it's a GET request (only mutation methods require CSRF)
GET /api/users/full/:id

Get full user details by ID including subscription and promotion information (requires admin authentication)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...

URL Parameters:

  • id - User ID (integer)

Response (200 OK):

{
    "id": 1,
    "username": "John Doe",
    "email": "john@example.com",
    "typeCode": "SUBS",
    "typeName": "Subscribed",
    "firstName": "John",
    "lastName": "Doe",
    "authTypeCode": "EMAI",
    "authTypeName": "Email",
    "isActive": true,
    "createdAt": "2023-01-15T08:30:00Z",
    "updatedAt": "2023-01-15T08:30:00Z",
    "verifiedAt": "2023-01-15T08:35:00Z",
    "subscriptionExemptionStartsAt": null,
    "subscriptionExemptionEndsAt": null,
    "legacyUserId": null,
    "lastLoginAt": "2023-06-15T14:20:00Z",
    "subscriptionId": 42,
    "subscriptionPlanId": 1,
    "subscriptionPlanName": "Monthly",
    "subscriptionPlanInterval": "month",
    "subscriptionPlanAmount": 1500,
    "subscriptionPlanCurrency": "usd",
    "subscriptionPlanTrialPeriodDays": 14,
    "subscriptionChargedAmount": 1200,
    "stripeCustomerId": "cus_abc123",
    "stripeSubscriptionId": "sub_xyz789",
    "subscriptionStatus": "active",
    "currentPeriodStart": "2023-06-01T00:00:00Z",
    "currentPeriodEnd": "2023-07-01T00:00:00Z",
    "trialStart": "2023-01-15T08:35:00Z",
    "trialEnd": "2023-01-29T08:35:00Z",
    "cancelAtPeriodEnd": false,
    "canceledAt": null,
    "subscriptionCreatedAt": "2023-01-15T08:40:00Z",
    "subscriptionUpdatedAt": "2023-06-01T00:00:00Z",
    "promotionRedemptionId": 5,
    "promotionId": 3,
    "promotionName": "Summer Special",
    "promotionCode": "SUMMER20",
    "promotionDescription": "20% off for summer",
    "couponId": 2,
    "couponName": "Summer Discount",
    "couponTypeCode": "DISC",
    "couponTypeName": "Discount Percentage",
    "discountPercentage": 20,
    "trialDays": null,
    "couponDurationTypeCode": "ONCE",
    "couponDurationTypeName": "Once",
    "durationInMonths": null,
    "stripeDiscountId": "di_abc123",
    "promotionAppliedAt": "2023-01-15T08:40:00Z"
}

Error Responses:

{
    "message": "Administrator access required"
}
{
    "message": "User not found"
}

Notes:

  • Admin-only endpoint - requires ADMI user type
  • Returns flattened data structure with all user, subscription, and promotion fields at the same level
  • Subscription and promotion fields will be null if the user doesn't have a subscription or applied promotion
  • Data is fetched directly from database in a single query for performance
  • Does not sync with Stripe - shows database state only
POST /api/users/verification-requests/resend-email

Resends verification email to selected users (requires authentication and CSRF token)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3

Request Body:

{
    "emails": ["john@example.com", "jane@example.com"]  // Array of email addresses
}

Response (200 OK):

{
    "code": 200,
    "message": "Successfully sent verification codes."
}

Error Responses:

{
    "message": "One or more users not found."
}

Notes:

  • This endpoint resends existing verification codes to the users with the specified email addresses
  • It will locate the appropriate verification request (registration or password reset) and resend it
  • This is useful when users report they haven't received their verification email
  • The system will send verification emails appropriate to the existing request type
  • Requires CSRF token as it's a POST request that triggers email sending
POST /api/users/me/verify-update

Verify and update current user profile with email change (requires authentication and CSRF token)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3

Request Body:

{
    "email": "johnsmith@example.com",
    "firstName": "John",
    "lastName": "Smith",
    "username": "johnsmith",
    "verificationCode": "123456"
}

Response (200 OK):

{
    "id": 1,
    "username": "johnsmith",
    "email": "johnsmith@example.com",
    "typeCode": "SUBS",
    "typeName": "Subscribed",
    "firstName": "John",
    "lastName": "Smith",
    "isActive": true,
    "createdAt": "2023-01-15T08:30:00Z",
    "updatedAt": "2023-06-10T14:20:00Z",
    "verifiedAt": "2023-01-15T08:35:00Z",
    "subscriptionExemptionStartsAt": null,
    "subscriptionExemptionEndsAt": null,
    "legacyUserId": null
}

Error Responses:

{
    "error": "Invalid verification code",
    "message": "The verification code is incorrect. 4 attempts remaining.",
    "code": "INVALID_CODE",
    "attemptsRemaining": 4
}

{
    "error": "No verification request found",
    "message": "No pending email update verification. Please request a new update.",
    "code": "NO_VERIFICATION_REQUEST"
}

{
    "error": "Too many attempts",
    "message": "Maximum verification attempts exceeded. Please request a new update.",
    "code": "MAX_ATTEMPTS_EXCEEDED"
}

{
    "error": "Email already in use",
    "message": "This email address is already associated with another account",
    "code": "EMAIL_EXISTS"
}

{
    "message": "User not authenticated"
}

Notes:

  • This endpoint is used to complete user profile updates when the email address is being changed
  • Important: You must resend ALL the update data (email, firstName, lastName, username) that was originally sent to PUT /api/users/me, plus the verification code
  • The verification code is sent to the NEW email address specified in the update request
  • Maximum 5 verification attempts allowed before the verification request is cleared
  • On successful verification, ALL fields are updated (email, firstName, lastName, username) and the Stripe customer is updated
  • Email Uniqueness: Even after entering the correct verification code, the update will still fail with a 409 error if another user has claimed that email address in the meantime. Email validation is case-insensitive and ignores whitespace.
  • If email is not being changed in the data, you can use PUT /api/users/me directly instead
  • Requires CSRF token as it's a POST request that modifies user data
PUT /api/users/me

Update current user profile (requires authentication and CSRF token)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3

Request Body:

{
    "username": "johnsmith",
    "email": "johnsmith@example.com",
    "firstName": "John",
    "lastName": "Smith"
}

Response (200 OK) - When email is NOT changing:

{
    "id": 1,
    "username": "johnsmith",
    "email": "john@example.com",
    "typeCode": "SUBS",
    "typeName": "Subscribed",
    "firstName": "John",
    "lastName": "Smith",
    "isActive": true,
    "createdAt": "2023-01-15T08:30:00Z",
    "updatedAt": "2023-06-10T14:20:00Z",
    "verifiedAt": "2023-01-15T08:35:00Z",
    "subscriptionExemptionStartsAt": null,
    "subscriptionExemptionEndsAt": null,
    "legacyUserId": null
}

Response (200 OK) - When email IS changing:

{
    "emailVerificationRequired": true,
    "message": "Verification code sent to johnsmith@example.com. Please verify to complete the update."
}

Error Responses:

{
    "message": "User not authenticated"
}

{
    "message": "User not found"
}

{
    "message": "Email address is already in use",
    "code": "EMAIL_EXISTS"
}

{
    "message": "Failed to send verification email"
}

Notes:

  • All fields in the request body are optional for partial updates
  • Email Change Flow: If the email address is being changed, NO updates are applied immediately. Instead, a verification code is sent to the new email address. The client must then call POST /api/users/me/verify-update with all the update data plus the verification code.
  • Non-Email Updates: If email is not being changed, updates are applied immediately and the Stripe customer is automatically updated
  • All-or-Nothing: When email verification is required, ALL pending changes (including firstName, lastName, username) must be resent to the verify-update endpoint. No partial updates are saved.
  • Email Uniqueness: Email addresses must be unique across all users. Validation is case-insensitive and ignores whitespace (e.g., "test@email.com", "TEST@email.com", and " test@email.com " are considered identical). If the email already exists, a 409 Conflict error is returned.
  • Requires CSRF token as it's a PUT request that modifies user data
PUT /api/users/:id

Update any user by ID (requires admin authentication and CSRF token)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3

Request Body:

{
    "username": "johnsmith",
    "email": "johnsmith@example.com",
    "firstName": "John",
    "lastName": "Smith",
    "userTypeCode": "SUBS",
    "isActive": true,
    "subscriptionExemptionStartsAt": "2024-01-01T00:00:00Z",
    "subscriptionExemptionEndsAt": "2024-12-31T23:59:59Z"
}

Response (200 OK):

{
    "id": 2,
    "username": "johnsmith",
    "email": "johnsmith@example.com",
    "typeCode": "SUBS",
    "typeName": "Subscribed",
    "firstName": "John",
    "lastName": "Smith",
    "isActive": true,
    "createdAt": "2023-01-15T08:30:00Z",
    "updatedAt": "2023-06-10T14:20:00Z",
    "verifiedAt": "2023-01-15T08:35:00Z",
    "subscriptionExemptionStartsAt": "2024-01-01T00:00:00Z",
    "subscriptionExemptionEndsAt": "2024-12-31T23:59:59Z",
    "legacyUserId": null
}

Error Responses:

{
    "message": "User not authenticated"
}

{
    "message": "User not found"
}

{
    "message": "Email address is already in use",
    "code": "EMAIL_EXISTS"
}

Notes:

  • This endpoint is restricted to admin users only
  • All fields in the request body are optional for partial updates
  • Admin can update userTypeCode (SUBS, NONS) and isActive status
  • Email Uniqueness: Email addresses must be unique across all users. Validation is case-insensitive and ignores whitespace. If the email already exists, a 409 Conflict error is returned.
  • The system automatically updates the corresponding Stripe customer if one exists
  • Requires CSRF token as it's a PUT request that modifies user data
PUT /api/users/verification-requests/new-request-code

Update verification requests with a new code (requires authentication and CSRF token)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3

Request Body:

{  
    "userReqList":[
        {
            "userId": 3,
            "request_type_code": "REGR"  // "REGR" for registration, "RPWR" for password reset
        },
        {
            "userId": 5,
            "request_type_code": "RPWR"
        }
    ]
}

Response (200 OK):

{
    "code": 200,
    "message": "Successfully created new verification requests.",
    "verificationCodes": [
        {
            "userId": 3,
            "verificationCode": "abc123def456",
            "requestType": "REGR"
        },
        {
            "userId": 5,
            "verificationCode": "xyz789uvw012",
            "requestType": "RPWR"
        }
    ]
}

Error Responses:

{
    "message": "One or more users not found."
}

Notes:

  • This endpoint generates new verification codes and sends them via email to the specified users
  • The request body should contain an array of user_id and request_type_code pairs
  • Valid request_type_code values are "REGR" (registration) and "RPWR" (password reset)
  • The system will send verification emails appropriate to the request type
  • Requires CSRF token as it's a PUT request that modifies data and triggers email sending
DELETE /api/users/:id

Delete a user by ID (requires authentication and CSRF token)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3

Response: 204 No Content

Error Responses:

{
    "message": "User not found"
}

{
    "message": "User not authenticated"
}

Notes:

  • This is a permanent deletion operation and cannot be undone
  • Requires CSRF token as it's a DELETE request that permanently removes data
DELETE /api/users/verification-requests/:id

Delete a verification request by ID (requires admin authentication and CSRF token)

Headers:

Authorization: Bearer ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr...
x-csrf-token: a8d7f9c6e5b4a3c2d1e0f9a8d7f6c5b4a3

Response: 204 No Content

Error Responses:

{
    "message": "Verification request not found"
}

{
    "message": "User not authenticated"
}

{
    "message": "Administrator access required"
}

Notes:

  • This endpoint is restricted to admin users only
  • This is a permanent deletion operation and cannot be undone
  • Deleting a verification request will prevent users from completing registration or password reset flows
  • Requires CSRF token as it's a DELETE request that permanently removes data
  • Recommended for admin use only to clean up orphaned or expired verification requests