Staff Profiles & Work History API
The users endpoints live under /api/v1/users and cover individual staff
profiles: personal details, profile pictures, portfolio, preferences, work
history, organisation role, and client assignments.
Authentication: every request needs a per-user API key. See API Keys & Authentication.
Most endpoints here follow one permission pattern: you can always act on your own profile, and organisation admins and owners can act on the profiles of people in their organisation. Where an endpoint differs, its section says so.
GET /api/v1/users/:id
Section titled “GET /api/v1/users/:id”Returns a user’s full profile. You can fetch your own profile; owners, admins, and managers can also fetch the profile of anyone who shares an organisation with them.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The user’s id. |
Example request
curl https://api.runnit.io/api/v1/users/<user-id> \ -H 'Authorization: Bearer rnk_your_key'Response is the user object (with credentials removed). The main fields:
{ "id": "<user-uuid>", "email": "priya@example.com", "firstName": "Priya", "lastName": "Shah", "displayName": "Priya Shah", "avatarUrl": null, "phone": "+61 400 000 000", "shortBio": "Producer with a broadcast background.", "longBio": "...", "industryStartDate": "2017-07-14", "roleStartDate": "2023-07-14", "yearsInIndustry": 9, "yearsInCurrentRole": 3, "currentTitle": "Senior Producer", "portfolioUrl": "https://priya.example.com", "skills": ["production", "editing"], "preferredWorkTypes": ["campaigns"], "acceptingProjects": true, "preferences": { "...": "..." }, "hourlyRate": 160, "actualCostRate": 95, "defaultRateCardRoleId": "<rate-card-role-uuid>", "status": "active", "lastLoginAt": "2026-07-06T23:41:12.000Z", "createdAt": "2024-11-20T22:14:00.000Z"}Errors
| Status | When |
|---|---|
403 | You are not the user and you do not hold an owner, admin, or manager role in an organisation the user belongs to. |
404 | No user exists with that id. |
PATCH /api/v1/users/:id
Section titled “PATCH /api/v1/users/:id”Updates a user’s core profile fields. You can update your own profile; organisation admins and owners can update anyone who shares an organisation with them. Managers cannot edit other people through this endpoint.
Request body (all optional)
| Name | Type | Required | Description |
|---|---|---|---|
email | string | No | A valid email address. Must not be in use by another account. |
firstName | string | No | Cannot be empty when supplied. |
lastName | string | No | Cannot be empty when supplied. |
displayName | string | No | Preferred display name. |
phone | string | No | Phone number. |
currentTitle | string | No | Job title. |
acceptingProjects | boolean | No | Whether the person is open to new project assignments. |
status | string | No | One of active, archived, or disabled. |
preferences | object | No | Replaces the user’s preferences object. Use the preferences endpoint below if you want merge behaviour. |
Example request
curl -X PATCH https://api.runnit.io/api/v1/users/<user-id> \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "currentTitle": "Executive Producer", "acceptingProjects": false }'Response is the full updated user object, in the same shape as
GET /api/v1/users/:id.
Errors
| Status | When |
|---|---|
400 | A supplied field fails validation. |
403 | You are not the user and not an admin or owner of a shared organisation. |
404 | No user exists with that id. |
409 | The email address is already in use. |
GET /api/v1/users/:id/avatar
Section titled “GET /api/v1/users/:id/avatar”Returns the user’s normalised profile picture as image/webp. The response
includes an ETag and a long-lived immutable cache policy. Use the user’s
avatarUrl field as the image source so the content-version value changes after
each replacement.
Errors
| Status | When |
|---|---|
404 | The user has no saved profile picture. |
POST /api/v1/users/:id/avatar
Section titled “POST /api/v1/users/:id/avatar”Uploads or replaces your own profile picture. This endpoint is self-only, so an admin or owner can’t change another person’s image.
Send multipart/form-data with one file field named avatar. Accepted input
formats are JPEG, PNG, and WebP, with a maximum file size of 10 MB. Runnit
corrects image orientation and saves a square 512 by 512 WebP image.
Example request
curl -X POST https://api.runnit.io/api/v1/users/<your-user-id>/avatar \ -H 'Authorization: Bearer rnk_your_key' \ -F 'avatar=@profile-photo.jpg'Response
{ "avatarUrl": "/api/users/<your-user-id>/avatar?v=<content-version>"}Errors
| Status | When |
|---|---|
400 | The file is missing, too large, unsupported, or doesn’t contain a valid image. |
403 | The path id is not your user id. |
404 | No user exists with that id. |
DELETE /api/v1/users/:id/avatar
Section titled “DELETE /api/v1/users/:id/avatar”Deletes your own profile picture and restores the initials avatar. This endpoint is self-only.
curl -X DELETE https://api.runnit.io/api/v1/users/<your-user-id>/avatar \ -H 'Authorization: Bearer rnk_your_key'Response is 204 No Content.
Errors
| Status | When |
|---|---|
403 | The path id is not your user id. |
404 | No user exists with that id. |
PATCH /api/v1/users/:id/portfolio
Section titled “PATCH /api/v1/users/:id/portfolio”Updates the portfolio section of a profile. Same permissions as
PATCH /api/v1/users/:id: yourself, or admins and owners of a shared
organisation.
Request body (all optional)
| Name | Type | Required | Description |
|---|---|---|---|
shortBio | string | No | Short biography. |
longBio | string | No | Long-form biography. |
industryStartDate | string | No | ISO 8601 date the person entered the industry, for example 2017-07-14. Must not be in the future. null clears it. |
roleStartDate | string | No | ISO 8601 date the person started their current role. Must not be in the future. null clears it. |
yearsInIndustry | number | No | Between 0 and 50. Accepted for compatibility: Runnit converts it to the industryStartDate it implies (today minus the value). Ignored when industryStartDate is also supplied. |
yearsInCurrentRole | number | No | Between 0 and 50. Accepted for compatibility: converted to the roleStartDate it implies. Ignored when roleStartDate is also supplied. |
currentTitle | string | No | Job title. |
portfolioUrl | string | No | Must start with http:// or https://. An empty string clears it. |
skills | array of strings | No | Replaces the skills list. An empty array clears it. |
preferredWorkTypes | array of strings | No | Replaces the preferred work types. An empty array clears it. |
Example request
curl -X PATCH https://api.runnit.io/api/v1/users/<user-id>/portfolio \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "shortBio": "Producer with a broadcast background.", "skills": ["production", "editing"] }'Response returns the updated portfolio fields:
{ "id": "<user-uuid>", "shortBio": "Producer with a broadcast background.", "longBio": null, "industryStartDate": "2017-07-14", "roleStartDate": "2023-07-14", "yearsInIndustry": 9, "yearsInCurrentRole": 3, "currentTitle": "Senior Producer", "portfolioUrl": "https://priya.example.com", "skills": ["production", "editing"], "preferredWorkTypes": ["campaigns"]}If the body contains no recognised fields, the response is
{ "message": "No updates provided" } and nothing changes.
Errors
| Status | When |
|---|---|
400 | A field fails validation, for example a start date in the future or yearsInIndustry outside 0 to 50. |
403 | You are not the user and not an admin or owner of a shared organisation. |
404 | No user exists with that id. |
PATCH /api/v1/users/:id/preferences
Section titled “PATCH /api/v1/users/:id/preferences”Updates your preferences. This endpoint is self-only: even admins cannot change another person’s preferences. The supplied object is merged into the existing preferences at the top level, so keys you omit are kept.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
preferences | object | Yes | Preference keys to set or overwrite. |
Example request
curl -X PATCH https://api.runnit.io/api/v1/users/<your-user-id>/preferences \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "preferences": { "timezone": "Australia/Melbourne" } }'Response is the full merged preferences object.
Errors
| Status | When |
|---|---|
400 | preferences is missing or not an object. |
403 | The id is not your own user id. |
PATCH /api/v1/users/:id/organization-role
Section titled “PATCH /api/v1/users/:id/organization-role”Changes a user’s role in an organisation. The caller must be an admin or owner
of that organisation, with two extra rules: only owners can assign the owner
role, and only owners can change another owner’s role.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
organizationId | UUID | Yes | The organisation to change the role in. With an API key this must be the key’s organisation. |
role | string | Yes | One of member, manager, admin, or owner. The freelancer role cannot be assigned here. |
Example request
curl -X PATCH https://api.runnit.io/api/v1/users/<user-id>/organization-role \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "organizationId": "<org-id>", "role": "manager" }'Response
{ "message": "User role updated successfully", "userId": "<user-uuid>", "organizationId": "<org-uuid>", "newRole": "manager"}Errors
| Status | When |
|---|---|
400 | organizationId is not a UUID, or role is not one of the four allowed values. |
403 | You are not an active member of the organisation, you are not an admin or owner, you tried to assign owner without being an owner, or you tried to change an owner’s role without being an owner. |
404 | The target user is not an active member of the organisation. |
Work history
Section titled “Work history”Work history entries (also called portfolio projects) describe past projects a person has worked on. They come from two sources: entries added through this API are always manual, and Runnit also creates entries automatically when delivery projects complete. Editing an automatic entry through the API converts it to manual, which stops the automatic sync from overwriting your change.
All five work history endpoints use the same permissions: yourself, or admins and owners of a shared organisation.
GET /api/v1/users/:id/projects
Section titled “GET /api/v1/users/:id/projects”Lists a user’s work history entries.
curl https://api.runnit.io/api/v1/users/<user-id>/projects \ -H 'Authorization: Bearer rnk_your_key'Response is a JSON array:
[ { "id": "<entry-uuid>", "userId": "<user-uuid>", "clientName": "Harbour Retail Group", "projectName": "Winter Launch", "briefSummary": "Campaign delivery across social and retail.", "startDate": "2025-06-01", "endDate": "2025-08-31", "durationMonths": 3, "projectRole": "Producer", "technologies": ["premiere"], "deliverables": ["30s hero film"], "teamSize": 6, "projectUrl": null, "status": "completed", "isFeatured": false, "displayOrder": 0, "source": "auto", "sourceProjectId": "<project-uuid>" }]source is manual or auto; sourceProjectId links an automatic entry to
the delivery project it came from.
POST /api/v1/users/:id/projects
Section titled “POST /api/v1/users/:id/projects”Adds a work history entry. Entries created here are always stored as manual.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
clientName | string | Yes | Who the work was for. |
projectName | string | Yes | The project’s name. |
briefSummary | string | Yes | Short description of the work. |
startDate | string | Yes | ISO 8601 date, for example 2025-06-01. |
endDate | string | No | ISO 8601 date. |
projectRole | string | Yes | The person’s role on the project. |
durationMonths | integer | No | 0 or higher. |
durationDays | integer | No | 0 or higher. |
teamSize | integer | No | 1 or higher. |
projectUrl | string | No | Must be a valid URL. |
technologies | array of strings | No | Tools and technologies used. |
deliverables | array of strings | No | What was delivered. |
Example request
curl -X POST https://api.runnit.io/api/v1/users/<user-id>/projects \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "clientName": "Harbour Retail Group", "projectName": "Winter Launch", "briefSummary": "Campaign delivery across social and retail.", "startDate": "2025-06-01", "endDate": "2025-08-31", "projectRole": "Producer" }'Response is 201 with the created entry, in the same shape as the list
endpoint.
Errors
| Status | When |
|---|---|
400 | A required field is missing, a date is not ISO 8601, or a numeric field is out of range. |
403 | You are not the user and not an admin or owner of a shared organisation. |
PATCH /api/v1/users/:id/projects/:projectId
Section titled “PATCH /api/v1/users/:id/projects/:projectId”Updates a work history entry. Accepts the same fields as creation, all optional, with the same constraints. Editing marks the entry as manual, so the automatic sync stops updating it.
curl -X PATCH https://api.runnit.io/api/v1/users/<user-id>/projects/<entry-id> \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "teamSize": 8 }'Response is the updated entry. Returns 404 if the entry does not belong
to the user.
DELETE /api/v1/users/:id/projects/:projectId
Section titled “DELETE /api/v1/users/:id/projects/:projectId”Deletes a work history entry. Returns 204 No Content, or 404 if the entry
does not belong to the user.
curl -X DELETE https://api.runnit.io/api/v1/users/<user-id>/projects/<entry-id> \ -H 'Authorization: Bearer rnk_your_key'POST /api/v1/users/:id/projects/reorder
Section titled “POST /api/v1/users/:id/projects/reorder”Sets the display order of a user’s work history entries.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
projectOrders | array | Yes | One object per entry: id (UUID of the entry) and order (integer, 0 or higher). |
Example request
curl -X POST https://api.runnit.io/api/v1/users/<user-id>/projects/reorder \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "projectOrders": [ { "id": "<entry-id-1>", "order": 0 }, { "id": "<entry-id-2>", "order": 1 } ] }'Response
{ "message": "Projects reordered successfully" }Client assignments
Section titled “Client assignments”These endpoints manage which clients a person is restricted to, within the organisation the API key is bound to. They pair with the client-side view in the Clients API.
GET /api/v1/users/:id/clients
Section titled “GET /api/v1/users/:id/clients”Returns the clients this user is explicitly assigned to. You can view your own assignments; owners, admins, and managers can view anyone’s in the organisation.
curl https://api.runnit.io/api/v1/users/<user-id>/clients \ -H 'Authorization: Bearer rnk_your_key'Response
{ "hasSpecificAssignments": true, "assignedClients": [ { "id": "<client-org-uuid>", "name": "Harbour Retail Group", "slug": "harbour-retail" } ]}When hasSpecificAssignments is false, the person isn’t explicitly named on
any client staffing list. They remain eligible on clients using the all-staff
default and on projects where they are an explicit project member.
Errors
| Status | When |
|---|---|
403 | You are not an active member of the organisation, or you are viewing someone else without an owner, admin, or manager role. |
PUT /api/v1/users/:id/clients
Section titled “PUT /api/v1/users/:id/clients”Replaces the user’s client assignments. Only organisation admins and owners can call it.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
clientIds | array of UUIDs | Yes | Client organisation ids whose default staffing lists include the user. An empty array removes the user from explicit client lists without changing project memberships. |
Example request
curl -X PUT https://api.runnit.io/api/v1/users/<user-id>/clients \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "clientIds": ["<client-org-id>"] }'Response
{ "success": true, "hasAllClientAccess": false, "clients": [ { "id": "<client-org-uuid>", "name": "Harbour Retail Group", "slug": "harbour-retail" } ]}Errors
| Status | When |
|---|---|
400 | clientIds is missing, not an array, or contains a value that is not a UUID. |
403 | You are not an admin or owner of the organisation. |
Endpoints intended for the app
Section titled “Endpoints intended for the app”DELETE /api/v1/users/:id exists but is designed for the Runnit app: it soft
deletes your own account and requires your password in the request body.
Avatar upload endpoints are also reserved for the app. None of these are
recommended for integrations.
Next step
Section titled “Next step”Manage memberships, roles, and rates at the organisation level with the Organisations & Staff API.