WORKFORCEOS

FRONTEND GUIDE FOR AI CODING AGENTS - PART 4 - Profile Management

This document is a part of a REST API guide for the workforceos project. It is designed for AI agents that will generate frontend code to consume the project’s backend.

This document includes information and api descriptions about building a profile page in the frontend using the auth service profile api calls. Avatar images are stored in the auth service’s database buckets — no external bucket service is needed for avatars.

The project has 1 auth service, 1 notification service, 1 BFF service, and 10 business services, plus other helper services such as bucket and realtime. In this document you will use the auth service (including its database bucket endpoints for avatar uploads).

Each service is a separate microservice application and listens for HTTP requests at different service URLs.

Services may be deployed to the preview server, staging server, or production server. Therefore, each service has 3 access URLs. The frontend application must support all deployment environments during development, and the user should be able to select the target API server on the home page.

Accessing the backend

Each backend service has its own URL for each deployment environment. Users may want to test the frontend in one of the three deployments—preview, staging, or production. Please ensure that the register and login pages include a deployment server selection option so that, as the frontend coding agent, you can set the base URL for all services.

The base URL of the application in each environment is as follows:

For the auth service, service urls are as follows:

For each other service, the service URL will be given in the service sections.

Any request that requires login must include a valid token in the Bearer authorization header.

Multi-Tenancy Management

THIS APPLICATION IS MULTI-TENANT

This application is mult-tanant, it means; as a SaaS application, it isolates each tenant-data from each other. The tenants are called company and a data object with the same name exist to store and manage the tenant information. The company data instances are referenced from other data objects or entities as companyId. Any data object which is in tenant level (some data objects may still stay in SaaS level) has a companyId property which attaches it to a company tenant. But this value is added to the data object instance in the time of creation by the system according to the current company user is navigating.

For human readability each company has also got a companyCodename which is a unique form of its name. Frontend must forward tenant targeting with the tenant header:

headers["mbx-company-codename"] = "babil";

When no companyCodename is given in the related parameters, application will assume that the access targets the root target, the Saas level. The companyCodename of SaaS level is always root. When no companyCodename is specified, root is assumed as the current companyCodename.

Note that, logins and registrations are all tenant scoped. If any a tenant-lvel api is login-required then it will check for a company specific token according to the target company defined in a related parameter with its codename. The application creates the cookies with companyCodename prefixes but it is recommended for the frontend application to populate the bearer header with the user’s company related token.

Not that all company tenants are managed in the same backend services, however a company tanant frontend should be specific to one company. Frontend should have a technical way to understand which company is targeted by the user, for production frontend application this should be managed by the subdomains that represents the codename of the company like,

https://babil.storeCreator.com

Using the subdomain, frontend will distinguish that the codename is babil and will atatch it to all tenant-level api calls.

The url may also be used for the SaaS directly with a more general wording like,

https://www.storeCreator.com

However, this subdomain management may not be handled easily in the AI frontend builders that they use their own preview urls, an other url structure should be handled to simulate the subdomain behaviour for tenant forwarding.

It may be like,

https://{somePreviewUrlOfABuilderPlatform}/babil/login

In some way, frontend should provide a simple and practical way to the user to target a specific tenant.

Sample Company Tenant

The applcation backend also includes a sample tenant created with a sample tenant owner, who has the same email and password of the superadmin. This sample is created in the backend to be able to test the multi-tenant behaviour of the frontend at the beginning.

The sample company has a codename babil and can be targetd by attaching this codename to the API calls. So whwn the front end home page is built, homepages both for the SaaS and tenant shoul be created and ready to be tested.

Avatar Storage (Database Buckets)

User avatars and tenant avatars are stored directly in the auth service database using database buckets (dbBuckets). This means avatar files are uploaded to and downloaded from the auth service itself — no external bucket service is needed.

The auth service provides these avatar buckets:

User Avatar Bucket

Upload: POST {authBaseUrl}/bucket/userAvatars/upload Download by ID: GET {authBaseUrl}/bucket/userAvatars/download/{fileId} Download by Key: GET {authBaseUrl}/bucket/userAvatars/download/key/{accessKey}

Upload example (multipart/form-data):

const formData = new FormData();
formData.append('file', croppedImageBlob, 'avatar.png');

const response = await fetch(`${authBaseUrl}/bucket/userAvatars/upload`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
  },
  body: formData,
});

const result = await response.json();
// result.file.id — the file ID (use for download URL)
// result.file.accessKey — 12-char key for public sharing
// result.file.fileName, result.file.mimeType, result.file.fileSize

After uploading, update the user’s avatar field with the download URL:

const avatarUrl = `${authBaseUrl}/bucket/userAvatars/download/${result.file.id}`;
// OR use the access key for a shorter, shareable URL:
const avatarUrl = `${authBaseUrl}/bucket/userAvatars/download/key/${result.file.accessKey}`;

await updateProfile({ avatar: avatarUrl });

Displaying avatars: Since read access is public, avatar URLs can be used directly in <img> tags without any authentication token:

<img src={user.avatar} alt="Avatar" />

Company Avatar Bucket

Upload: POST {authBaseUrl}/bucket/companyAvatars/upload Download by ID: GET {authBaseUrl}/bucket/companyAvatars/download/{fileId} Download by Key: GET {authBaseUrl}/bucket/companyAvatars/download/key/{accessKey}

Same configuration as user avatars: public read, authenticated write, images only, 5 MB max.

Upload and display work the same way — upload the image, store the download URL in the company’s avatar field.

Listing and Deleting Avatars

The auth service also provides metadata APIs for each bucket (auto-generated):

API Method Path Description
getUserAvatarsFile GET /v1/userAvatarsFiles/:id Get file metadata (no binary)
listUserAvatarsFiles GET /v1/userAvatarsFiles List files with filtering
deleteUserAvatarsFile DELETE /v1/userAvatarsFiles/:id Delete file and its data

| getCompanyAvatarsFile | GET | /v1/companyAvatarsFiles/:id | Get tenant avatar metadata | | listCompanyAvatarsFiles | GET | /v1/companyAvatarsFiles | List tenant avatars | | deleteCompanyAvatarsFile | DELETE | /v1/companyAvatarsFiles/:id | Delete tenant avatar |

Profile Page

Design a profile page to manage (view and edit) user information. The profile page should include an avatar upload component that uploads to the database bucket.

On the profile page, you will need 4 business APIs: getUser , updateProfile, updateUserPassword and archiveProfile. Do not rely on the /currentuser response for profile data, because it contains session information. The most recent user data is in the user database and should be accessed via the getUser business API.

The updateProfile, updateUserPassword and archiveProfile can only be called by the users themselves. They are designed specific to the profile page.

Avatar upload workflow:

  1. User selects an image → crop with react-easy-crop (install it, do not implement your own)
  2. Convert cropped area to a Blob
  3. Upload to POST {authBaseUrl}/bucket/userAvatars/upload with the access token
  4. Get back the file metadata (id, accessKey)
  5. Construct the download URL: {authBaseUrl}/bucket/userAvatars/download/key/{accessKey}
  6. Call PATCH {authBaseUrl}/v1/profile with body { avatar: downloadUrl }no userId in the URL. The route is session-based; the BE resolves the target user from the access token.

Note that the user cannot change/update their email or roleId.

For password update you should make a separate block in the UI, so that user can enter old password, new password and confirm new password before calling the updateUserPassword.

Here are the 3 auth APIs—getUser , updateProfile and updateUserPassword— as follows: You can access these APIs through the auth service base URL, {appUrl}/auth-api.

Get User API

This api is used by admin roles or the users themselves to get the user profile information.

Rest Route

The getUser API REST controller can be triggered via the following route:

/v1/users/:userId

Rest Request Parameters

The getUser api has got 1 regular request parameter

Parameter Type Required Population
userId ID true request.params?.[“userId”]
userId : This id paremeter is used to query the required data object.

REST Request To access the api you can use the REST controller with the path GET /v1/users/:userId

  axios({
    method: 'GET',
    url: `/v1/users/${userId}`,
    data: {
    
    },
    params: {
    
        }
  });

REST Response

{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "user",
	"method": "GET",
	"action": "get",
	"appVersion": "Version",
	"rowCount": 1,
	"user": {
		"id": "ID",
		"email": "String",
		"password": "String",
		"fullname": "String",
		"avatar": "String",
		"roleId": "String",
		"emailVerified": "Boolean",
		"companyId": "ID",
		"isActive": true,
		"recordVersion": "Integer",
		"createdAt": "Date",
		"updatedAt": "Date",
		"_owner": "ID"
	}
}

Update Profile API

This route is used by users to update their own profiles. The target user is always the session user — no userId is needed in the URL.

Rest Route

The updateProfile API REST controller can be triggered via the following route:

/v1/profile

Rest Request Parameters

The updateProfile api has got 2 regular request parameters

Parameter Type Required Population
fullname String false request.body?.[“fullname”]
avatar String false request.body?.[“avatar”]
fullname : A string value to represent the fullname of the user
avatar : The avatar url of the user. A random avatar will be generated if not provided

REST Request To access the api you can use the REST controller with the path PATCH /v1/profile

  axios({
    method: 'PATCH',
    url: '/v1/profile',
    data: {
            fullname:"String",  
            avatar:"String",  
    
    },
    params: {
    
        }
  });

REST Response

{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "user",
	"method": "PATCH",
	"action": "update",
	"appVersion": "Version",
	"rowCount": 1,
	"user": {
		"id": "ID",
		"email": "String",
		"password": "String",
		"fullname": "String",
		"avatar": "String",
		"roleId": "String",
		"emailVerified": "Boolean",
		"companyId": "ID",
		"isActive": true,
		"recordVersion": "Integer",
		"createdAt": "Date",
		"updatedAt": "Date",
		"_owner": "ID"
	}
}

Update Userpassword API

This route is used to update the password of users in the profile page by users themselves. The target user is always the session user — no userId is needed in the URL.

Rest Route

The updateUserPassword API REST controller can be triggered via the following route:

/v1/profile/password

Rest Request Parameters

The updateUserPassword api has got 2 regular request parameters

Parameter Type Required Population
oldPassword String true request.body?.[“oldPassword”]
newPassword String true request.body?.[“newPassword”]
oldPassword : The old password of the user that will be overridden bu the new one. Send for double check.
newPassword : The new password of the user to be updated

REST Request To access the api you can use the REST controller with the path PATCH /v1/profile/password

  axios({
    method: 'PATCH',
    url: '/v1/profile/password',
    data: {
            oldPassword:"String",  
            newPassword:"String",  
    
    },
    params: {
    
        }
  });

REST Response

{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "user",
	"method": "PATCH",
	"action": "update",
	"appVersion": "Version",
	"rowCount": 1,
	"user": {
		"id": "ID",
		"email": "String",
		"password": "String",
		"fullname": "String",
		"avatar": "String",
		"roleId": "String",
		"emailVerified": "Boolean",
		"companyId": "ID",
		"isActive": true,
		"recordVersion": "Integer",
		"createdAt": "Date",
		"updatedAt": "Date",
		"_owner": "ID"
	}
}

Archiving A Profile

A user may want to archive their profile. So the profile page should include an archive section for the users to archive their accounts. When an account is archived, it is marked as archived and an aarchiveDate is atteched to the profile. All user data is kept in the database for 1 month after user archived. If user tries to login or register with the same email, the account will be activated again. But if no login or register occures in 1 month after archiving, the profile and its related data will be deleted permanenetly. So in the profile page,

  1. The arcihve options should be accepted after user writes a text like (“ARCHİVE MY ACCOUNT”) to a confirmation dialog, so that frontend UX can ensure this is not an unconscious request.
  2. The user should be warned about the process, that his account will be available for a restore for 1 month.

The archive api, can only be called by the users themselves and its used as follows.

Archive Profile API

This api is used by users to archive their own profiles. The target user is always the session user — no userId is needed in the URL.

Rest Route

The archiveProfile API REST controller can be triggered via the following route:

/v1/profile

Rest Request Parameters The archiveProfile api has got no request parameters.

REST Request To access the api you can use the REST controller with the path DELETE /v1/profile

  axios({
    method: 'DELETE',
    url: '/v1/profile',
    data: {
    
    },
    params: {
    
        }
  });

REST Response

{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "user",
	"method": "DELETE",
	"action": "delete",
	"appVersion": "Version",
	"rowCount": 1,
	"user": {
		"id": "ID",
		"email": "String",
		"password": "String",
		"fullname": "String",
		"avatar": "String",
		"roleId": "String",
		"emailVerified": "Boolean",
		"companyId": "ID",
		"isActive": false,
		"recordVersion": "Integer",
		"createdAt": "Date",
		"updatedAt": "Date",
		"_owner": "ID"
	}
}

Tenant Profile Page (Tenant Manager)

In multi-tenant projects, also build a tenant profile page for tenantOwner and tenantAdmin roles. This page allows these roles to view/update tenant standard and custom properties.

Use tenant-scoped profile APIs:

For SaaS-level tenant management pages (superAdmin, saasAdmin, saasUser), use:

Do not use deprecated tenant APIs such as getCompanyByCodename or listRegisteredCompanies.

Get Companyprofile API

Get tenant profile information in tenant level. A private route for tenantOwner and tenantAdmin.

Rest Route

The getCompanyProfile API REST controller can be triggered via the following route:

/v1/companyprofile

Rest Request Parameters The getCompanyProfile api has got no request parameters.

REST Request To access the api you can use the REST controller with the path GET /v1/companyprofile

  axios({
    method: 'GET',
    url: '/v1/companyprofile',
    data: {
    
    },
    params: {
    
        }
  });

REST Response

{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "company",
	"method": "GET",
	"action": "get",
	"appVersion": "Version",
	"rowCount": 1,
	"company": {
		"id": "ID",
		"name": "String",
		"codename": "String",
		"fullname": "String",
		"avatar": "String",
		"ownerId": "ID",
		"industry": "String",
		"companySize": "String",
		"isActive": true,
		"recordVersion": "Integer",
		"createdAt": "Date",
		"updatedAt": "Date",
		"_owner": "ID"
	}
}

Update Company API

Update a company by id. An admin route which can be called by admins or tenant owners.

Rest Route

The updateCompany API REST controller can be triggered via the following route:

/v1/companies/:companyId

Rest Request Parameters

The updateCompany api has got 6 regular request parameters

Parameter Type Required Population
companyId ID true request.params?.[“companyId”]
name String false request.body?.[“name”]
fullname String false request.body?.[“fullname”]
avatar String false request.body?.[“avatar”]
industry String false request.body?.[“industry”]
companySize String false request.body?.[“companySize”]
companyId : This id paremeter is used to select the required data object that will be updated
name : A string value to represent one word name of the company
fullname : A string value to represent the fullname of the company
avatar : A string value represent the url of the company avatar. Keep null for random avatar.
industry : The industry the company operates in (e.g., Technology, Healthcare, Finance, etc.)
companySize : The size of the company (e.g., 1-10, 11-50, 51-200, etc.)

REST Request To access the api you can use the REST controller with the path PATCH /v1/companies/:companyId

  axios({
    method: 'PATCH',
    url: `/v1/companies/${companyId}`,
    data: {
            name:"String",  
            fullname:"String",  
            avatar:"String",  
            industry:"String",  
            companySize:"String",  
    
    },
    params: {
    
        }
  });

REST Response

{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "company",
	"method": "PATCH",
	"action": "update",
	"appVersion": "Version",
	"rowCount": 1,
	"company": {
		"id": "ID",
		"name": "String",
		"codename": "String",
		"fullname": "String",
		"avatar": "String",
		"ownerId": "ID",
		"industry": "String",
		"companySize": "String",
		"isActive": true,
		"recordVersion": "Integer",
		"createdAt": "Date",
		"updatedAt": "Date",
		"_owner": "ID"
	}
}

Get Companyaccount API

Get tenant account information by id. A private SaaS-level route for superAdmin, saasAdmin and saasUser.

Rest Route

The getCompanyAccount API REST controller can be triggered via the following route:

/v1/companyaccounts/:companyId

Rest Request Parameters

The getCompanyAccount api has got 1 regular request parameter

Parameter Type Required Population
companyId ID true request.params?.[“companyId”]
companyId : The id of the company account to fetch

REST Request To access the api you can use the REST controller with the path GET /v1/companyaccounts/:companyId

  axios({
    method: 'GET',
    url: `/v1/companyaccounts/${companyId}`,
    data: {
    
    },
    params: {
    
        }
  });

REST Response

{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "company",
	"method": "GET",
	"action": "get",
	"appVersion": "Version",
	"rowCount": 1,
	"company": {
		"id": "ID",
		"name": "String",
		"codename": "String",
		"fullname": "String",
		"avatar": "String",
		"ownerId": "ID",
		"industry": "String",
		"companySize": "String",
		"isActive": true,
		"recordVersion": "Integer",
		"createdAt": "Date",
		"updatedAt": "Date",
		"_owner": "ID"
	}
}

List Companiesaccounts API

List tenant accounts in SaaS level for superAdmin, saasAdmin and saasUser.

Rest Route

The listCompaniesAccounts API REST controller can be triggered via the following route:

/v1/companyaccounts

Rest Request Parameters

Filter Parameters

The listCompaniesAccounts api supports 5 optional filter parameters for filtering list results:

name (String): A string value to represent one word name of the company

codename (String): A string value to represent a unique code name for the company which is generated automatically using name

fullname (String): A string value to represent the fullname of the company

avatar (String): A string value represent the url of the company avatar. Keep null for random avatar.

ownerId (ID): An ID value to represent the user id of company owner who created the tenant

REST Request To access the api you can use the REST controller with the path GET /v1/companyaccounts

  axios({
    method: 'GET',
    url: '/v1/companyaccounts',
    data: {
    
    },
    params: {
    
        // Filter parameters (see Filter Parameters section above)
        // name: '<value>' // Filter by name
        // codename: '<value>' // Filter by codename
        // fullname: '<value>' // Filter by fullname
        // avatar: '<value>' // Filter by avatar
        // ownerId: '<value>' // Filter by ownerId
            }
  });

REST Response

{
	"status": "OK",
	"statusCode": "200",
	"elapsedMs": 126,
	"ssoTime": 120,
	"source": "db",
	"cacheKey": "hexCode",
	"userId": "ID",
	"sessionId": "ID",
	"requestId": "ID",
	"dataName": "companies",
	"method": "GET",
	"action": "list",
	"appVersion": "Version",
	"rowCount": "\"Number\"",
	"companies": [
		{
			"id": "ID",
			"name": "String",
			"codename": "String",
			"fullname": "String",
			"avatar": "String",
			"ownerId": "ID",
			"industry": "String",
			"companySize": "String",
			"isActive": true,
			"recordVersion": "Integer",
			"createdAt": "Date",
			"updatedAt": "Date",
			"_owner": "ID"
		},
		{},
		{}
	],
	"paging": {
		"pageNumber": "Number",
		"pageRowCount": "NUmber",
		"totalRowCount": "Number",
		"pageCount": "Number"
	},
	"filters": [],
	"uiPermissions": []
}

After you complete this step, please ensure you have not made the following common mistakes:

  1. Avatar uploads go to the auth service’s database bucket endpoints (/bucket/userAvatars/upload), not to an external bucket service. Use the same accessToken (Bearer header) for both auth APIs and avatar bucket uploads — no bucket-specific tokens are needed.
  2. Note that any api call to the application backend is based on a service base url, in this prompt all auth apis (including avatar bucket endpoints) should be called by the auth service base url.
  3. On the profile page, fetch the latest user data from the service using getUser. The /currentuser API is session-stored data; the latest data is in the database.
  4. When you upload the avatar image on the profile page, use the returned download URL as the user’s avatar property and update the user record when the Save button is clicked.

After this prompt, the user may give you new instructions to update your first output or provide subsequent prompts about the project.