Profiles & Directory
Your own profile and avatar, the people directory with its filters, the org chart, skills and the contributor leaderboard.
Your own profile and avatar, the people directory with its filters, the org chart, skills and the contributor leaderboard.
Generated from the OpenAPI spec by
npm run docs:api -w api. Do not edit this page — change the decorators inapps/api/src/and regenerate.
Conventions
- Auth — a session on every route: the cookie on web, the bearer token on mobile. See Authentication.
- Errors — every non-2xx response has the body described in Errors.
- Types — a
?after a field name means it is optional;| nullmeans it is present but may be null.
Endpoints
GET /api/v1/users/me/profile — The signed-in person’s profile
Call this straight after sign-in: it is the cheapest confirmation that the stored token is still good, and it carries role, which decides whether the admin screens and the announcement composer are shown at all.
Returns 200 → ProfileResponse
| Error | When |
|---|---|
401 | No valid session. Clear the stored token and sign in again. |
403 | Signed in, but not allowed to do this. |
PATCH /api/v1/users/me/profile — Edit your own profile
A partial update — send only the fields that changed. joinedOn is dropped here rather than rejected: it is an HR record, writable only by a super admin through PATCH /people/{userId}/profile. Sending projects replaces the whole timeline, so send the full list including the entries that did not change.
Body — UpdateProfileDto
Returns 200 → ProfileResponse
| Error | When |
|---|---|
400 | The request was rejected. message is an array of field errors when validation failed. |
401 | No valid session. Clear the stored token and sign in again. |
403 | Signed in, but not allowed to do this. |
POST /api/v1/users/me/avatar — Replace your avatar
JPG, PNG or WebP, up to 2MB. Returns the whole profile with image already updated, so there is nothing to re-fetch afterwards. Upload the photo at any size: it is cropped square and re-encoded to a 256px WebP before it is stored, so do not resize it yourself.
Body — multipart/form-data, one part named file.
Returns 201 → ProfileResponse
| Error | When |
|---|---|
400 | The request was rejected. message is an array of field errors when validation failed. |
401 | No valid session. Clear the stored token and sign in again. |
403 | Signed in, but not allowed to do this. |
413 | The file is larger than 2MB. |
PATCH /api/v1/people/{userId}/profile — Edit somebody else’s profile
Super admins only. Same fields as the self-serve route, plus joinedOn.
| Path parameter | Type | Notes |
|---|---|---|
userId | string |
Body — UpdateProfileDto
Returns 200 → ProfileResponse
| Error | When |
|---|---|
400 | The request was rejected. message is an array of field errors when validation failed. |
401 | No valid session. Clear the stored token and sign in again. |
403 | Signed in, but not allowed to do this. |
404 | No such person. |
POST /api/v1/people/{userId}/avatar — Replace somebody else’s avatar
Super admins only. Same limits as your own.
| Path parameter | Type | Notes |
|---|---|---|
userId | string |
Body — multipart/form-data, one part named file.
Returns 201 → ProfileResponse
| Error | When |
|---|---|
401 | No valid session. Clear the stored token and sign in again. |
403 | Signed in, but not allowed to do this. |
413 | The file is larger than 2MB. |
GET /api/v1/org-chart — The reporting tree, flat
Everybody in one array. Build the hierarchy on the client by matching managerId to id; a node with managerId: null is a root.
Returns 200 → OrgNodeResponse[]
| Error | When |
|---|---|
401 | No valid session. Clear the stored token and sign in again. |
403 | Signed in, but not allowed to do this. |
GET /api/v1/people/skills — Every skill anyone has listed
For the skill picker’s suggestions. Plain strings.
Returns 200 → string[]
| Error | When |
|---|---|
401 | No valid session. Clear the stored token and sign in again. |
403 | Signed in, but not allowed to do this. |
GET /api/v1/people/facets — The directory’s filter options
Built from the people actually listed, so a filter can never offer a choice that returns nobody. Fetch once and hold it for the session.
Returns 200 → DirectoryFacetsResponse
| Error | When |
|---|---|
401 | No valid session. Clear the stored token and sign in again. |
403 | Signed in, but not allowed to do this. |
GET /api/v1/people/contributors — Top contributors
Super-admin picks first (isFeatured), then the most active people to fill the rest of the list.
| Query | Type | Required | Notes |
|---|---|---|---|
limit | number | no | Rows per page. Defaults to 20, capped at 50. |
Returns 200 → ContributorResponse[]
| Error | When |
|---|---|
401 | No valid session. Clear the stored token and sign in again. |
403 | Signed in, but not allowed to do this. |
GET /api/v1/people/leaderboard — Engagement leaderboard
People ranked by the reactions, comments and shares their posts received from other people in the period — dislikes and self-engagement excluded. Only people with a score above zero are listed. period defaults to all.
| Query | Type | Required | Notes |
|---|---|---|---|
period | week | month | all | no | |
limit | number | no | Rows per page. Defaults to 20, capped at 50. |
Returns 200 → LeaderboardEntryResponse[]
| Error | When |
|---|---|
401 | No valid session. Clear the stored token and sign in again. |
403 | Signed in, but not allowed to do this. |
GET /api/v1/people — The people directory
Cursor-paged. Every filter is optional and they combine with AND. Take the skill, role and location values from GET /people/facets rather than inventing them.
| Query | Type | Required | Notes |
|---|---|---|---|
q | string | no | Free text over name, title and bio. |
skill | string | no | |
role | string | no | A job title, not an access level. |
location | string | no | |
team | string | no | A team id from GET /teams. |
status | active | pending | no | |
sort | name | recent | posts | no | Defaults to name. |
cursor | string | no | The nextCursor from the previous page. Omit for the first. |
limit | number | no | Rows per page. Defaults to 20, capped at 50. |
Returns 200 → Page<DirectoryEntryResponse> — see Paging
| Error | When |
|---|---|
401 | No valid session. Clear the stored token and sign in again. |
403 | Signed in, but not allowed to do this. |
GET /api/v1/people/{userId} — One person’s full profile
| Path parameter | Type | Notes |
|---|---|---|
userId | string |
Returns 200 → ProfileResponse
| Error | When |
|---|---|
401 | No valid session. Clear the stored token and sign in again. |
403 | Signed in, but not allowed to do this. |
404 | No such person. |
Data models
ProfileResponse
| Field | Type | Notes |
|---|---|---|
id | string | |
name | string | |
email | string | |
role | member | super_admin | |
image | string | null | A path under /api/v1/files/avatars/... — resolve against the API origin. |
title | string | |
location | string | |
bio | string | |
currentWork | string | What they are working on right now. |
department | "" | engineering | product | design | hr | business_development | digital_marketing | finance | operations | support | legal | '' when they have not picked a team yet. |
team | ProfileTeamResponse | null | |
skills | string[] | |
learningInterests | string[] | |
projects | ProjectResponse[] | |
instagram | string | |
linkedin | string | |
github | string | |
x | string | X, formerly Twitter. |
youtube | string | |
portfolio | string | A personal site. |
hobbies | string[] | |
interests | string[] | |
favoriteMusic | string | |
favoriteMovie | string | |
favoriteFood | string | |
funFact | string | |
dreamDestination | string | |
superpower | string | |
drink | string | Whatever they actually drink — not a fixed set. |
joinedAt | string ISO 8601 | When the login was provisioned — not when the person joined. |
joinedOn | string | null | Employment start date, YYYY-MM-DD, set by a super admin. Prefer it over joinedAt wherever a joining date is shown; null until one is set. |
dateOfBirth | string | null | Date of birth, YYYY-MM-DD. Returned only to the person themselves and to super admins — null for everyone else, and until it is set. |
status | active | pending | deactivated | |
presence | active | away | offline | Whether they are here right now, computed from heartbeats — unrelated to status, which is about the account. Everyone never seen is offline. |
lastSeenAt | string ISO 8601 | null | When a client of theirs was last open. Null if never. |
postCount | number | |
manager | ManagerResponse | null | |
reportCount | number | How many people report to them. |
isComplete | boolean | Whether the profile has enough filled in to stop nagging about it. |
ProfileTeamResponse
| Field | Type | Notes |
|---|---|---|
id | string | |
name | string | |
color | blue | emerald | violet | amber | rose | cyan | orange | slate | A palette token, not a CSS colour — map it to your own styles. |
ProjectResponse
| Field | Type | Notes |
|---|---|---|
id | string | |
name | string | |
description | string | Empty string when unset, never null. |
contribution | string | Superseded by contributions; still filled for older rows. |
role | string | |
startedAt | string | null | |
endedAt | string | null | null while the work is still running — show it as "Present". |
status | in_progress | completed | on_hold | |
tags | string[] | |
contributions | string[] | The "Key Contributions" bullets. |
link | string | Empty string when unset, never null. |
ManagerResponse
| Field | Type | Notes |
|---|---|---|
id | string | |
name | string | |
image | string | null |
UpdateProfileDto
| Field | Type | Notes |
|---|---|---|
skills? | string[] | At most 20, 40 characters each. Replaces the whole list. |
learningInterests? | string[] | At most 10. Replaces the whole list. · 40 chars each |
projects? | ProjectDto[] | The whole work timeline, at most 10 entries. Sending this REPLACES every entry, so include the ones that did not change. |
hobbies? | string[] | At most 12. Replaces the whole list. · 40 chars each |
interests? | string[] | At most 12. Replaces the whole list. · 40 chars each |
name? | string | ≤ 120 chars |
title? | string | ≤ 120 chars |
location? | string | ≤ 120 chars |
department? | "" | engineering | product | design | hr | business_development | digital_marketing | finance | operations | support | legal | '' clears it back to "not set"; anything else must be a known team. |
teamId? | string | null | null unassigns them; omitting the key leaves the team untouched. |
bio? | string | ≤ 500 chars |
currentWork? | string | ≤ 1000 chars |
managerId? | string | null | null clears the reporting line; omitting the key leaves it untouched. |
joinedOn? | string | null | Employment start date, YYYY-MM-DD. Super admins only — the self-serve route strips it (see ProfilesController), because it is an HR record rather than something you state about yourself. null or '' clears it; omitting the key leaves it untouched. |
dateOfBirth? | string | null | Date of birth, YYYY-MM-DD. Editable by the person themselves; shown to nobody else. null or '' clears it; omitting the key leaves it alone. |
instagram? | string | Instagram — a handle or a full URL. '' clears it. · ≤ 200 chars |
linkedin? | string | LinkedIn — a handle or a full URL. '' clears it. · ≤ 200 chars |
github? | string | GitHub — a handle or a full URL. '' clears it. · ≤ 200 chars |
x? | string | X — a handle or a full URL. '' clears it. · ≤ 200 chars |
youtube? | string | YouTube — a handle or a full URL. '' clears it. · ≤ 200 chars |
portfolio? | string | Portfolio — a handle or a full URL. '' clears it. · ≤ 200 chars |
favoriteMusic? | string | Favourite music. · ≤ 200 chars |
favoriteMovie? | string | Favourite movie. · ≤ 200 chars |
favoriteFood? | string | Favourite food. · ≤ 200 chars |
funFact? | string | Fun fact. · ≤ 200 chars |
dreamDestination? | string | Dream destination. · ≤ 200 chars |
superpower? | string | Superpower. · ≤ 200 chars |
drink? | string | Coffee or tea. · ≤ 200 chars |
ProjectDto
| Field | Type | Notes |
|---|---|---|
link? | string | An http(s) URL. Empty string clears it. · ≤ 500 chars |
tags? | string[] | Tech stack, channels, workstreams — whatever the department labels with. · ≤ 10 items, 40 chars each |
contributions? | string[] | The "Key Contributions" bullets. At most 8. · 200 chars each |
name | string | ≤ 120 chars |
description? | string | ≤ 500 chars |
contribution? | string | ≤ 500 chars |
role? | string | What the person did on it — "Full-stack Developer", "Recruitment Lead". · ≤ 120 chars |
startedAt? | string | null | Month precision in the UI, a full date on the wire. null on endedAt is what the timeline shows as "Present". |
endedAt? | string | null | |
status? | in_progress | completed | on_hold |
OrgNodeResponse
| Field | Type | Notes |
|---|---|---|
id | string | |
name | string | |
image | string | null | |
title | string | |
currentWork | string | |
status | active | pending | deactivated | |
managerId | string | null | null for the root of the tree. |
DirectoryFacetsResponse
| Field | Type | Notes |
|---|---|---|
skills | string[] | |
titles | string[] | |
locations | string[] |
ContributorResponse
| Field | Type | Notes |
|---|---|---|
id | string | |
name | string | |
image | string | null | |
title | string | |
note | string | Why a super admin pinned them; empty for the automatic tail. |
isFeatured | boolean | |
postCount | number |
LeaderboardEntryResponse
| Field | Type | Notes |
|---|---|---|
rank | number | Shared by people on the same score. |
id | string | |
name | string | |
image | string | null | |
title | string | |
postCount | number | Posts written in the period. Not scored. |
reactionCount | number | Reactions from other people, dislikes excluded. |
commentCount | number | Comments and replies from other people. |
shareCount | number | Shares by other people. |
score | number | Reactions, comments and shares received, weighted. |
DirectoryEntryResponse
| Field | Type | Notes |
|---|---|---|
id | string | |
name | string | |
image | string | null | |
title | string | |
location | string | |
bio | string | |
currentWork | string | |
skills | string[] | |
team | ProfileTeamResponse | null | |
joinedAt | string ISO 8601 | |
status | active | pending | deactivated | |
presence | active | away | offline | Whether they are here right now, computed from heartbeats — unrelated to status, which is about the account. Everyone never seen is offline. |
lastSeenAt | string ISO 8601 | null | When a client of theirs was last open. Null if never. |
postCount | number |