Profile Service API

The Profile Service manages user profiles (referred to as “teammates”) and team structures. It runs on internal port 9100 and is exposed through the gateway at the /profile prefix.

Base paths:

Environment URL
Production https://api.zeswa.com/profile
Local https://localhost:18443/profile

All endpoints in this service require a valid Bearer token unless otherwise noted.


Teammate Endpoints

Teammates represent individual user profiles within the platform.

GET /profile/teammate

Retrieves a list of all teammates visible to the authenticated user.

Authentication: Required.

Success response – 200 OK

[
  {
    "_id": "64a1b2c3d4e5f6a7b8c9d0e1",
    "firstName": "Jane",
    "lastName": "Doe",
    "emailAddress": "jane.doe@example.com",
    "createdAt": "2024-01-15T10:30:00.000Z",
    "updatedAt": "2024-01-15T10:30:00.000Z"
  }
]

Error responses

Status Condition
401 Missing Authorization header.
403 Invalid or expired token.
500 Internal server error.

POST /profile/teammate

Creates a new teammate profile.

Authentication: Required.

Request body

{
  "firstName": "Jane",
  "lastName": "Doe",
  "emailAddress": "jane.doe@example.com"
}
Field Type Required Description
firstName string Yes The teammate’s first name.
lastName string Yes The teammate’s last name.
emailAddress string Yes Valid email address. Must be unique across the platform.

Success response – 201 Created

{
  "_id": "64a1b2c3d4e5f6a7b8c9d0e1",
  "firstName": "Jane",
  "lastName": "Doe",
  "emailAddress": "jane.doe@example.com",
  "createdAt": "2024-01-15T10:30:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z"
}

Error responses

Status Condition
400 Validation failed – missing required fields or invalid format.
401 Missing Authorization header.
403 Invalid or expired token.
409 A teammate with the same email address already exists.
500 Internal server error.

GET /profile/teammate/:id

Retrieves a single teammate by ID.

Authentication: Required.

Path parameters

Parameter Type Description
id string MongoDB ObjectId of the teammate.

Success response – 200 OK

{
  "_id": "64a1b2c3d4e5f6a7b8c9d0e1",
  "firstName": "Jane",
  "lastName": "Doe",
  "emailAddress": "jane.doe@example.com",
  "createdAt": "2024-01-15T10:30:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z"
}

Error responses

Status Condition
401 Missing Authorization header.
403 Invalid or expired token.
404 Teammate not found.
500 Internal server error.

PUT /profile/teammate/:id

Updates an existing teammate profile.

Authentication: Required.

Path parameters

Parameter Type Description
id string MongoDB ObjectId of the teammate.

Request body

Include only the fields to update:

{
  "firstName": "Janet",
  "lastName": "Doe"
}

Success response – 200 OK

Returns the updated teammate object.

Error responses

Status Condition
400 Validation failed.
401 Missing Authorization header.
403 Invalid or expired token.
404 Teammate not found.
500 Internal server error.

DELETE /profile/teammate/:id

Deletes a teammate profile.

Authentication: Required.

Path parameters

Parameter Type Description
id string MongoDB ObjectId of the teammate.

Success response – 200 OK

{
  "message": "Teammate deleted successfully"
}

Error responses

Status Condition
401 Missing Authorization header.
403 Invalid or expired token.
404 Teammate not found.
500 Internal server error.

Current User Endpoint

GET /profile/me

Retrieves the profile of the currently authenticated user.

Authentication: Required.

Success response – 200 OK

Returns the teammate object associated with the authenticated token.

{
  "_id": "64a1b2c3d4e5f6a7b8c9d0e1",
  "firstName": "Jane",
  "lastName": "Doe",
  "emailAddress": "jane.doe@example.com",
  "createdAt": "2024-01-15T10:30:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z"
}

Error responses

Status Condition
401 Missing Authorization header.
403 Invalid or expired token.
500 Internal server error.

Persona Auth Endpoints

Persona-specific PKCE + OTP authentication flow. These endpoints allow personas to log in using their emailAddress field, independent of the main Auth Service.

POST /profile/persona/login

Initiates a PKCE-based login flow for a persona. Sends a one-time password to the persona’s registered email address and returns a JWT token pair.

Authentication: None required. This is a public endpoint.

Request body

{
  "emailAddress": "bob.chen@example.com",
  "codeChallenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
}
Field Type Required Description
emailAddress string Yes The persona’s registered email address.
codeChallenge string Yes PKCE code challenge (SHA-256, base64url-encoded, 43-128 chars).

Success response – 200 OK

{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}

The accessToken contains emailAddress and personaId claims. Use it as the Bearer token for the verify step.

Error responses

Status Condition
404 No persona found for this email address.
500 Internal server error.

POST /profile/persona/verify

Verifies the OTP and PKCE code verifier for a persona login. Requires the Bearer token returned from the login step.

Authentication: Bearer token from /profile/persona/login (not the main platform auth token – no prior login session required).

Request body

{
  "otp": "12345678",
  "codeVerifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}
Field Type Required Description
otp string Yes The one-time password received via email.
codeVerifier string Yes PKCE code verifier matching the code challenge from login (43-128 chars).

Success response – 200 OK

{
  "error": false,
  "message": "OTP verified successfully",
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIs...",
  "persona": {
    "id": "2b3c4d5e-6f7a-4b2c-9d0e-1f2a3b4c5d02",
    "role": "Senior Developer",
    "emailAddress": "bob.chen@example.com",
    "user": {
      "id": 2,
      "firstName": "Bob",
      "lastName": "Chen",
      "username": "bob"
    }
  }
}

The accessToken and refreshToken are issued with the personaId claim and can be used directly to authenticate subsequent persona API calls.

Error responses

Status Condition
400 Invalid or expired OTP, or invalid code verifier.
401 Missing or invalid Bearer token.
404 Persona not found.
500 Internal server error.

GET /profile/persona/me

Returns the full persona document for the currently authenticated persona. Prefer GET /profile/persona/profile for new integrations.

Authentication: Bearer token from /profile/persona/login (must contain personaId claim).

Success response – 200 OK

{
  "id": "2b3c4d5e-6f7a-4b2c-9d0e-1f2a3b4c5d02",
  "user_id": "b2f1d5e3-ac8e-4a4b-9f2d-3e6a7b8c9d02",
  "user": {
    "id": 2,
    "firstName": "Bob",
    "lastName": "Chen",
    "username": "bob"
  },
  "role": "Senior Developer",
  "description": "Analytical introvert who values ethics, results, and deadlines.",
  "emailAddress": "bob.chen@example.com",
  "linkedinProfileUrl": "https://linkedin.com/in/bob-chen",
  "personalWebsite": "https://bobchen.io",
  "contactNumber": "+1-555-0102",
  "isCandidate": false,
  "createdAt": "2025-01-10T00:00:00.000Z",
  "updatedAt": "2025-01-10T00:00:00.000Z"
}

Error responses

Status Condition
400 Token does not contain a personaId claim.
401 Missing or invalid Bearer token.
404 Persona not found.
500 Internal server error.

GET /profile/persona/profile

Returns the full persona document for the currently authenticated persona. Reads personaId directly from the access token — no path parameter needed.

Authentication: Bearer token containing personaId claim (from /profile/persona/verify).

Success response – 200 OK

{
  "id": "4d5e6f7a-8b9c-4d4e-1f2a-3b4c5d6e7f04",
  "user_id": "d4b3f7a5-cea0-4c6d-b04f-5a8c9d0e1f04",
  "user": {
    "id": 4,
    "firstName": "Gaurav",
    "lastName": "Jassal",
    "username": "gaurav"
  },
  "role": "Full-Stack Developer",
  "roles": ["EMPLOYEE", "CANDIDATE"],
  "status": "ACTIVE",
  "emailAddress": "gauravjassal@gmail.com",
  "profileBio": "Software engineer passionate about team dynamics.",
  "contactNumber": "+1-555-0104",
  "linkedinProfile": "gaurav-jassal",
  "linkedinProfileUrl": "https://linkedin.com/in/gaurav-jassal",
  "portfolioURL": "https://gaurav.dev",
  "xHandler": "@gauravj",
  "instagramHandler": "@gauravjassal",
  "resumeContent": "...",
  "verifiedEmail": true,
  "verified": true,
  "createdAt": "2025-01-10T00:00:00.000Z",
  "updatedAt": "2026-05-04T16:44:35.502Z"
}

Error responses

Status Condition
400 Token does not contain a personaId claim.
401 Missing or invalid Bearer token.
404 Persona not found.
500 Internal server error.

GET /profile/persona/status

Returns how complete the authenticated persona’s profile is as a percentage, plus a per-field breakdown. Useful for prompting users to fill in missing profile details.

Authentication: Bearer token containing personaId claim.

Completeness fields

The following 9 fields are checked (each worth ~11%):

Field Description
firstName user.firstName present and non-empty
lastName user.lastName present and non-empty
emailAddress Top-level emailAddress present
profileBio Bio text filled in
contactNumber Phone number provided
linkedinProfile Either linkedinProfile or linkedinProfileUrl set
portfolioURL Portfolio URL provided
xHandler X (Twitter) handle provided
resumeContent Resume text provided

Note: roles is excluded — it is set by administrators and not part of user-completable profile fields.

Success response – 200 OK

{
  "personaId": "4d5e6f7a-8b9c-4d4e-1f2a-3b4c5d6e7f04",
  "completeness": 60,
  "isComplete": false,
  "fields": {
    "firstName": true,
    "lastName": true,
    "emailAddress": true,
    "profileBio": true,
    "contactNumber": true,
    "linkedinProfile": true,
    "portfolioURL": false,
    "xHandler": false,
    "resumeContent": false
  }
}

Error responses

Status Condition
400 Token does not contain a personaId claim.
401 Missing or invalid Bearer token.
404 Persona not found.
500 Internal server error.

Persona Endpoints

Personas represent role-based profiles associated with users. Each persona has a UUID (id field) for external references.

Persona schema

Persona documents include the following fields:

Field Type Description
id string UUID of the persona
user_id string UUID of the associated user
user object Embedded user details (firstName, lastName, username, emailAddress)
role string Free-text role title
roles string[] Typed roles: EMPLOYEE, CANDIDATE, HR, TEAM_MANAGER, MANAGER
status string ACTIVE, BLOCKED, or PENDING (default: PENDING)
emailAddress string Contact email
profileBio string Short bio
contactNumber string Phone number
linkedinProfile string LinkedIn handle/username
linkedinProfileUrl string Full LinkedIn profile URL
portfolioURL string Portfolio website URL
xHandler string X (Twitter) handle
instagramHandler string Instagram handle
resumeContent string Plain-text resume
verifiedEmail boolean Whether email has been verified
verified boolean Whether the persona is verified
isCandidate boolean Legacy flag
createdAt date Creation timestamp
updatedAt date Last update timestamp

GET /profile/persona

Retrieves a paginated list of personas. Supports optional filtering.

Authentication: Required.

Query parameters

Parameter Type Required Description
page string No Page number (default: 1).
limit string No Items per page (default: 10).
user_id string No Filter by user ID.
role string No Filter by role (case-insensitive partial match).

Success response – 200 OK

{
  "personas": [
    {
      "id": "2b3c4d5e-6f7a-4b2c-9d0e-1f2a3b4c5d02",
      "user_id": "b2f1d5e3-ac8e-4a4b-9f2d-3e6a7b8c9d02",
      "user": {
        "id": 2,
        "firstName": "Bob",
        "lastName": "Chen",
        "username": "bob"
      },
      "role": "Senior Developer",
      "description": "Analytical introvert who values ethics, results, and deadlines.",
      "emailAddress": "bob.chen@example.com",
      "linkedinProfileUrl": "https://linkedin.com/in/bob-chen",
      "personalWebsite": "https://bobchen.io",
      "contactNumber": "+1-555-0102",
      "isCandidate": false,
      "createdAt": "2025-01-10T00:00:00.000Z",
      "updatedAt": "2025-01-10T00:00:00.000Z"
    }
  ],
  "total": 8,
  "page": 1,
  "limit": 10,
  "totalPages": 1
}

Error responses

Status Condition
401 Missing Authorization header.
403 Invalid or expired token.
500 Internal server error.

POST /profile/persona

Creates a new persona.

Authentication: Required.

Request body

{
  "user_id": "b2f1d5e3-ac8e-4a4b-9f2d-3e6a7b8c9d02",
  "role": "Developer",
  "description": "Full-stack developer persona",
  "emailAddress": "developer@example.com",
  "linkedinProfileUrl": "https://linkedin.com/in/developer",
  "personalWebsite": "https://developer.dev",
  "contactNumber": "+1-555-0100"
}
Field Type Required Description
user_id string Yes UUID of the associated user.
role string Yes Role title for the persona.
description string Yes Description of the persona.
emailAddress string No Contact email address.
linkedinProfileUrl string No LinkedIn profile URL.
personalWebsite string No Personal website URL.
contactNumber string No Contact phone number.

Success response – 201 Created

Returns the newly created persona object.

Error responses

Status Condition
400 Validation failed – missing required fields or invalid format.
401 Missing Authorization header.
403 Invalid or expired token.
500 Internal server error.

GET /profile/persona/:id

Retrieves a single persona by its UUID.

Authentication: Required.

Path parameters

Parameter Type Description
id string UUID of the persona.

Success response – 200 OK

{
  "id": "2b3c4d5e-6f7a-4b2c-9d0e-1f2a3b4c5d02",
  "user_id": "b2f1d5e3-ac8e-4a4b-9f2d-3e6a7b8c9d02",
  "user": {
    "id": 2,
    "firstName": "Bob",
    "lastName": "Chen",
    "username": "bob"
  },
  "role": "Senior Developer",
  "description": "Analytical introvert who values ethics, results, and deadlines.",
  "emailAddress": "bob.chen@example.com",
  "linkedinProfileUrl": "https://linkedin.com/in/bob-chen",
  "personalWebsite": "https://bobchen.io",
  "contactNumber": "+1-555-0102",
  "isCandidate": false,
  "createdAt": "2025-01-10T00:00:00.000Z",
  "updatedAt": "2025-01-10T00:00:00.000Z"
}

Error responses

Status Condition
401 Missing Authorization header.
403 Invalid or expired token.
404 Persona not found.
500 Internal server error.

PUT /profile/persona/:id

Updates an existing persona. All body fields are optional. Nested user fields are merged individually (dot-notation), so partial user updates do not overwrite the entire subdocument.

Authentication: Required.

Path parameters

Parameter Type Description
id string UUID of the persona.

Request body

Include only the fields to update:

{
  "user": {
    "firstName": "Robert"
  },
  "role": "Senior Developer",
  "emailAddress": "bob.updated@example.com",
  "linkedinProfileUrl": "https://linkedin.com/in/bob-chen-updated",
  "personalWebsite": "https://bobchen.dev",
  "contactNumber": "+1-555-0199"
}
Field Type Required Description
user object No Partial user fields to merge (firstName, lastName, username, emailAddress).
user_id string No UUID of the associated user.
role string No Updated role title.
description string No Updated description.
emailAddress string No Updated contact email.
linkedinProfileUrl string No Updated LinkedIn URL.
personalWebsite string No Updated personal website URL.
contactNumber string No Updated contact number.

Success response – 200 OK

Returns the updated persona object.

Error responses

Status Condition
400 Validation failed.
401 Missing Authorization header.
403 Invalid or expired token.
404 Persona not found.
500 Internal server error.

PATCH /profile/persona/:persona_id

Partially updates a persona. Behaves identically to PUT /profile/persona/:id but uses the PATCH HTTP method. Only the fields present in the request body are updated — omitted fields are left unchanged.

Authentication: Required.

Path parameters

Parameter Type Description
persona_id string UUID of the persona.

Request body

Include only the fields to update. Supports all persona fields including nested user sub-fields:

{
  "profileBio": "Software engineer passionate about team dynamics.",
  "portfolioURL": "https://gaurav.dev",
  "xHandler": "@gauravj",
  "user": {
    "firstName": "Gaurav"
  }
}
Field Type Description
user object Partial user fields (firstName, lastName, username, emailAddress)
role string Free-text role title
profileBio string Short bio
emailAddress string Contact email
contactNumber string Phone number
linkedinProfile string LinkedIn handle
linkedinProfileUrl string Full LinkedIn URL
portfolioURL string Portfolio URL
xHandler string X (Twitter) handle
instagramHandler string Instagram handle
resumeContent string Plain-text resume
verifiedEmail boolean Email verified flag
verified boolean Persona verified flag

Note: roles and status are admin-only fields and cannot be set via this endpoint.

Success response – 200 OK

Returns the full updated persona document.

Error responses

Status Condition
400 Validation failed — invalid field format or enum value.
401 Missing Authorization header.
403 Invalid or expired token.
404 Persona not found.
500 Internal server error.

DELETE /profile/persona/:id

Deletes a persona by its UUID.

Authentication: Required.

Path parameters

Parameter Type Description
id string UUID of the persona.

Success response – 204 No Content

No body returned.

Error responses

Status Condition
401 Missing Authorization header.
403 Invalid or expired token.
404 Persona not found.
500 Internal server error.

Team Endpoints

Teams allow grouping of teammates for collaboration and management purposes.

GET /profile/team

Retrieves all teams accessible to the authenticated user.

Authentication: Required.

Success response – 200 OK

[
  {
    "_id": "64b2c3d4e5f6a7b8c9d0e1f2",
    "name": "Engineering",
    "description": "Core engineering team",
    "members": ["64a1b2c3d4e5f6a7b8c9d0e1"],
    "createdAt": "2024-01-15T10:30:00.000Z",
    "updatedAt": "2024-01-15T10:30:00.000Z"
  }
]

POST /profile/team

Creates a new team within an organisation. Only teamName and organisation_id are required; all other fields are optional.

Authentication: Required.

Request body

{
  "organisation_id": "64a1b2c3d4e5f6a7b8c9d0e1",
  "teamName": "Engineering",
  "teamDescription": "Core engineering team",
  "roleName": "Software Engineer",
  "managers": ["64a1b2c3d4e5f6a7b8c9d0e2"],
  "memberIds": ["64a1b2c3d4e5f6a7b8c9d0e3"]
}
Field Type Required Description
organisation_id string Yes ID of the organisation this team belongs to.
teamName string Yes Unique name of the team within the organisation.
teamDescription string No A brief description of the team.
roleName string No Role name associated with this team.
managers string[] No List of manager IDs. Defaults to [].
memberIds string[] No List of member IDs. Defaults to [].

Success response – 201 Created

Returns the newly created team object.

{
  "organisation_id": "64a1b2c3d4e5f6a7b8c9d0e1",
  "teamName": "Engineering",
  "teamDescription": "Core engineering team",
  "roleName": "Software Engineer",
  "managers": ["64a1b2c3d4e5f6a7b8c9d0e2"],
  "memberIds": ["64a1b2c3d4e5f6a7b8c9d0e3"],
  "createdAt": "2024-01-15T10:30:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z"
}

Error responses

Status Condition
400 Validation failed – missing required fields.
401 Missing Authorization header.
403 Invalid or expired token.
409 A team with this name already exists in the organisation.
500 Internal server error.

GET /profile/team/:id

Retrieves a single team by ID, including its member list.

Authentication: Required.

Path parameters

Parameter Type Description
id string MongoDB ObjectId of the team.

Success response – 200 OK

{
  "_id": "64b2c3d4e5f6a7b8c9d0e1f2",
  "name": "Engineering",
  "description": "Core engineering team",
  "members": ["64a1b2c3d4e5f6a7b8c9d0e1"],
  "createdAt": "2024-01-15T10:30:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z"
}

Error responses

Status Condition
401 Missing Authorization header.
403 Invalid or expired token.
404 Team not found.
500 Internal server error.

PUT /profile/team/:id

Updates an existing team. All fields are optional; only provided fields are updated.

Authentication: Required.

Path parameters

Parameter Type Description
id string MongoDB ObjectId of the team.

Request body

All fields are optional.

{
  "teamName": "Platform Engineering",
  "teamDescription": "Handles platform infrastructure",
  "roleName": "Platform Engineer",
  "managers": ["64a1b2c3d4e5f6a7b8c9d0e2"],
  "memberIds": ["64a1b2c3d4e5f6a7b8c9d0e3", "64a1b2c3d4e5f6a7b8c9d0e4"]
}
Field Type Required Description
organisation_id string No ID of the organisation this team belongs to.
teamName string No Updated team name (must be unique within the organisation).
teamDescription string No Updated description of the team.
roleName string No Updated role name associated with this team.
managers string[] No Updated list of manager IDs.
memberIds string[] No Updated list of member IDs.

Success response – 200 OK

Returns the updated team object.

{
  "organisation_id": "64a1b2c3d4e5f6a7b8c9d0e1",
  "teamName": "Platform Engineering",
  "teamDescription": "Handles platform infrastructure",
  "roleName": "Platform Engineer",
  "managers": ["64a1b2c3d4e5f6a7b8c9d0e2"],
  "memberIds": ["64a1b2c3d4e5f6a7b8c9d0e3", "64a1b2c3d4e5f6a7b8c9d0e4"],
  "createdAt": "2024-01-15T10:30:00.000Z",
  "updatedAt": "2024-01-16T08:00:00.000Z"
}

Error responses

Status Condition
400 Validation failed.
401 Missing Authorization header.
403 Invalid or expired token.
404 Team not found.
409 A team with this name already exists in the organisation.
500 Internal server error.

DELETE /profile/team/:id

Deletes a team.

Authentication: Required.

Path parameters

Parameter Type Description
id string MongoDB ObjectId of the team.

Success response – 200 OK

{
  "message": "Team deleted successfully"
}

Error responses

Status Condition
401 Missing Authorization header.
403 Invalid or expired token.
404 Team not found.
500 Internal server error.

Utility Endpoints

GET /profile/health

Returns the health status of the Profile Service.

Authentication: None required.

GET /profile/_ping

Lightweight liveness probe.

Authentication: None required.